OAuth2授权码流程中登录后获取code参数并跳转的问题
问题场景
应用自定义了登录端点,Spring Authorization Server会将用户代理重定向至/login。执行以下curl命令后会跳转至登录页面:
curl --location 'http://localhost:8082/oauth2/authorize?response_type=code&client_id=oauth-dev&client_secret=secret&redirect_uri=https%3A%2F%2Fexample.com&state=state123'
当前问题:登录端点可以重定向至redirect_uri(https://example.com),但无法获取OAuth2的code参数,无法构造如下格式的跳转:
"redirect:" + savedRequest.getParameterValues("redirect_uri") + "&code=" + code + "&state" = state;
登录端点代码
@PostMapping("/login") public String login(final HttpServletRequest request, String username) { // 认证服务通过用户名/密码完成用户认证 authService.authenticate(username); SavedRequest savedRequest = request.session.getAttribute("SPRING_SECURITY_SAVED_REQUEST") as SavedRequest; return "redirect:" + savedRequest.getParameterValues("redirect_uri"); // 缺少code参数,如何获取OAuth2的code参数? }
SecurityConfig配置
@Autowired private lateinit var passwordEncoder: PasswordEncoder @Bean @Order(1) fun authorizationServerSecurityFilterChain(http: HttpSecurity): SecurityFilterChain { OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http) http.getConfigurer(OAuth2AuthorizationServerConfigurer::class.java) .oidc(Customizer.withDefaults()) http .exceptionHandling { exceptions -> exceptions.authenticationEntryPoint(LoginUrlAuthenticationEntryPoint ("/login")) } .oauth2ResourceServer { it.jwt() } return http.build() } @Bean fun authorizationServerSettings(): AuthorizationServerSettings { return AuthorizationServerSettings.builder().build() } @Bean fun authorizationService(): OAuth2AuthorizationService { return InMemoryOAuth2AuthorizationService() } @Bean fun registeredClientRepository(): RegisteredClientRepository { val registeredClient = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId("oauth-dev") .clientSecret(passwordEncoder.encode("secret")) .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .redirectUri("https://example.com") .clientSettings(ClientSettings.builder() .requireAuthorizationConsent(false) .requireProofKey(false).build()) .build() return InMemoryRegisteredClientRepository(registeredClient) } @Bean fun jwkSource(): JWKSource<SecurityContext> { val rsaKey = Jwks.generateRsa() val jwkSet = JWKSet(rsaKey) return JWKSource { jwkSelector: JWKSelector, securityContext: SecurityContext? -> jwkSelector.select(jwkSet) } } @Bean fun jwtDecoder(jwkSource: JWKSource<SecurityContext>): JwtDecoder { return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource) }
问题根源分析
查看Spring Authorization Server源码发现,code生成逻辑未被触发的原因是存在如下条件判断:
if (!isPrincipalAuthenticated(principal)) {
此时的principal是匿名认证令牌:AnonymousAuthenticationToken [主体=anonymousUser, 凭证=[受保护], 已认证=true, 详情=null, 授予权限=[ROLE_ANONYMOUS]],即用户登录后并未在Spring Security的上下文中标注为已认证状态,导致授权服务器认为用户仍未认证,跳过code生成步骤。
解决方案
核心是在自定义登录端点中,将认证后的用户信息存入Spring Security的安全上下文,让授权服务器识别到用户已完成认证:
- 修改登录端点代码,在完成用户认证后,创建已认证的Authentication对象并放入SecurityContext:
@PostMapping("/login") fun login(request: HttpServletRequest, username: String): String { // 完成用户认证,获取用户权限等信息 val userDetails = authService.loadUserByUsername(username) // 需确保authService能返回UserDetails实例 val authentication = UsernamePasswordAuthenticationToken( userDetails, null, // 密码已验证,此处可传null userDetails.authorities ) // 将认证信息存入SecurityContext SecurityContextHolder.getContext().authentication = authentication // 保存到session,确保后续请求能获取到 request.session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext()) val savedRequest = request.session.getAttribute("SPRING_SECURITY_SAVED_REQUEST") as SavedRequest // 重定向回原授权请求地址,由授权服务器完成code生成和跳转 return "redirect:" + savedRequest.redirectUrl }
- 关键说明:
- 不要直接拼接redirect_uri和code,应该重定向回Spring Authorization Server的授权请求地址(即savedRequest中的redirectUrl),由授权服务器自动完成code生成、存储,并最终重定向到客户端的redirect_uri并携带code和state参数。
- 确保
authService.loadUserByUsername方法能返回合法的UserDetails实例,包含用户的权限信息,这样才能生成有效的已认证Authentication对象。
内容的提问来源于stack exchange,提问作者Holm
相关产品推荐
相关产品推荐

