You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让资源服务器验证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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 11:49:52