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

Spring Authorization Server /auth/oauth2/authorize端点404问题排查

Spring Authorization Server /auth/oauth2/authorize端点404问题排查

配置代码

@Configuration
@EnableWebSecurity
@Import(OAuth2AuthorizationServerConfiguration::class)
class AuthorizationServerConfig(
    private val passwordEncoder: PasswordEncoder,
    @Value("\${key.store.password.private}") private val password: String
) {

    @Bean
    @Order(1)
    @Throws(Exception::class)
    fun authorizationServerSecurityFilterChain(
        http: HttpSecurity,
        jdbcOperations: JdbcOperations,
        registeredClientRepository: RegisteredClientRepository,
        jwtService: JwtService
    ): SecurityFilterChain {
        val authorizationServerConfigurer = OAuth2AuthorizationServerConfigurer.authorizationServer()
        http.authorizeHttpRequests { it.requestMatchers("/auth/**").permitAll() }
            .securityMatcher(authorizationServerConfigurer.endpointsMatcher).with(
                authorizationServerConfigurer
            ) { authorizationServer ->
                authorizationServer.authorizationService(
                    JdbcOAuth2AuthorizationService(
                        jdbcOperations, registeredClientRepository
                    )
                ).registeredClientRepository(registeredClientRepository(jdbcOperations)).authorizationConsentService(
                    JdbcOAuth2AuthorizationConsentService(
                        jdbcOperations, registeredClientRepository
                    )
                )
                    .authorizationEndpoint {
                        it
                    }
                    .authorizationServerSettings(endpoints).tokenGenerator(JwtGenerator(NimbusJwtEncoder(jwkSource())))
                    .oidc { oidcConfigurer ->
                        oidcConfigurer.userInfoEndpoint { userInfoEndpointConfigurer ->
                            userInfoEndpointConfigurer.userInfoMapper {
                                logger.info(it.accessToken.tokenValue)
                                logger.info(it.accessToken.expiresAt?.toString())
                                val claims = jwtService.getAllClaims(it.accessToken.tokenValue)
                                OidcUserInfo(claims)
                            }
                        }
                    }
            }
        return http.build()
    }


        @Bean
    fun registeredClientRepository(jdbcOperations: JdbcOperations): RegisteredClientRepository {
        val adminClient = RegisteredClient.withId(UUID.randomUUID().toString()).clientId("admin-client")
            .clientSecret(passwordEncoder.encode("secret")).clientAuthenticationMethods {
                it.apply {
                    add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                    add(ClientAuthenticationMethod.CLIENT_SECRET_POST)
                    add(ClientAuthenticationMethod.PRIVATE_KEY_JWT)
                    add(ClientAuthenticationMethod.TLS_CLIENT_AUTH)
                    add(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
                    add(ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH)
                }
            }.authorizationGrantTypes {
                it.apply {
                    add(AuthorizationGrantType.CLIENT_CREDENTIALS)
                    add(AuthorizationGrantType.AUTHORIZATION_CODE)
                    add(AuthorizationGrantType.REFRESH_TOKEN)
                    add(AuthorizationGrantType.JWT_BEARER)
                    add(AuthorizationGrantType.DEVICE_CODE)
                    add(AuthorizationGrantType.TOKEN_EXCHANGE)
                }
            }.redirectUri("http://localhost:8081/new").scope(OidcScopes.OPENID).scope(OidcScopes.PROFILE)
            .scope("client.create").scope("message.write")
            .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build()
        // adminClient already persisting in DB
        return JdbcRegisteredClientRepository(jdbcOperations)
    }


    // Other beans (jwkSource, jwtDecoder, etc.) omitted for brevity
}

问题详情

发送GET请求到http://localhost:8081/auth/oauth2/authorize?response_type=code&client_id=admin-client&redirect_uri=http://localhost:8081/new&scope=openid&state=abc123时,收到响应:

{
  "timestamp": "2025-02-03T09:36:04.774+00:00",
  "status": 404,
  "error": "Not Found",
  "path": "/auth/oauth2/authorize"
}

已尝试操作

  • 验证服务器运行在8081端口,无自定义上下文路径;
  • 确认redirect_uri与RegisteredClient中注册的一致;
  • 检查安全配置未拦截/oauth2/authorize端点。

疑问

  1. 为何/auth/oauth2/authorize端点返回404 Not Found错误?
  2. 暴露/oauth2/authorize端点是否需要额外配置或依赖?
  3. 如何进一步调试该问题?

问题排查与解决方案

1. 404错误的核心原因

(1)端点路径未配置前缀

Spring Authorization Server默认授权端点为/oauth2/authorize,你请求的路径带/auth前缀,但代码中未配置该前缀。需补充AuthorizationServerSettings Bean配置路径前缀:

@Bean
fun authorizationServerSettings(): AuthorizationServerSettings {
    return AuthorizationServerSettings.builder()
        .baseUrl("/auth") // 全局前缀,所有授权服务器端点都会带上/auth
        // 或单独指定授权端点路径:.authorizationEndpoint("/auth/oauth2/authorize")
        .build()
}

缺少该配置时,默认端点不带/auth前缀,因此带前缀的请求会返回404。

(2)SecurityFilterChain匹配逻辑不匹配

你使用.securityMatcher(authorizationServerConfigurer.endpointsMatcher),该Matcher默认仅匹配授权服务器的默认端点路径(不带/auth前缀),导致/auth/**路径的请求不会被该FilterChain处理,进而找不到对应端点。

需调整匹配规则,让FilterChain覆盖/auth/**路径:

http.securityMatcher("/auth/**")
    .authorizeHttpRequests { it.requestMatchers("/auth/**").permitAll() }
    .with(authorizationServerConfigurer) { /* 原有授权服务器配置 */ }

2. 端点暴露的配置与依赖要求

  • 依赖:确保已引入spring-security-oauth2-authorization-server依赖,版本需与Spring Boot/Spring Security兼容(如Spring Boot 3.x对应Authorization Server 1.x)。
  • 配置:无需额外暴露端点,但必须保证请求路径与授权服务器配置的端点路径一致,同时SecurityFilterChain能正确匹配这些路径。自定义前缀必须通过AuthorizationServerSettings配置。

3. 进一步调试方法

  • 启用调试日志:添加日志配置,查看请求过滤链和端点映射:
    logging.level.org.springframework.security=DEBUG
    logging.level.org.springframework.web.servlet.mvc.method.annotation=TRACE
    
    可清晰看到请求经过的过滤器,以及端点是否被正确注册。
  • 检查端点映射:引入spring-boot-starter-actuator依赖,开启mappings端点,访问/actuator/mappings查看是否存在/auth/oauth2/authorize的映射。
  • 测试默认端点:直接请求http://localhost:8081/oauth2/authorize(不带/auth前缀),若能正常响应,说明问题出在路径前缀配置。
  • 确认FilterChain顺序:确保authorizationServerSecurityFilterChain的@Order(1)优先级高于其他SecurityFilterChain,避免请求被其他过滤器拦截。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 14:47:33