Java案例如何实现OAuth2?

wen python案例 4

本文目录导读:

Java案例如何实现OAuth2?

  1. 使用Spring Security OAuth2(推荐)
  2. 自定义OAuth2客户端实现
  3. 使用第三方OAuth2登录(如Google)
  4. 使用JWT实现OAuth2令牌
  5. 完整的OAuth2流程示例
  6. 配置示例(application.yml)
  7. 安全建议
  8. 注意事项

我来详细介绍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;
    }
}

注意事项

  1. 使用HTTPS:所有OAuth2通信都应使用HTTPS
  2. 安全存储客户端密钥:不要硬编码,使用配置服务或环境变量
  3. 设置合理的令牌过期时间:access token短时效,refresh token可配置更长
  4. 实施CSRF保护:使用state参数
  5. 日志记录:记录认证失败尝试
  6. 速率限制:防止暴力破解

选择哪种实现方式取决于你的具体需求:

  • Spring Security OAuth2:适合Spring Boot应用
  • 自定义实现:需要更多控制时
  • 第三方OAuth2:集成社交登录

抱歉,评论功能暂时关闭!