如何让资源服务器验证Spring及Google授权服务器的Token?
解决方案:统一令牌颁发,让资源服务器只验证自有JWT
核心思路:禁止客户端直接使用Google/Facebook的第三方令牌访问资源服务器。用户通过第三方账号登录Auth-server后,Auth-server为其颁发本系统的JWT令牌(包含自定义角色),客户端后续仅使用该JWT访问资源服务器。这样资源服务器无需适配多种第三方令牌,只需保持原有JWT验证逻辑即可。
步骤1:完善第三方登录后的令牌颁发逻辑
在SocialLoginAuthenticationSuccessHandler中,完成用户信息入库后,直接调用Auth-server的令牌生成逻辑,为当前已认证的第三方用户颁发JWT令牌并返回给客户端。
修改SocialLoginAuthenticationSuccessHandler的实现:
public class SocialLoginAuthenticationSuccessHandler implements AuthenticationSuccessHandler { private final AuthenticationSuccessHandler delegate = new SavedRequestAwareAuthenticationSuccessHandler(); private final RegisteredClientRepository registeredClientRepository; private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator; private final ObjectMapper objectMapper = new ObjectMapper(); private Consumer<OAuth2User> oauth2UserHandler = (user) -> {}; @Setter private Consumer<OidcUser> oidcUserHandler = (user) -> this.oauth2UserHandler.accept(user); // 注入所需依赖 public SocialLoginAuthenticationSuccessHandler(RegisteredClientRepository registeredClientRepository, OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) { this.registeredClientRepository = registeredClientRepository; this.tokenGenerator = tokenGenerator; } @Override public void onAuthenticationSuccess( HttpServletRequest request, HttpServletResponse response, Authentication authentication ) throws IOException, ServletException { if (authentication instanceof OAuth2AuthenticationToken) { // 执行用户信息入库逻辑 if (authentication.getPrincipal() instanceof OidcUser) { this.oidcUserHandler.accept((OidcUser) authentication.getPrincipal()); } else if (authentication.getPrincipal() != null) { this.oauth2UserHandler.accept((OAuth2User) authentication.getPrincipal()); } // 为本系统用户颁发JWT令牌 issueLocalToken(response, authentication); return; } this.delegate.onAuthenticationSuccess(request, response, authentication); } private void issueLocalToken(HttpServletResponse response, Authentication authentication) throws IOException { RegisteredClient registeredClient = registeredClientRepository.findByClientId("demo-client"); if (registeredClient == null) { throw new IllegalStateException("客户端未找到"); } // 构造令牌生成上下文 OAuth2TokenContext tokenContext = OAuth2TokenContext.builder() .registeredClient(registeredClient) .principal(authentication) .authorizationGrantType(AuthorizationGrantType.PASSWORD) .authorizationGrant(new OAuth2PasswordAuthenticationToken( authentication.getName(), null, authentication.getAuthorities() )) .build(); // 生成JWT令牌 OAuth2Token generatedToken = tokenGenerator.generate(tokenContext); if (generatedToken instanceof Jwt jwt) { // 构造返回的令牌响应 Map<String, Object> tokenResponse = new HashMap<>(); tokenResponse.put("access_token", jwt.getTokenValue()); tokenResponse.put("token_type", "Bearer"); tokenResponse.put("expires_in", jwt.getExpiresAt().getEpochSecond() - System.currentTimeMillis() / 1000); // 返回JSON格式的令牌 response.setContentType(MediaType.APPLICATION_JSON_VALUE); objectMapper.writeValue(response.getWriter(), tokenResponse); } else { throw new IllegalStateException("无法生成JWT令牌"); } } public void setOAuth2UserHandler(Consumer<OAuth2User> oauth2UserHandler) { this.oauth2UserHandler = oauth2UserHandler; } }
步骤2:调整RegisteredClient配置
确保客户端支持PASSWORD授权类型(用于第三方登录后的令牌颁发),并添加必要的Scope:
@Bean public RegisteredClientRepository registeredClientRepository() { RegisteredClient demoClient = RegisteredClient.withId(UUID.randomUUID().toString()) .clientName("Demo client") .clientId("demo-client") .clientSecret(passwordEncoder().encode("secret")) .redirectUri("http://localhost:8080/auth") .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .authorizationGrantType(AuthorizationGrantType.PASSWORD) // 添加PASSWORD授权类型 .scope(OidcScopes.OPENID) .scope(OidcScopes.PROFILE) .clientSettings(ClientSettings.builder() .requireAuthorizationConsent(false) .build() ) .build(); return new InMemoryRegisteredClientRepository(demoClient); }
步骤3:资源服务器保持原有配置
无需修改资源服务器的JWT验证逻辑,因为客户端现在只会携带Auth-server颁发的JWT令牌,资源服务器通过JWKS端点即可完成签名和角色验证,完全兼容原有@Preauthorize(hasRole...)注解逻辑。
不推荐的替代方案:资源服务器适配多令牌验证
如果必须让资源服务器同时处理自有JWT和第三方不透明令牌,可通过链式解码器实现,但该方案复杂度高、性能差,仅作参考:
@Bean public JwtDecoder jwtDecoder() { // 自有JWT解码器 JwtDecoder localJwtDecoder = NimbusJwtDecoder.withJwkSetUri("http://localhost:8080/oauth2/jwks").build(); return token -> { try { // 优先验证自有JWT return localJwtDecoder.decode(token); } catch (JwtException e) { // 自有JWT验证失败,尝试验证Google不透明令牌 // 调用Google令牌验证端点:https://oauth2.googleapis.com/tokeninfo?access_token=xxx // 验证通过后构造自定义JWT对象返回 throw new UnsupportedOperationException("需自行实现第三方令牌验证逻辑"); } }; }
内容的提问来源于stack exchange,提问作者Mohamde amine Karek
相关产品推荐
相关产品推荐

