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

Spring Authorization Server请求Token时重定向至登录页问题排查

OAuth2认证流程中/oauth2/token接口返回302重定向问题排查

问题背景

我们有两个原基于CAS认证的Web应用,应用间原通过Basic Auth通信。出于安全考量,计划切换到OAuth2替代Basic Auth,同时弃用不符合需求的CAS。目前已实现:用户可登录管理系统并操作数据,应用2能正常跳转至应用1完成认证,应用1也能正确响应应用2,但应用2调用/oauth2/token接口时,应用1返回302重定向至/login而非Token。


相关配置信息

应用1(授权服务器)AuthorizationServerConfig配置

@Configuration
public class AuthorizationServerConfig {

  private final SecurityApp app;

  public AuthorizationServerConfig(SecurityApp app) {
    this.app = app;
  }

  @Bean
  @Order(1)
  public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
      throws Exception {

    OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
        new OAuth2AuthorizationServerConfigurer();
    authorizationServerConfigurer
        .authorizationEndpoint(authorizationEndpoint ->
            authorizationEndpoint.consentPage("/oauth2/authorize"))
        .oidc(Customizer.withDefaults());   // Enable OpenID Connect 1.0

    RequestMatcher endpointsMatcher = authorizationServerConfigurer
        .getEndpointsMatcher();

    http
        .securityMatcher(endpointsMatcher)
        .authorizeHttpRequests(authorize ->
            authorize.anyRequest().authenticated()
        )
        .csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
        .exceptionHandling(exceptions ->
            exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
        )
        .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
        .apply(authorizationServerConfigurer);
    return http.build();
  }

  @Bean
  @Order(2)
  public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
      throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .anyRequest().authenticated()
        )
        // Form login handles the redirect to the login page from the
        // authorization server filter chain
        .formLogin(Customizer.withDefaults());

    return http.build();
  }

  @Bean
  UserDetailsService users() {
    return app::findUserByLogin;
  }

  // OVERWATCH

  @Bean
  public RegisteredClientRepository registeredClientRepository() {

    return new RegisteredClientRepository() {
      @Override
      public void save(RegisteredClient registeredClient) {
        throw new NotImplementedException();
      }

      @Override
      public RegisteredClient findById(String id) {
        return app.findByClientId(id);
      }

      @Override
      public RegisteredClient findByClientId(String clientId) {
        return app.findByClientId(clientId);
      }
    };
  }

  @Bean
  public JWKSource<SecurityContext> jwkSource() {
    RSAPublicKey publicKey = app.getPublicKey();
    RSAPrivateKey privateKey = (RSAPrivateKey) app.getPrivateKey();
    RSAKey rsaKey = new RSAKey.Builder(publicKey)
        .privateKey(privateKey)
        .keyID(UUID.randomUUID().toString())
        .build();
    JWKSet jwkSet = new JWKSet(rsaKey);
    return new ImmutableJWKSet<>(jwkSet);
  }

  @Bean
  public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
    return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
  }

  @Bean
  public AuthorizationServerSettings authorizationServerSettings() {
    return AuthorizationServerSettings.builder().build();
  }


  // region Password Authenticator

  @Bean
  public PasswordEncoder passwordEncoder() {
    return app.passwordEncoder();
  }

  // endregion
}

应用2安全配置

@Configuration
@EnableWebSecurity
public class AceSecurityConfiguration extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
        .antMatcher("/**")
        .authorizeRequests()
        .antMatchers("/oauth/authorize**", "/login**", "/error**")
        .permitAll()
        .and()
        .authorizeRequests()
        .anyRequest().authenticated()
        .and()
        .oauth2Login( oauth2Login -> oauth2Login.defaultSuccessUrl("/index.html") );
  }
}

应用2application.properties配置

spring.security.oauth2.client.registration.<provider-name>.client-id=${app.uuid}
spring.security.oauth2.client.registration.<provider-name>.client-secret=${app.secret}
spring.security.oauth2.client.registration.<provider-name>.scope=openid
spring.security.oauth2.client.registration.<provider-name>.redirect-uri=http://127.0.0.1:8088/login/oauth2/code/<provider-name>
spring.security.oauth2.client.registration.<provider-name>.client-name=${app.name}
spring.security.oauth2.client.registration.<provider-name>.provider=${provider.name}
spring.security.oauth2.client.registration.<provider-name>.client-authentication-method=code
spring.security.oauth2.client.registration.<provider-name>.authorization-grant-type=authorization_code

spring.security.oauth2.client.provider.<provider-name>.authorization-uri=http://localhost:8086/oauth2/authorize
spring.security.oauth2.client.provider.<provider-name>.token-uri=http://localhost:8086/oauth2/token
spring.security.oauth2.client.provider.<provider-name>.user-info-uri=http://localhost:8086/oauth2/userinfo?schema=openid
spring.security.oauth2.client.provider.<provider-name>.user-name-attribute=name
spring.security.oauth2.client.provider.<provider-name>.user-info-authentication-method=header
spring.security.oauth2.client.provider.<provider-name>.jwk-set-uri=http://localhost:8086/jwks

触发重定向的请求

POST http://localhost:8086/oauth2/token

Body:
grant_type=authorization_code, 
code=Zls0ppjnS_RXyMVPB8fg_eQQgoiUAxRguOMsdyVYQpgd8eDkUDzgz813L0ybovTL7sNj0TDRUHibPfek9NzwULND1mty5WPW2DOtQjTAaEROL3qP7RvyTWXTEzzYe-o,
redirect_uri=http://127.0.0.1:8088/login/oauth2/code/<provider-name>,
client_id=<app.uuid>

Header:
Accept:"application/json;charset=UTF-8", 
Content-Type:"application/x-www-form-urlencoded;charset=UTF-8"

遗漏配置与修复方案

1. 客户端认证方式配置错误

应用2的application.properties中,client-authentication-method设置为code完全错误。授权码模式下,客户端调用/oauth2/token接口时,应使用client_secret_basic或client_secret_post的认证方式。

修改配置:

spring.security.oauth2.client.registration.<provider-name>.client-authentication-method=client_secret_basic

2. 客户端请求缺失凭证

当前触发重定向的请求仅传递了client_id,未携带client_secret,也未通过Basic Auth头传递客户端凭证。需补充:

  • 方式一:在请求头添加Authorization: Basic <base64编码的client_id:client_secret>
  • 方式二:若使用client_secret_post认证方式,在请求体中添加client_secret=<app.secret>

3. 授权服务器客户端配置校验

检查应用1中app.findByClientId(clientId)返回的RegisteredClient实例,需确保:

  • clientAuthenticationMethod()设置为ClientAuthenticationMethod.CLIENT_SECRET_BASIC(或对应配置的认证方式)
  • authorizationGrantTypes()包含AuthorizationGrantType.AUTHORIZATION_CODE
  • clientSecret与应用2配置的<app.secret>一致,且已通过正确的PasswordEncoder编码

4. 授权服务器安全链异常处理优化

当前授权服务器对所有端点使用LoginUrlAuthenticationEntryPoint,会将未认证请求重定向到登录页,但/oauth2/token属于客户端认证端点,应返回401而非重定向。修改安全链的异常处理逻辑:

.exceptionHandling(exceptions ->
    exceptions
        .defaultAuthenticationEntryPointFor(
            new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
            new AntPathRequestMatcher("/oauth2/token")
        )
        .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 17:55:42