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

Spring Boot3+Security6 OAuth2授权码模式无法兑换令牌问题

问题

使用Spring Boot 3.3.3 + Spring Security 6.4.2对接第三方OAuth2服务(非Google/Github等),目标是获取用户授权以拿到开发者数据,流程如下:

  1. 用户被重定向至第三方登录页;
  2. 用户完成登录;
  3. 用户收到授权请求;
  4. 我方成功获取第三方的授权码;
  5. 需要用授权码兑换access token和refresh token(后续无需用户保持登录)。

当前问题:Spring在第4步后无法自动向令牌端点兑换令牌,但用Postman可以成功兑换。日志显示授权码已到达/oauth2/authorization/cool-client,但未触发令牌兑换,调试Controller也未被命中。

相关配置与代码

application.yml配置

spring:
  application:
    name: glorious-name
  security:
    oauth2:
      client:
        registration:
          cool-client:
            provider:                     server
            client-id:                    cool-client-s-app-id
            client-secret:                cool-client-s-app-secret
            authorization-grant-type:     authorization_code
            redirect-uri:                 http://localhost:8080/oauth2/authorization/cool-client
            client-authentication-method: client_credentials
            scope:                        scopes_that_matter
            state:                        true
        provider:
          server:
            authorization-uri:            https://server.com/oauth/oauth2/auth
            token-uri:                    https://server.com/oauth/oauth2/token

执行日志

417+02:00 DEBUG 232660 --- [glorious-name] [nio-8581-exec-1] [hash1] o.s.s.web.DefaultRedirectStrategy        : Redirecting to http://localhost:8080/oauth2/authorization/cool-client
424+02:00 DEBUG 232660 --- [glorious-name] [nio-8581-exec-2] [hash2] o.s.security.web.FilterChainProxy        : Securing GET /oauth2/authorization/cool-client
431+02:00 DEBUG 232660 --- [glorious-name] [nio-8581-exec-2] [hash2] o.s.s.web.DefaultRedirectStrategy        : Redirecting to https://server.com/oauth/oauth2/auth?response_type=code&client_id=cool-client-id&scope=encoded_scopes&state=...D&redirect_uri=http://localhost:8080/oauth2/authorization/cool-client
540+02:00 DEBUG 232660 --- [glorious-name] [nio-8581-exec-3] [hash3] o.s.security.web.FilterChainProxy        : Securing GET /oauth2/authorization/cool-client?code=...&scope=encoded_scopes&state=...
544+02:00 DEBUG 232660 --- [glorious-name] [nio-8581-exec-3] [hash3] o.s.s.web.DefaultRedirectStrategy        : Redirecting to https://server.com/oauth/oauth2/auth?response_type=code&client_id=cool_client_id&scope=encoded_scopes&state=...&redirect_uri=http://localhost:8080/oauth2/authorization/cool-client

调试Controller(尝试过多种写法)

@GetMapping("/oauth2/authorization/cool-client")
// public ResponseEntity<String> exchangeCodeForToken(@RequestParam(name = "code") String code, @RequestParam(name = "state") String state, @RequestParam(name = "scope") String scope) {
// public ResponseEntity<String> exchangeCodeForToken(@RegisteredOAuth2AuthorizedClient("cool-client") OAuth2AuthorizedClient authorizedClient) {
public ResponseEntity<String> exchangeCodeForToken(@AuthenticationPrincipal OAuth2User user) {

    // log.info("!!!!! SUCCESS !!!!!" + authorizedClient.getClientRegistration().getRegistrationId());
    log.info("USER " + user.getAttributes());
    // log.info("!!!!! THE CODE!!!!! from authorization/lh"  + code);

    return ResponseEntity.ok("Successfully reading the auth code");
}

SecurityConfig.java

@Slf4j
@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
            .authorizeHttpRequests(authorize -> authorize
                    .requestMatchers("/", "/error", "/login/**", "/oauth2/**").permitAll()
                    .anyRequest().authenticated()
            )
            .oauth2Login(oauth -> oauth
                    .failureUrl("/oauth2/error")
            )
            .oauth2Client(Customizer.withDefaults());

    return http.build();
}

用户疑问:需要修改哪些配置/代码实现正确流程?是否必须自定义令牌兑换逻辑?还有哪些调试技巧?


解决方案

核心配置修正

1. 修正redirect-uri配置

/oauth2/authorization/cool-client是Spring Security用于发起授权请求的端点,不是接收授权码的回调地址。回调地址应该用Spring OAuth2 Login默认的回调端点{baseUrl}/login/oauth2/code/{registrationId},同时要在第三方OAuth2服务后台配置完全一致的回调地址。

修改application.yml中的redirect-uri:

redirect-uri: "{baseUrl}/login/oauth2/code/cool-client"

2. 修正client-authentication-method

授权码模式下,客户端认证方式应该用client_secret_basic或client_secret_post,client_credentials是客户端凭证模式的认证方式,不适合当前场景。根据第三方服务要求调整,比如:

client-authentication-method: client_secret_post

3. 补充provider用户信息配置(可选)

如果需要获取用户信息,补充第三方的用户信息端点配置:

provider:
  server:
    authorization-uri: https://server.com/oauth/oauth2/auth
    token-uri: https://server.com/oauth/oauth2/token
    user-info-uri: https://server.com/oauth/oauth2/userinfo
    user-name-attribute: id # 根据第三方返回的用户唯一标识字段调整

代码调整

1. 监听登录成功事件获取令牌

Spring Security会自动完成授权码兑换令牌的流程,无需手动编写兑换逻辑。可以通过自定义登录成功处理器来获取令牌:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http, OAuth2AuthorizedClientService authorizedClientService) throws Exception {
    http
            .authorizeHttpRequests(authorize -> authorize
                    .requestMatchers("/", "/error", "/login/**", "/oauth2/**").permitAll()
                    .anyRequest().authenticated()
            )
            .oauth2Login(oauth -> oauth
                    .failureUrl("/oauth2/error")
                    .successHandler((request, response, authentication) -> {
                        // 从认证信息中提取授权客户端
                        OAuth2AuthenticationToken authToken = (OAuth2AuthenticationToken) authentication;
                        OAuth2AuthorizedClient client = authorizedClientService.loadAuthorizedClient(
                                authToken.getAuthorizedClientRegistrationId(),
                                authToken.getName()
                        );
                        // 获取accessToken和refreshToken
                        String accessToken = client.getAccessToken().getTokenValue();
                        String refreshToken = client.getRefreshToken() != null ? client.getRefreshToken().getTokenValue() : null;
                        log.info("Access Token: {}", accessToken);
                        log.info("Refresh Token: {}", refreshToken);
                        // 重定向到业务页面
                        response.sendRedirect("/success");
                    })
            )
            .oauth2Client(Customizer.withDefaults());

    return http.build();
}

2. 移除无效的调试Controller

原Controller映射的是授权请求端点,不会被授权码回调触发,直接删除或修改映射路径为自定义业务端点即可。

是否需要自定义令牌兑换逻辑?

不需要。只要配置符合OAuth2标准规范,Spring Security OAuth2 Login会自动完成授权码兑换令牌的流程。只有当第三方服务的OAuth2实现不符合RFC规范时,才需要自定义OAuth2AccessTokenResponseClient来适配。

额外调试技巧

  • 开启OAuth2相关TRACE日志,重点关注OAuth2AuthorizationCodeAuthenticationProvider和DefaultAuthorizationCodeTokenResponseClient的日志,查看令牌请求的参数、响应及错误信息:
logging:
  level:
    org.springframework.security.oauth2: TRACE
  • 抓包对比Postman和Spring发出的令牌请求,检查请求头、参数(如grant_type、code、redirect_uri)是否完全一致;
  • 验证第三方返回的state参数是否和Spring发送的一致,Spring会验证state防止CSRF,不一致会触发重定向回授权页(这就是日志中最后一步重定向的原因);
  • 确认第三方服务后台配置的回调地址和Spring配置的redirect-uri完全一致,包括协议(http/https)、端口、路径。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 09:57:09