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

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的安全上下文,让授权服务器识别到用户已完成认证:

  1. 修改登录端点代码,在完成用户认证后,创建已认证的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
}
  1. 关键说明:
    • 不要直接拼接redirect_uri和code,应该重定向回Spring Authorization Server的授权请求地址(即savedRequest中的redirectUrl),由授权服务器自动完成code生成、存储,并最终重定向到客户端的redirect_uri并携带code和state参数。
    • 确保authService.loadUserByUsername方法能返回合法的UserDetails实例,包含用户的权限信息,这样才能生成有效的已认证Authentication对象。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 05:27:25