本文目录导读:

- Maven依赖配置
- 配置文件 application.yml
- 主应用类
- 安全配置类
- 自定义请求实体转换器
- 自定义OAuth2用户服务
- 自定义OAuth2用户类
- 控制器类
- API客户端调用示例
- HTML模板
- 配置文件 application.properties (备用)
- 运行说明
- 完整功能说明
我来提供一个完整的Java实现OAuth2客户端的案例,使用Spring Security OAuth2 Client框架。
Maven依赖配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>oauth2-client-demo</artifactId>
<version>1.0.0</version>
<name>oauth2-client-demo</name>
<description>OAuth2 Client Demo</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Security OAuth2 Client -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Thymeleaf模板引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- WebClient for HTTP requests -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
配置文件 application.yml
spring:
application:
name: oauth2-client-demo
security:
oauth2:
client:
registration:
github:
client-id: your-github-client-id
client-secret: your-github-client-secret
scope:
- read:user
- user:email
google:
client-id: your-google-client-id
client-secret: your-google-client-secret
scope:
- email
- profile
# 自定义OAuth2服务提供商
custom:
client-id: your-custom-client-id
client-secret: your-custom-client-secret
client-name: Custom OAuth2 Provider
authorization-grant-type: authorization_code
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
scope:
- read
- write
provider:
github:
authorization-uri: https://github.com/login/oauth/authorize
token-uri: https://github.com/login/oauth/access_token
user-info-uri: https://api.github.com/user
user-name-attribute: id
google:
authorization-uri: https://accounts.google.com/o/oauth2/v2/auth
token-uri: https://oauth2.googleapis.com/token
user-info-uri: https://www.googleapis.com/oauth2/v3/userinfo
user-name-attribute: sub
custom:
authorization-uri: https://your-provider.com/oauth2/authorize
token-uri: https://your-provider.com/oauth2/token
user-info-uri: https://your-provider.com/api/userinfo
user-name-attribute: sub
server:
port: 8080
logging:
level:
org.springframework.security: DEBUG
主应用类
package com.example.oauth2client;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class OAuth2ClientApplication {
public static void main(String[] args) {
SpringApplication.run(OAuth2ClientApplication.class, args);
}
}
安全配置类
package com.example.oauth2client.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/login", "/oauth2/**", "/error").permitAll()
.anyRequest().authenticated()
.and()
.oauth2Login()
.loginPage("/login")
.defaultSuccessUrl("/home", true)
.successHandler(successHandler())
.userInfoEndpoint()
.userService(oAuth2UserService())
.and()
.failureUrl("/login?error=true")
.and()
.logout()
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID");
}
@Bean
public OAuth2UserService<OAuth2UserRequest, OAuth2User> oAuth2UserService() {
DefaultOAuth2UserService service = new DefaultOAuth2UserService();
service.setRequestEntityConverter(new CustomRequestEntityConverter());
return new CustomOAuth2UserService(service);
}
@Bean
public AuthenticationSuccessHandler successHandler() {
SavedRequestAwareAuthenticationSuccessHandler handler =
new SavedRequestAwareAuthenticationSuccessHandler();
handler.setDefaultTargetUrl("/home");
handler.setTargetUrlParameter("redirect");
handler.setAlwaysUseDefaultTargetUrl(false);
return handler;
}
}
自定义请求实体转换器
package com.example.oauth2client.config;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.RequestEntity;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequestEntityConverter;
public class CustomRequestEntityConverter
implements Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>> {
private final OAuth2AuthorizationCodeGrantRequestEntityConverter
defaultConverter = new OAuth2AuthorizationCodeGrantRequestEntityConverter();
@Override
public RequestEntity<?> convert(OAuth2AuthorizationCodeGrantRequest request) {
RequestEntity<?> entity = defaultConverter.convert(request);
// 可以在这里添加自定义的Headers或修改请求参数
return entity;
}
}
自定义OAuth2用户服务
package com.example.oauth2client.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Component
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
private final DefaultOAuth2UserService defaultOAuth2UserService;
public CustomOAuth2UserService() {
this.defaultOAuth2UserService = new DefaultOAuth2UserService();
}
public CustomOAuth2UserService(DefaultOAuth2UserService defaultOAuth2UserService) {
this.defaultOAuth2UserService = defaultOAuth2UserService;
}
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
log.info("Loading user from provider: {}", userRequest.getClientRegistration().getRegistrationId());
OAuth2User oauth2User = defaultOAuth2UserService.loadUser(userRequest);
// 创建自定义User对象,可以在这里添加业务逻辑
Map<String, Object> attributes = new HashMap<>(oauth2User.getAttributes());
attributes.put("provider", userRequest.getClientRegistration().getRegistrationId());
return new CustomOAuth2User(
oauth2User.getAuthorities(),
attributes,
userRequest.getClientRegistration().getProviderDetails()
.getUserInfoEndpoint().getUserNameAttributeName()
);
}
}
自定义OAuth2用户类
package com.example.oauth2client.config;
import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import java.util.Collection;
import java.util.Map;
@Getter
public class CustomOAuth2User extends DefaultOAuth2User {
private final String provider;
public CustomOAuth2User(Collection<? extends GrantedAuthority> authorities,
Map<String, Object> attributes,
String nameAttributeKey) {
super(authorities, attributes, nameAttributeKey);
this.provider = (String) attributes.get("provider");
}
public String getEmail() {
return (String) this.getAttributes().get("email");
}
public String getNameAttribute() {
return this.getAttribute(this.getNameAttributeKey());
}
public String getPicture() {
return (String) this.getAttributes().get("picture");
}
public String getAvatarUrl() {
// 对GitHub支持
return (String) this.getAttributes().getOrDefault("avatar_url",
this.getAttributes().get("picture"));
}
}
控制器类
package com.example.oauth2client.controller;
import com.example.oauth2client.config.CustomOAuth2User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import java.security.Principal;
import java.util.Map;
@Slf4j
@Controller
public class HomeController {
@GetMapping("/")
public String index(Principal principal) {
return principal != null ? "redirect:/home" : "index";
}
@GetMapping("/login")
public String login() {
return "login";
}
@GetMapping("/home")
public String home(@AuthenticationPrincipal OAuth2User oauth2User, Model model) {
model.addAttribute("name", oauth2User.getAttribute("name"));
model.addAttribute("email", oauth2User.getAttribute("email"));
model.addAttribute("avatar", getAvatar(oauth2User));
if (oauth2User instanceof CustomOAuth2User) {
CustomOAuth2User customUser = (CustomOAuth2User) oauth2User;
model.addAttribute("provider", customUser.getProvider());
}
return "home";
}
@GetMapping("/profile")
public String profile(@AuthenticationPrincipal OAuth2User oauth2User, Model model) {
Map<String, Object> attributes = oauth2User.getAttributes();
model.addAttribute("attributes", attributes);
model.addAttribute("authorities", oauth2User.getAuthorities());
return "profile";
}
@GetMapping("/user")
public String user(@AuthenticationPrincipal OAuth2User oauth2User, Model model) {
model.addAttribute("user", oauth2User);
return "user";
}
private String getAvatar(OAuth2User oauth2User) {
String avatarUrl = oauth2User.getAttribute("avatar_url");
if (avatarUrl == null) {
avatarUrl = oauth2User.getAttribute("picture");
}
return avatarUrl != null ? avatarUrl : "/images/default-avatar.png";
}
}
API客户端调用示例
package com.example.oauth2client.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
@Slf4j
@Controller
public class ApiController {
@Autowired
private OAuth2AuthorizedClientService authorizedClientService;
@Autowired
private RestTemplate restTemplate;
@GetMapping("/api/data")
public String getApiData(Model model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication instanceof OAuth2AuthenticationToken) {
OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
String clientRegistrationId = oauthToken.getAuthorizedClientRegistrationId();
OAuth2AuthorizedClient client = authorizedClientService.loadAuthorizedClient(
clientRegistrationId,
oauthToken.getName()
);
if (client != null) {
OAuth2AccessToken accessToken = client.getAccessToken();
model.addAttribute("accessToken", accessToken.getTokenValue());
// 使用access token调用第三方API
String apiUrl = "https://api.github.com/user/emails";
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(accessToken.getTokenValue());
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(headers);
try {
ResponseEntity<String> response = restTemplate.exchange(
apiUrl, HttpMethod.GET, entity, String.class);
model.addAttribute("apiResponse", response.getBody());
} catch (Exception e) {
log.error("Error calling API", e);
model.addAttribute("error", e.getMessage());
}
}
}
return "api-data";
}
}
HTML模板
login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">Login</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h3 class="text-center">Login</h3>
</div>
<div class="card-body">
<th:block th:if="${param.error}">
<div class="alert alert-danger">
Login failed. Please try again.
</div>
</th:block>
<div class="text-center">
<h4>Choose a provider:</h4>
<a th:href="@{/oauth2/authorization/github}"
class="btn btn-dark btn-lg btn-block mb-3">
Login with GitHub
</a>
<br>
<a th:href="@{/oauth2/authorization/google}"
class="btn btn-primary btn-lg btn-block">
Login with Google
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
home.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">Home</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="/">OAuth2 Client</a>
<div class="navbar-nav ms-auto">
<a class="nav-link" th:href="@{/profile}">Profile</a>
<a class="nav-link" th:href="@{/api/data}">API Data</a>
<form th:action="@{/logout}" method="post" class="d-inline">
<button type="submit" class="btn btn-outline-light">Logout</button>
</form>
</div>
</div>
</nav>
<div class="container mt-5">
<div class="row">
<div class="col-md-6 mx-auto">
<div class="card">
<div class="card-body text-center">
<img th:src="${avatar}" class="rounded-circle mb-3"
style="width: 100px; height: 100px;" alt="Avatar">
<h2 th:text="${name}">User Name</h2>
<p class="text-muted" th:text="${email}">email@example.com</p>
<p>
<span class="badge bg-success" th:text="${provider}">
Provider
</span>
</p>
<div class="mt-4">
<h5>User Information:</h5>
<ul class="list-group">
<li class="list-group-item" th:text="${name}">Name</li>
<li class="list-group-item" th:text="${email}">Email</li>
<li class="list-group-item">Provider: <span th:text="${provider}">Provider</span></li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
配置文件 application.properties (备用)
# 应用配置
spring.application.name=oauth2-client-demo
server.port=8080
# GitHub OAuth2配置
spring.security.oauth2.client.registration.github.client-id=${GITHUB_CLIENT_ID}
spring.security.oauth2.client.registration.github.client-secret=${GITHUB_CLIENT_SECRET}
spring.security.oauth2.client.registration.github.scope=read:user,user:email
# Google OAuth2配置
spring.security.oauth2.client.registration.google.client-id=${GOOGLE_CLIENT_ID}
spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_CLIENT_SECRET}
spring.security.oauth2.client.registration.google.scope=email,profile
# OAuth2 Provider配置
spring.security.oauth2.client.provider.github.authorization-uri=https://github.com/login/oauth/authorize
spring.security.oauth2.client.provider.github.token-uri=https://github.com/login/oauth/access_token
spring.security.oauth2.client.provider.github.user-info-uri=https://api.github.com/user
spring.security.oauth2.client.provider.github.user-name-attribute=id
运行说明
-
获取OAuth2客户端凭证:在GitHub、Google等平台上注册应用,获取Client ID和Client Secret。
-
配置环境变量:
export GITHUB_CLIENT_ID=your_client_id export GITHUB_CLIENT_SECRET=your_client_secret export GOOGLE_CLIENT_ID=your_client_id export GOOGLE_CLIENT_SECRET=your_client_secret
-
启动应用:
mvn spring-boot:run
-
访问应用:打开浏览器访问
http://localhost:8080
完整功能说明
- OAuth2登录:支持GitHub、Google等OAuth2提供商
- 用户信息获取:从提供商获取用户基本信息
- 自定义用户服务:扩展默认的用户服务,处理自定义业务逻辑
- API调用:使用访问令牌调用受保护的API
- 会话管理:管理OAuth2会话和令牌
- 多提供商支持:支持多个OAuth2提供商
这个案例提供了完整的OAuth2客户端实现,包括配置、安全设置、控制器、模板和API调用示例,可以根据实际需求进行扩展和定制。