OAuth 2.1 规范解读与实现指南

David Ng | 2026-09-01T08:57:51 | Spring Boot, Security

解读 OAuth 2.1 相比 2.0 的变化,废弃隐式授权和密码模式,强制 PKCE,并用 Spring Authorization Server 实现完整流程。

# OAuth 2.1 规范解读与实现指南 ## OAuth 2.1 vs 2.0 关键变化 | 变化 | OAuth 2.0 | OAuth 2.1 | |------|----------|----------| | 隐式授权 | 支持 | 已废弃 | | 密码模式 | 支持 | 已废弃 | | PKCE | 可选 | 公开客户端强制 | | Refresh Token | 无限制 | 必须 Sender-Constrained 或一次性使用 | | Bearer Token | 可在 URL 中传递 | 禁止 URL 传递 | ## PKCE 授权码流程 ``` 1. 客户端生成 code_verifier (43-128字符随机串) 2. 计算 code_challenge = BASE64URL(SHA256(code_verifier)) 3. 重定向到授权端点,携带 code_challenge 4. 用户授权后获得 authorization_code 5. 用 code + code_verifier 换取 token 6. 服务端验证 SHA256(code_verifier) == code_challenge ``` ## Spring Authorization Server 实现 ```java @Configuration public class AuthorizationServerConfig { @Bean public RegisteredClientRepository registeredClientRepository() { RegisteredClient webClient = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId("web-app") .clientAuthenticationMethod(ClientAuthenticationMethod.NONE) // 公开客户端 .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .redirectUri("https://app.example.com/callback") .scope("openid") .scope("profile") .scope("read") .clientSettings(ClientSettings.builder() .requireProofKey(true) // 强制 PKCE .requireAuthorizationConsent(true) .build()) .tokenSettings(TokenSettings.builder() .accessTokenTimeToLive(Duration.ofMinutes(15)) .refreshTokenTimeToLive(Duration.ofHours(8)) .reuseRefreshTokens(false) // 一次性 refresh token .build()) .build(); return new InMemoryRegisteredClientRepository(webClient); } @Bean public JWKSource jwkSource() throws Exception { RSAKey rsaKey = generateRsaKey(); JWKSet jwkSet = new JWKSet(rsaKey); return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); } } ``` ## 前端 PKCE 实现 ```javascript async function generatePKCE() { const verifier = generateRandomString(64) const encoder = new TextEncoder() const data = encoder.encode(verifier) const digest = await crypto.subtle.digest('SHA-256', data) const challenge = base64UrlEncode(digest) return { verifier, challenge } } async function startLogin() { const { verifier, challenge } = await generatePKCE() sessionStorage.setItem('code_verifier', verifier) const params = new URLSearchParams({ response_type: 'code', client_id: 'web-app', redirect_uri: 'https://app.example.com/callback', scope: 'openid profile read', code_challenge: challenge, code_challenge_method: 'S256' }) window.location.href = '/oauth2/authorize?' + params.toString() } ``` OAuth 2.1 强制使用 PKCE 有效防御了授权码拦截攻击,是目前推荐的最佳实践。

← Back to Blog