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

Spring Authorization Server令牌端点404问题排查与配置咨询

问题描述

我使用org.springframework.security:spring-security-oauth2-authorization-server:1.2.2搭建简易授权服务器,参照官方文档配置后基础认证已生效,但调用自定义令牌端点http://localhost:8000/oauth2/v1/token(grant_type=client_credentials)时返回404错误,无法定位原因。以下是我的配置代码与调试日志,请求告知令牌URL的正确配置方式。

配置代码

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {


    @Bean
    public SecurityFilterChain securityFilterChain( HttpSecurity http ) throws Exception {
        http
                .authorizeHttpRequests( ( authorize ) -> authorize
                        .requestMatchers( "/oauth2/**" ).authenticated()
                        .anyRequest().permitAll()
                )
                .httpBasic( Customizer.withDefaults() )
                .formLogin( Customizer.withDefaults() )
                .cors( httpSecurityCorsConfigurer -> httpSecurityCorsConfigurer.disable())
                .csrf( httpSecurityCsrfConfigurer -> httpSecurityCsrfConfigurer.disable());


        return http.build();
    }

    @Bean
    public AuthorizationServerSettings authorizationServerSettings(  ){

        return AuthorizationServerSettings.builder()
                .issuer( "http://localhost:8000" )
                .tokenEndpoint( "/oauth2/v1/token" )
                .tokenIntrospectionEndpoint( "/oauth2/v1/verify" )
                .build();
    }

    @Bean
    public OAuth2AuthorizationService authorizationService(){
        return new InMemoryOAuth2AuthorizationService();
    }

    @Bean
    public RegisteredClientRepository registeredClientRepository(){

        RegisteredClient client = RegisteredClient.withId( UUID.randomUUID().toString() )
                .clientId( "user" )
                .clientSecret( "{noop}user" )
                .clientAuthenticationMethod( ClientAuthenticationMethod.CLIENT_SECRET_BASIC )
                .authorizationGrantType( AuthorizationGrantType.AUTHORIZATION_CODE )
                .authorizationGrantType( AuthorizationGrantType.CLIENT_CREDENTIALS )
                .authorizationGrantType( AuthorizationGrantType.REFRESH_TOKEN )
                .redirectUri( "http://localhost:8000/redirect" )
                .tokenSettings( TokenSettings.builder().accessTokenTimeToLive( Duration.ofDays( 1 ) ).build() )
                .clientSettings( ClientSettings.builder()
                        .build() )
                .build();


        return new InMemoryRegisteredClientRepository( client );
    }

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

        OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
                new OAuth2AuthorizationServerConfigurer();

        http.apply( authorizationServerConfigurer );

        authorizationServerConfigurer
                .tokenEndpoint( Customizer.withDefaults() )
                .tokenGenerator( new OAuth2AccessTokenGenerator() )
                .authorizationServerSettings( authorizationServerSettings( ) )
                .registeredClientRepository( registeredClientRepository() )
                .authorizationService( authorizationService() );
//              .setBuilder( http );


        return http.build();
    }

}

调试日志

2024-02-27T16:07:12.942+09:00 DEBUG 84973 --- [nio-8000-exec-8] o.s.security.web.FilterChainProxy        : Securing POST /oauth2/v1/token
2024-02-27T16:07:13.147+09:00 DEBUG 84973 --- [nio-8000-exec-8] o.s.s.a.dao.DaoAuthenticationProvider    : Authenticated user
2024-02-27T16:07:13.148+09:00 DEBUG 84973 --- [nio-8000-exec-8] o.s.s.w.a.www.BasicAuthenticationFilter  : Set SecurityContextHolder to UsernamePasswordAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=user, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, CredentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[]]
2024-02-27T16:07:13.150+09:00 DEBUG 84973 --- [nio-8000-exec-8] o.s.security.web.FilterChainProxy        : Secured POST /oauth2/v1/token
2024-02-27T16:07:13.161+09:00 DEBUG 84973 --- [nio-8000-exec-8] o.s.security.web.FilterChainProxy        : Securing POST /error
2024-02-27T16:07:13.161+09:00 DEBUG 84973 --- [nio-8000-exec-8] o.s.security.web.FilterChainProxy        : Secured POST /error

问题分析与解决方案

核心问题是两个SecurityFilterChain Bean的优先级和配置冲突,导致授权服务器的端点逻辑未被正确触发:

  1. 第一个securityFilterChain拦截了/oauth2/**请求,完成认证后没有将请求转发到授权服务器的处理逻辑
  2. 授权服务器的authorizationServerSecurityFilterChain未配置请求匹配规则,Spring无法识别哪些请求应该由它处理

修改后的完整配置代码

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    // 普通Web安全过滤器链,优先级低于授权服务器链
    @Bean
    @Order(2)
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(authorize -> authorize
                        .anyRequest().permitAll()
                )
                .httpBasic(Customizer.withDefaults())
                .formLogin(Customizer.withDefaults())
                .cors(cors -> cors.disable())
                .csrf(csrf -> csrf.disable());

        return http.build();
    }

    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder()
                .issuer("http://localhost:8000")
                .tokenEndpoint("/oauth2/v1/token")
                .tokenIntrospectionEndpoint("/oauth2/v1/verify")
                .build();
    }

    @Bean
    public OAuth2AuthorizationService authorizationService() {
        return new InMemoryOAuth2AuthorizationService();
    }

    @Bean
    public RegisteredClientRepository registeredClientRepository() {
        RegisteredClient client = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("user")
                .clientSecret("{noop}user")
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .redirectUri("http://localhost:8000/redirect")
                .tokenSettings(TokenSettings.builder().accessTokenTimeToLive(Duration.ofDays(1)).build())
                .clientSettings(ClientSettings.builder().build())
                .build();

        return new InMemoryRegisteredClientRepository(client);
    }

    // 授权服务器过滤器链,设置最高优先级
    @Bean
    @Order(1)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
                new OAuth2AuthorizationServerConfigurer();

        http
                .securityMatcher("/oauth2/**") // 指定该链处理授权相关请求
                .apply(authorizationServerConfigurer);

        authorizationServerConfigurer
                .tokenEndpoint(Customizer.withDefaults())
                .tokenGenerator(new OAuth2AccessTokenGenerator())
                .authorizationServerSettings(authorizationServerSettings())
                .registeredClientRepository(registeredClientRepository())
                .authorizationService(authorizationService());

        // 授权服务器端点的安全配置
        http
                .cors(cors -> cors.disable())
                .csrf(csrf -> csrf.disable())
                .authorizeHttpRequests(authorize -> authorize
                        .anyRequest().authenticated()
                );

        return http.build();
    }
}

关键修改点说明

  1. 过滤器链优先级:给两个SecurityFilterChain添加@Order注解,让授权服务器的链(@Order(1))先执行,普通链(@Order(2))处理剩余请求
  2. 请求匹配规则:在授权服务器的过滤器链中添加.securityMatcher("/oauth2/**"),明确该链负责所有授权端点的请求
  3. 规则去重:移除普通链中对/oauth2/**的拦截,避免重复处理导致逻辑冲突
  4. 补充安全配置:给授权服务器链补充必要的cors、csrf禁用规则,确保端点正常接收请求

验证方式

修改后重启服务,调用POST http://localhost:8000/oauth2/v1/token,携带参数grant_type=client_credentials和Basic认证信息(用户名user,密码user),即可正常获取access_token。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 08:41:01