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

Spring Security OAuth2:如何让登录+资源服务应用使用授权服务器JWT

解决方案:获取授权服务器原生JWT并兼容多模式

核心思路

要实现使用authorization-server生成的JWT(支持authorization_code和password模式),关键是在authorization_code登录流程完成后,直接提取授权服务器返回的令牌返回给前端,而非自行生成JWT;同时保留资源服务对授权服务器令牌的验证逻辑,确保password模式获取的令牌能正常使用。

步骤1:完善客户端配置

在jwt-app的application.yml中补充OAuth2客户端注册信息,确保能正常与授权服务器完成授权码交换:

spring:
  security:
    oauth2:
      client:
        registration:
          toquery: # 与登录页配置的授权客户端ID一致
            client-id: your-client-id # 授权服务器上注册的客户端ID
            client-secret: your-client-secret # 授权服务器上注册的客户端密钥
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/login/oauth2/code/toquery" # 授权服务器配置的回调地址
            scope: openid, profile, api # 根据实际业务需求配置权限范围
        provider:
          toquery:
            issuer-uri: ${app.oauth2.domain} # 授权服务器的Issuer地址,Spring会自动发现JWKS等配置
      resourceserver:
        jwt:
          jwk-set-uri: ${app.oauth2.domain}/oauth2/jwks # 保留原资源服务验证配置

步骤2:自定义登录成功处理器

替换原有的appOAuth2AuthenticationSuccessHandler,在登录成功时直接提取授权服务器返回的令牌并返回给前端:

@Component
public class TokenReturningSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        if (authentication instanceof OAuth2AuthenticationToken) {
            OAuth2AuthenticationToken oauth2Token = (OAuth2AuthenticationToken) authentication;
            OAuth2User principal = oauth2Token.getPrincipal();

            // 提取授权服务器生成的AccessToken
            OAuth2AccessToken accessToken = principal.getAttributes().get("access_token");
            // OIDC模式下可提取IdToken
            OidcIdToken idToken = principal instanceof OidcUser ? ((OidcUser) principal).getIdToken() : null;

            // 构建响应体返回令牌信息
            Map<String, Object> tokenInfo = new HashMap<>();
            tokenInfo.put("access_token", accessToken.getTokenValue());
            tokenInfo.put("token_type", accessToken.getTokenType().getValue());
            tokenInfo.put("expires_in", accessToken.getExpiresAt().getEpochSecond() - Instant.now().getEpochSecond());
            if (idToken != null) {
                tokenInfo.put("id_token", idToken.getTokenValue());
            }

            // 设置响应为JSON格式
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            new ObjectMapper().writeValue(response.getWriter(), tokenInfo);
        } else {
            // 非OAuth2登录场景的默认处理,可根据需求调整
            response.sendRedirect("/");
        }
    }
}

步骤3:更新Security配置

将自定义处理器注入并替换原有的successHandler:

@Autowired
private TokenReturningSuccessHandler appOAuth2AuthenticationSuccessHandler;

@Autowired
private OAuth2AuthenticationFailureHandler appOAuth2AuthenticationFailureHandler;

@Autowired
private HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository;

@Autowired
private AppOAuth2UserService appOAuth2UserService;

@Autowired
private AppOidcUserService appOidcUserService;

// 资源服务相关依赖注入
@Autowired
private BearerTokenResolver bearerTokenResolver;
@Autowired
private AccessDeniedHandler accessDeniedHandler;
@Autowired
private AuthenticationEntryPoint appAuthenticationEntryPoint;
@Autowired
private JwtAuthenticationConverter jwtAuthenticationConverter;

protected void configure(HttpSecurity http) throws Exception {
    http.oauth2Login(oauth2LoginConfigurer -> {
        oauth2LoginConfigurer.loginPage("/oauth2/authorization/toquery");

        oauth2LoginConfigurer.authorizationEndpoint(authorizationEndpointConfig -> {
            authorizationEndpointConfig.authorizationRequestRepository(httpCookieOAuth2AuthorizationRequestRepository);
        });

        oauth2LoginConfigurer.userInfoEndpoint(userInfoEndpointConfig -> {
            userInfoEndpointConfig.userService(appOAuth2UserService);
            userInfoEndpointConfig.oidcUserService(appOidcUserService);
        });

        oauth2LoginConfigurer.successHandler(appOAuth2AuthenticationSuccessHandler);
        oauth2LoginConfigurer.failureHandler(appOAuth2AuthenticationFailureHandler);
    });

    http.oauth2ResourceServer(auth2ResourceServerConfigurer -> {
        auth2ResourceServerConfigurer.bearerTokenResolver(bearerTokenResolver);
        auth2ResourceServerConfigurer.accessDeniedHandler(accessDeniedHandler);
        auth2ResourceServerConfigurer.authenticationEntryPoint(appAuthenticationEntryPoint);

        auth2ResourceServerConfigurer.jwt(jwtConfigurer -> {
            jwtConfigurer.jwtAuthenticationConverter(jwtAuthenticationConverter);
        });
    });
}

关键说明

  1. 令牌来源一致性:自定义处理器直接提取授权服务器在授权码交换后返回的原生JWT,没有自行生成令牌,因此不会与password模式获取的令牌产生冲突。
  2. 资源服务验证兼容:保留原有的jwk-set-uri配置,资源服务仍通过授权服务器的JWKS验证令牌合法性,确保两种模式的令牌都能正常通过校验。
  3. 前端使用方式:前端拿到令牌后,可将其存储在localStorage或sessionStorage中,后续请求资源时在Authorization请求头中携带Bearer {token}即可。

内容的提问来源于stack exchange,提问作者ToQuery

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 22:45:25