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); }); }); }
关键说明
- 令牌来源一致性:自定义处理器直接提取授权服务器在授权码交换后返回的原生JWT,没有自行生成令牌,因此不会与password模式获取的令牌产生冲突。
- 资源服务验证兼容:保留原有的
jwk-set-uri配置,资源服务仍通过授权服务器的JWKS验证令牌合法性,确保两种模式的令牌都能正常通过校验。 - 前端使用方式:前端拿到令牌后,可将其存储在
localStorage或sessionStorage中,后续请求资源时在Authorization请求头中携带Bearer {token}即可。
内容的提问来源于stack exchange,提问作者ToQuery
相关产品推荐
相关产品推荐

