本文目录导读:

- 使用Spring Security OAuth2(推荐)
- 自定义OAuth2客户端实现
- 使用第三方OAuth2登录(如Google)
- 使用JWT实现OAuth2令牌
- 完整的OAuth2流程示例
- 配置示例(application.yml)
- 安全建议
- 注意事项
我来详细介绍Java中实现OAuth2的几种常见方式:
使用Spring Security OAuth2(推荐)
添加依赖(Spring Boot)
<!-- Spring Boot 2.x -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>
配置授权服务器
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private DataSource dataSource;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client-id")
.secret(passwordEncoder.encode("client-secret"))
.authorizedGrantTypes("authorization_code", "password", "refresh_token")
.scopes("read", "write")
.redirectUris("http://localhost:8080/callback")
.accessTokenValiditySeconds(3600)
.refreshTokenValiditySeconds(86400);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.authenticationManager(authenticationManager)
.tokenStore(tokenStore())
.approvalStoreDisabled(false);
}
@Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
配置资源服务器
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.antMatchers("/api/**").authenticated()
.and()
.csrf().disable();
}
}
自定义OAuth2客户端实现
@Service
public class OAuth2ClientService {
private final RestTemplate restTemplate;
@Value("${oauth2.client.id}")
private String clientId;
@Value("${oauth2.client.secret}")
private String clientSecret;
@Value("${oauth2.token.url}")
private String tokenUrl;
public OAuth2ClientService() {
this.restTemplate = new RestTemplate();
}
// 获取授权码URL
public String getAuthorizationUrl(String redirectUri, String state) {
return "https://oauth-server.com/oauth/authorize?" +
"response_type=code" +
"&client_id=" + clientId +
"&redirect_uri=" + redirectUri +
"&state=" + state;
}
// 交换授权码获取访问令牌
public OAuth2Token exchangeCodeForToken(String code, String redirectUri) {
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "authorization_code");
params.add("code", code);
params.add("redirect_uri", redirectUri);
params.add("client_id", clientId);
params.add("client_secret", clientSecret);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String, String>> request =
new HttpEntity<>(params, headers);
ResponseEntity<OAuth2Token> response = restTemplate.postForEntity(
tokenUrl, request, OAuth2Token.class);
return response.getBody();
}
// 刷新令牌
public OAuth2Token refreshToken(String refreshToken) {
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "refresh_token");
params.add("refresh_token", refreshToken);
params.add("client_id", clientId);
params.add("client_secret", clientSecret);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String, String>> request =
new HttpEntity<>(params, headers);
ResponseEntity<OAuth2Token> response = restTemplate.postForEntity(
tokenUrl, request, OAuth2Token.class);
return response.getBody();
}
}
@Data
public class OAuth2Token {
@JsonProperty("access_token")
private String accessToken;
@JsonProperty("token_type")
private String tokenType;
@JsonProperty("expires_in")
private int expiresIn;
@JsonProperty("refresh_token")
private String refreshToken;
@JsonProperty("scope")
private String scope;
}
使用第三方OAuth2登录(如Google)
@Controller
public class OAuth2LoginController {
@Autowired
private OAuth2ClientService oAuth2ClientService;
// 重定向到OAuth2提供商
@GetMapping("/login/google")
public String loginWithGoogle(HttpSession session) {
String state = UUID.randomUUID().toString();
session.setAttribute("oauth_state", state);
String redirectUri = "http://localhost:8080/callback/google";
String authUrl = oAuth2ClientService.getAuthorizationUrl(redirectUri, state);
return "redirect:" + authUrl;
}
// 处理回调
@GetMapping("/callback/google")
public String handleGoogleCallback(
@RequestParam("code") String code,
@RequestParam("state") String state,
HttpSession session) {
// 验证state防止CSRF
String savedState = (String) session.getAttribute("oauth_state");
if (!state.equals(savedState)) {
return "error";
}
// 交换令牌
String redirectUri = "http://localhost:8080/callback/google";
OAuth2Token token = oAuth2ClientService.exchangeCodeForToken(code, redirectUri);
// 保存令牌到session
session.setAttribute("access_token", token.getAccessToken());
session.setAttribute("refresh_token", token.getRefreshToken());
return "redirect:/dashboard";
}
}
使用JWT实现OAuth2令牌
@Component
public class JwtTokenProvider {
@Value("${jwt.secret}")
private String jwtSecret;
@Value("${jwt.expiration}")
private int jwtExpiration;
public String generateToken(Authentication authentication) {
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
Date now = new Date();
Date expiryDate = new Date(now.getTime() + jwtExpiration);
return Jwts.builder()
.setSubject(userPrincipal.getId())
.setIssuedAt(new Date())
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
public String getUserIdFromJWT(String token) {
Claims claims = Jwts.parser()
.setSigningKey(jwtSecret)
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
}
public boolean validateToken(String authToken) {
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
return true;
} catch (SignatureException ex) {
// 无效签名
} catch (MalformedJwtException ex) {
// 无效令牌
} catch (ExpiredJwtException ex) {
// 令牌过期
} catch (UnsupportedJwtException ex) {
// 不支持的令牌
} catch (IllegalArgumentException ex) {
// 空令牌
}
return false;
}
}
完整的OAuth2流程示例
@RestController
@RequestMapping("/api/oauth2")
public class OAuth2Controller {
@Autowired
private OAuth2Service oAuth2Service;
// 获取授权码
@PostMapping("/authorize")
public ResponseEntity<AuthorizationResponse> authorize(
@RequestBody AuthorizationRequest request) {
String authCode = oAuth2Service.generateAuthorizationCode(
request.getClientId(),
request.getRedirectUri(),
request.getScope());
return ResponseEntity.ok(new AuthorizationResponse(authCode));
}
// 获取访问令牌
@PostMapping("/token")
public ResponseEntity<OAuth2Token> getToken(
@RequestBody TokenRequest request) {
OAuth2Token token = oAuth2Service.generateToken(
request.getGrantType(),
request.getCode(),
request.getRedirectUri(),
request.getClientId(),
request.getClientSecret());
return ResponseEntity.ok(token);
}
// 验证令牌
@GetMapping("/verify")
public ResponseEntity<TokenInfo> verifyToken(
@RequestHeader("Authorization") String authHeader) {
String token = authHeader.replace("Bearer ", "");
TokenInfo tokenInfo = oAuth2Service.verifyToken(token);
return ResponseEntity.ok(tokenInfo);
}
// 撤销令牌
@PostMapping("/revoke")
public ResponseEntity<Void> revokeToken(
@RequestParam("token") String token) {
oAuth2Service.revokeToken(token);
return ResponseEntity.ok().build();
}
}
配置示例(application.yml)
oauth2:
client:
id: your-client-id
secret: your-client-secret
redirect-uri: http://localhost:8080/callback
server:
authorization-url: https://oauth-server.com/oauth/authorize
token-url: https://oauth-server.com/oauth/token
user-info-url: https://oauth-server.com/userinfo
jwt:
secret: your-jwt-secret-key
expiration: 86400000 # 24小时
安全建议
@Component
public class OAuth2SecurityUtils {
// 验证回调URL
public boolean isValidRedirectUri(String redirectUri) {
// 白名单验证
List<String> allowedUris = Arrays.asList(
"http://localhost:8080/callback",
"https://yourdomain.com/callback"
);
return allowedUris.contains(redirectUri);
}
// 生成安全的state参数
public String generateSecureState() {
byte[] bytes = new byte[32];
new SecureRandom().nextBytes(bytes);
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
}
// 验证令牌作用域
public boolean hasScope(String token, String requiredScope) {
// 实现作用域验证逻辑
return true;
}
}
注意事项
- 使用HTTPS:所有OAuth2通信都应使用HTTPS
- 安全存储客户端密钥:不要硬编码,使用配置服务或环境变量
- 设置合理的令牌过期时间:access token短时效,refresh token可配置更长
- 实施CSRF保护:使用state参数
- 日志记录:记录认证失败尝试
- 速率限制:防止暴力破解
选择哪种实现方式取决于你的具体需求:
- Spring Security OAuth2:适合Spring Boot应用
- 自定义实现:需要更多控制时
- 第三方OAuth2:集成社交登录