← 返回首页
🔒

OAuth2授权架构:PKCE流程详解

📂 architecture ⏱ 2 min 325 words

OAuth2授权架构:PKCE流程详解

OAuth2授权码流程

OAuth2授权码流程是最安全的授权方式之一,适用于服务端应用和单页应用。

// OAuth2授权端点
@RestController
@RequestMapping("/oauth2")
public class OAuth2AuthorizationEndpoint {
    
    @GetMapping("/authorize")
    public ResponseEntity<Void> authorize(
            @RequestParam("response_type") String responseType,
            @RequestParam("client_id") String clientId,
            @RequestParam("redirect_uri") String redirectUri,
            @RequestParam("scope") String scope,
            @RequestParam(value = "state", required = false) String state,
            @RequestParam(value = "code_challenge", required = false) String codeChallenge,
            @RequestParam(value = "code_challenge_method", required = false) String codeChallengeMethod) {
        
        // 验证客户端
        Client client = clientService.validate(clientId, redirectUri);
        
        // 生成授权码
        AuthorizationCode code = authorizationService.generateCode(
            client, scope, state, codeChallenge, codeChallengeMethod
        );
        
        // 重定向回客户端
        String redirectUrl = redirectUri + 
            "?code=" + code.getValue() + 
            (state != null ? "&state=" + state : "");
        
        return ResponseEntity.status(302)
            .header("Location", redirectUrl)
            .build();
    }
}

PKCE增强安全

PKCE(Proof Key for Code Exchange)防止授权码被拦截攻击,特别适用于公共客户端。

// PKCE实现
@Component
public class PKCEHandler {
    
    public CodeChallenge generateCodeChallenge(String codeVerifier) {
        // SHA256哈希
        String challenge = Base64.getUrlEncoder().withoutPadding()
            .digestToString(MessageDigest.getInstance("SHA-256")
                .digest(codeVerifier.getBytes()));
        
        return CodeChallenge.builder()
            .verifier(codeVerifier)
            .challenge(challenge)
            .method("S256")
            .build();
    }
    
    public boolean verifyCodeExchange(String codeVerifier, String codeChallenge, 
                                       String codeChallengeMethod) {
        if ("S256".equals(codeChallengeMethod)) {
            String computed = generateCodeChallenge(codeVerifier).getChallenge();
            return MessageDigest.isEqual(computed.getBytes(), codeChallenge.getBytes());
        } else if ("plain".equals(codeChallengeMethod)) {
            return codeVerifier.equals(codeChallenge);
        }
        return false;
    }
}

令牌交换与刷新

// 令牌端点
@RestController
@RequestMapping("/oauth2")
public class TokenEndpoint {
    
    @PostMapping("/token")
    public ResponseEntity<TokenResponse> token(
            @RequestParam("grant_type") String grantType,
            @RequestParam(value = "code", required = false) String code,
            @RequestParam(value = "redirect_uri", required = false) String redirectUri,
            @RequestParam(value = "client_id", required = false) String clientId,
            @RequestParam(value = "client_secret", required = false) String clientSecret,
            @RequestParam(value = "refresh_token", required = false) String refreshToken,
            @RequestParam(value = "code_verifier", required = false) String codeVerifier) {
        
        if ("authorization_code".equals(grantType)) {
            // 验证授权码
            AuthorizationCode authCode = codeService.validate(code, clientId, redirectUri);
            
            // PKCE验证
            if (authCode.getCodeChallenge() != null) {
                if (!pkceHandler.verifyCodeExchange(
                    codeVerifier, 
                    authCode.getCodeChallenge(), 
                    authCode.getCodeChallengeMethod())) {
                    throw new InvalidGrantException("PKCE验证失败");
                }
            }
            
            // 生成令牌
            TokenSet tokens = tokenService.generateTokens(authCode);
            return ResponseEntity.ok(TokenResponse.fromTokenSet(tokens));
            
        } else if ("refresh_token".equals(grantType)) {
            // 刷新令牌
            TokenSet newTokens = tokenService.refresh(refreshToken, clientId);
            return ResponseEntity.ok(TokenResponse.fromTokenSet(newTokens));
        }
        
        throw new UnsupportedGrantTypeException("不支持的授权类型");
    }
}

安全最佳实践

# OAuth2安全配置
oauth2:
  security:
    # 强制使用PKCE
    require_pkce: true
    allowed_challenge_methods:
      - S256
    
    # 令牌配置
    access_token:
      lifetime: 3600
      algorithm: RS256
      issuer: "https://auth.example.com"
    
    refresh_token:
      lifetime: 2592000
      rotation: true
    
    # 客户端认证
    client_authentication:
      methods:
        - client_secret_basic
        - client_secret_post
        - private_key_jwt
    
    # 作用域限制
    scopes:
      allowed:
        - openid
        - profile
        - email
      default: openid

OAuth2授权码流程配合PKCE提供了安全可靠的授权机制,是现代应用授权的标准解决方案。