为什么openid-configuration返回404(Spring授权服务器)

问题描述 投票:0回答:1

我有以下安全配置:

package auth

import classLogger
import com.nimbusds.jose.jwk.JWKSet
import com.nimbusds.jose.jwk.RSAKey
import com.nimbusds.jose.jwk.source.ImmutableJWKSet
import com.nimbusds.jose.jwk.source.JWKSource
import com.nimbusds.jose.proc.SecurityContext
import lib.serialization.json
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.annotation.Order
import org.springframework.http.MediaType
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.authentication.ProviderManager
import org.springframework.security.config.Customizer
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.invoke
import org.springframework.security.oauth2.core.AuthorizationGrantType
import org.springframework.security.oauth2.core.ClientAuthenticationMethod
import org.springframework.security.oauth2.core.oidc.OidcScopes
import org.springframework.security.oauth2.jwt.JwtDecoder
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.interfaces.RSAPrivateKey
import java.security.interfaces.RSAPublicKey
import java.util.*

const val baseUrl = "/auth"

@Configuration
@EnableWebSecurity
class SecurityConfig {

    private val logger by classLogger

    @Bean
    @Order(1)
    fun pre(http: HttpSecurity, authManager: AuthenticationManager, authProvider: AuthenticationProvider): SecurityFilterChain {
        logger.info { "chain: pre" }
        http {
            csrf { disable() }
            http.passwordlessLogin(authManager) {}
            http.authenticationProvider(authProvider)
        }
        return http.build()
    }

    @Bean
    @Order(2)
    fun oidc(http: HttpSecurity): SecurityFilterChain {
        logger.info { "chain: oidc" }
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http)
        http.getConfigurer(OAuth2AuthorizationServerConfigurer::class.java).oidc(Customizer.withDefaults())
        http.exceptionHandling { exceptions ->
            exceptions.defaultAuthenticationEntryPointFor(
                LoginUrlAuthenticationEntryPoint("/login"),
                MediaTypeRequestMatcher(MediaType.TEXT_HTML)
            ).defaultAccessDeniedHandlerFor(
                { _, response, _ ->
                    response.contentType = MediaType.APPLICATION_JSON_VALUE
                    response.sendError(403, mapOf("status" to "error", "message" to "Access denied").json)
                },
                MediaTypeRequestMatcher(MediaType.ALL)
            )
        }
        http.oauth2ResourceServer {
            it.jwt(Customizer.withDefaults())
        }
        return http.build()
    }

    @Bean
    @Order(3)
    fun post(http: HttpSecurity): SecurityFilterChain {
        logger.info { "chain: post" }
        http {
            authorizeHttpRequests {
                authorize("$baseUrl/login/**", permitAll)
                authorize("$baseUrl/oauth2/**", permitAll)
                authorize("/error", permitAll)
                authorize(anyRequest, authenticated)
            }
        }
        return http.build()
    }

    @Bean
    fun registeredClientRepository(): RegisteredClientRepository {
        val publicClient = RegisteredClient.withId(UUID.randomUUID().toString())
            .clientId("public-client")
            .clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
            .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
            .redirectUri("https://localhost/auth/callback")
            .scope(OidcScopes.OPENID)
            .scope(OidcScopes.PROFILE)
            .clientSettings(
                ClientSettings.builder()
                    .requireAuthorizationConsent(false)
                    .requireProofKey(true)
                    .build()
            )
            .build()


        return InMemoryRegisteredClientRepository(publicClient)
    }

    @Bean
    fun jwkSource(): JWKSource<SecurityContext> {
        val keyPair = generateRsaKey()
        val publicKey = keyPair.public as RSAPublicKey
        val privateKey = keyPair.private as RSAPrivateKey
        val rsaKey = RSAKey.Builder(publicKey)
            .privateKey(privateKey)
            .keyID(UUID.randomUUID().toString())
            .build()
        val jwkSet = JWKSet(rsaKey)
        return ImmutableJWKSet(jwkSet)
    }

    private fun generateRsaKey(): KeyPair {
        val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
        keyPairGenerator.initialize(2048)
        return keyPairGenerator.generateKeyPair()
    }

    @Bean
    fun jwtDecoder(jwkSource: JWKSource<SecurityContext?>?): JwtDecoder =
        OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource)

    @Bean
    fun authorizationServerSettings(): AuthorizationServerSettings = AuthorizationServerSettings.builder()
        .issuer("https://api.localhost")
        .authorizationEndpoint("$baseUrl/oauth2/authorize")
        .tokenEndpoint("$baseUrl/oauth2/token")
        .jwkSetEndpoint("$baseUrl/oauth2/jwks")
        .oidcUserInfoEndpoint("$baseUrl/oauth2/userinfo")
        .tokenIntrospectionEndpoint("$baseUrl/oauth2/introspect")
        .deviceAuthorizationEndpoint("$baseUrl/oauth2/device")
        .deviceVerificationEndpoint("$baseUrl/oauth2/device/verify")
        .oidcClientRegistrationEndpoint("$baseUrl/oauth2/register")
        .oidcLogoutEndpoint("$baseUrl/oauth2/logout")
        .build()

    @Bean
    fun authenticationManager(authProvider: AuthenticationProvider): AuthenticationManager {
        return ProviderManager(listOf(authProvider))
    }

}

但是当我尝试访问

/.well-known/openid-configuration
时,我得到的是:

error

日志相关部分:

2024-07-17T11:34:51.620+02:00 TRACE 180639 --- [nio-8097-exec-5] o.s.security.web.FilterChainProxy        : Trying to match request against DefaultSecurityFilterChain [RequestMatcher=any request, Filters=[org.springframework.security.web.session.DisableEncodeUrlFilter@3af9675c, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@3dbe3b7e, org.springframework.security.web.context.SecurityContextHolderFilter@5e1d932, org.springframework.security.web.header.HeaderWriterFilter@7a69df13, org.springframework.web.filter.CorsFilter@5a17d5a3, org.springframework.security.web.authentication.logout.LogoutFilter@3c181093, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@1b190187, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@7111c765, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@347de48e, org.springframework.security.web.access.ExceptionTranslationFilter@762523f3]] (1/3)
2024-07-17T11:34:51.620+02:00 DEBUG 180639 --- [nio-8097-exec-5] o.s.security.web.FilterChainProxy        : Securing GET /.well-known/openid-configuration
2024-07-17T11:34:51.620+02:00 TRACE 180639 --- [nio-8097-exec-5] o.s.security.web.FilterChainProxy        : Invoking DisableEncodeUrlFilter (1/10)
2024-07-17T11:34:51.620+02:00 TRACE 180639 --- [nio-8097-exec-5] o.s.security.web.FilterChainProxy        : Invoking WebAsyncManagerIntegrationFilter (2/10)
2024-07-17T11:34:51.620+02:00 TRACE 180639 --- [nio-8097-exec-5] o.s.security.web.FilterChainProxy        : Invoking SecurityContextHolderFilter (3/10)
2024-07-17T11:34:51.620+02:00 TRACE 180639 --- [nio-8097-exec-5] o.s.security.web.FilterChainProxy        : Invoking HeaderWriterFilter (4/10)
2024-07-17T11:34:51.620+02:00 TRACE 180639 --- [nio-8097-exec-5] o.s.security.web.FilterChainProxy        : Invoking CorsFilter (5/10)
2024-07-17T11:34:51.620+02:00 TRACE 180639 --- [nio-8097-exec-5] o.s.security.web.FilterChainProxy        : Invoking LogoutFilter (6/10)
2024-07-17T11:34:51.620+02:00 TRACE 180639 --- [nio-8097-exec-5] o.s.s.w.a.logout.LogoutFilter            : Did not match request to Or [Ant [pattern='/logout', GET], Ant [pattern='/logout', POST], Ant [pattern='/logout', PUT], Ant [pattern='/logout', DELETE]]
2024-07-17T11:34:51.620+02:00 TRACE 180639 --- [nio-8097-exec-5] o.s.security.web.FilterChainProxy        : Invoking RequestCacheAwareFilter (7/10)
2024-07-17T11:34:51.620+02:00 TRACE 180639 --- [nio-8097-exec-5] o.s.s.w.s.HttpSessionRequestCache        : matchingRequestParameterName is required for getMatchingRequest to lookup a value, but not provided
2024-07-17T11:34:51.621+02:00 TRACE 180639 --- [nio-8097-exec-5] o.s.security.web.FilterChainProxy        : Invoking SecurityContextHolderAwareRequestFilter (8/10)
2024-07-17T11:34:51.621+02:00 TRACE 180639 --- [nio-8097-exec-5] o.s.security.web.FilterChainProxy        : Invoking AnonymousAuthenticationFilter (9/10)
2024-07-17T11:34:51.621+02:00 TRACE 180639 --- [nio-8097-exec-5] o.s.security.web.FilterChainProxy        : Invoking ExceptionTranslationFilter (10/10)
2024-07-17T11:34:51.621+02:00 DEBUG 180639 --- [nio-8097-exec-5] o.s.security.web.FilterChainProxy        : Secured GET /.well-known/openid-configuration
2024-07-17T11:34:51.621+02:00 DEBUG 180639 --- [nio-8097-exec-5] o.s.web.servlet.DispatcherServlet        : GET "/.well-known/openid-configuration", parameters={}
2024-07-17T11:34:51.621+02:00 DEBUG 180639 --- [nio-8097-exec-5] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped to ResourceHttpRequestHandler [classpath [META-INF/resources/], classpath [resources/], classpath [static/], classpath [public/], ServletContext [/]]
2024-07-17T11:34:51.622+02:00 DEBUG 180639 --- [nio-8097-exec-5] o.s.w.s.r.ResourceHttpRequestHandler     : Resource not found
2024-07-17T11:34:51.622+02:00 DEBUG 180639 --- [nio-8097-exec-5] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.servlet.resource.NoResourceFoundException: No static resource .well-known/openid-configuration.]
2024-07-17T11:34:51.622+02:00 DEBUG 180639 --- [nio-8097-exec-5] o.s.web.servlet.DispatcherServlet        : Completed 404 NOT_FOUND
2024-07-17T11:34:51.622+02:00 TRACE 180639 --- [nio-8097-exec-5] w.c.HttpSessionSecurityContextRepository : No HttpSession currently exists
2024-07-17T11:34:51.622+02:00 TRACE 180639 --- [nio-8097-exec-5] .s.s.w.c.SupplierDeferredSecurityContext : Created SecurityContextImpl [Null authentication]
2024-07-17T11:34:51.622+02:00 TRACE 180639 --- [nio-8097-exec-5] .s.s.w.c.SupplierDeferredSecurityContext : Created SecurityContextImpl [Null authentication]
2024-07-17T11:34:51.622+02:00 TRACE 180639 --- [nio-8097-exec-5] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=172.27.16.1, SessionId=null], Granted Authorities=[ROLE_ANONYMOUS]]
2024-07-17T11:34:51.623+02:00 TRACE 180639 --- [nio-8097-exec-5] o.s.security.web.FilterChainProxy        : Trying to match request against DefaultSecurityFilterChain [RequestMatcher=any request, Filters=[org.springframework.security.web.session.DisableEncodeUrlFilter@3af9675c, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@3dbe3b7e, org.springframework.security.web.context.SecurityContextHolderFilter@5e1d932, org.springframework.security.web.header.HeaderWriterFilter@7a69df13, org.springframework.web.filter.CorsFilter@5a17d5a3, org.springframework.security.web.authentication.logout.LogoutFilter@3c181093, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@1b190187, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@7111c765, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@347de48e, org.springframework.security.web.access.ExceptionTranslationFilter@762523f3]] (1/3)
2024-07-17T11:34:51.623+02:00 DEBUG 180639 --- [nio-8097-exec-5] o.s.security.web.FilterChainProxy        : Securing GET /error
2024-07-17T11:34:51.623+02:00 TRACE 180639 --- [nio-8097-exec-5] o.s.security.web.FilterChainProxy        : Invoking DisableEncodeUrlFilter (1/10)
2024-07-17T11:34:51.623+02:00 TRACE 180639 --- [nio-8097-exec-5] o.s.security.web.FilterChainProxy        : Invoking WebAsyncManagerIntegrationFilter (2/10)
2024-07-17T11:34:51.623+02:00 TRACE 180639 --- [nio-8097-exec-5] o.s.security.web.FilterChainProxy        : Invoking SecurityContextHolderFilter (3/10)
2024-07-17T11:34:51.623+02:00 TRACE 180639 --- [nio-8097-exec-5] o.s.security.web.FilterChainProxy        : Invoking HeaderWriterFilter (4/10)
2024-07-17T11:34:51.623+02:00 TRACE 180639 --- [nio-8097-exec-5] o.s.security.web.FilterChainProxy        : Invoking CorsFilter (5/10)
2024-07-17T11:34:51.623+02:00 TRACE 180639 --- [nio-8097-exec-5] o.s.security.web.FilterChainProxy        : Invoking LogoutFilter (6/10)
2024-07-17T11:34:51.623+02:00 TRACE 180639 --- [nio-8097-exec-5] o.s.s.w.a.logout.LogoutFilter            : Did not match request to Or [Ant [pattern='/logout', GET], Ant [pattern='/logout', POST], Ant [pattern='/logout', PUT], Ant [pattern='/logout', DELETE]]
2024-07-17T11:34:51.623+02:00 TRACE 180639 --- [nio-8097-exec-5] o.s.security.web.FilterChainProxy        : Invoking RequestCacheAwareFilter (7/10)
2024-07-17T11:34:51.623+02:00 TRACE 180639 --- [nio-8097-exec-5] o.s.s.w.s.HttpSessionRequestCache        : matchingRequestParameterName is required for getMatchingRequest to lookup a value, but not provided
2024-07-17T11:34:51.623+02:00 TRACE 180639 --- [nio-8097-exec-5] o.s.security.web.FilterChainProxy        : Invoking SecurityContextHolderAwareRequestFilter (8/10)
2024-07-17T11:34:51.623+02:00 TRACE 180639 --- [nio-8097-exec-5] o.s.security.web.FilterChainProxy        : Invoking AnonymousAuthenticationFilter (9/10)
2024-07-17T11:34:51.623+02:00 TRACE 180639 --- [nio-8097-exec-5] o.s.security.web.FilterChainProxy        : Invoking ExceptionTranslationFilter (10/10)
2024-07-17T11:34:51.623+02:00 DEBUG 180639 --- [nio-8097-exec-5] o.s.security.web.FilterChainProxy        : Secured GET /error
2024-07-17T11:34:51.623+02:00 DEBUG 180639 --- [nio-8097-exec-5] o.s.web.servlet.DispatcherServlet        : "ERROR" dispatch for GET "/error", parameters={}
2024-07-17T11:34:51.623+02:00 DEBUG 180639 --- [nio-8097-exec-5] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#errorHtml(HttpServletRequest, HttpServletResponse)
2024-07-17T11:34:51.624+02:00 DEBUG 180639 --- [nio-8097-exec-5] o.s.w.s.v.ContentNegotiatingViewResolver : Selected 'text/html' given [text/html, text/html;q=0.8]
2024-07-17T11:34:51.624+02:00 DEBUG 180639 --- [nio-8097-exec-5] o.s.web.servlet.DispatcherServlet        : Exiting from "ERROR" dispatch, status 404
2024-07-17T11:34:51.624+02:00 TRACE 180639 --- [nio-8097-exec-5] w.c.HttpSessionSecurityContextRepository : No HttpSession currently exists
2024-07-17T11:34:51.624+02:00 TRACE 180639 --- [nio-8097-exec-5] .s.s.w.c.SupplierDeferredSecurityContext : Created SecurityContextImpl [Null authentication]
2024-07-17T11:34:51.624+02:00 TRACE 180639 --- [nio-8097-exec-5] .s.s.w.c.SupplierDeferredSecurityContext : Created SecurityContextImpl [Null authentication]
2024-07-17T11:34:51.624+02:00 TRACE 180639 --- [nio-8097-exec-5] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=172.27.16.1, SessionId=null], Granted Authorities=[ROLE_ANONYMOUS]]

在日志中您可以看到过滤器已添加:

2024-07-17T11:30:12.266+02:00 DEBUG 180639 --- [rverApplication] o.s.s.web.DefaultSecurityFilterChain     : Will secure org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer$$Lambda$767/0x00007db008574590@2e0f746c with filters: DisableEncodeUrlFilter, WebAsyncManagerIntegrationFilter, SecurityContextHolderFilter, AuthorizationServerContextFilter, HeaderWriterFilter, CorsFilter, CsrfFilter, OidcLogoutEndpointFilter, LogoutFilter, OAuth2AuthorizationServerMetadataEndpointFilter, OAuth2AuthorizationEndpointFilter, OAuth2DeviceVerificationEndpointFilter, OidcProviderConfigurationEndpointFilter, NimbusJwkSetEndpointFilter, OAuth2ClientAuthenticationFilter, BearerTokenAuthenticationFilter, RequestCacheAwareFilter, SecurityContextHolderAwareRequestFilter, AnonymousAuthenticationFilter, ExceptionTranslationFilter, AuthorizationFilter, OAuth2TokenEndpointFilter, OAuth2TokenIntrospectionEndpointFilter, OAuth2TokenRevocationEndpointFilter, OAuth2DeviceAuthorizationEndpointFilter, OidcUserInfoEndpointFilter
2024-07-17T11:30:12.267+02:00 DEBUG 180639 --- [rverApplication] o.s.b.f.s.DefaultListableBeanFactory     : Creating shared instance of singleton bean 'post'
2024-07-17T11:30:12.267+02:00 DEBUG 180639 --- [rverApplication] o.s.b.f.s.DefaultListableBeanFactory     : Autowiring by type from bean name 'post' via factory method to bean named 'org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration.httpSecurity'
2024-07-17T11:30:12.268+02:00  INFO 180639 --- [rverApplication] auth.SecurityConfig                      : chain: post
2024-07-17T11:30:12.271+02:00 DEBUG 180639 --- [rverApplication] o.s.s.web.DefaultSecurityFilterChain     : Will secure any request with filters: DisableEncodeUrlFilter, WebAsyncManagerIntegrationFilter, SecurityContextHolderFilter, HeaderWriterFilter, CorsFilter, CsrfFilter, LogoutFilter, RequestCacheAwareFilter, SecurityContextHolderAwareRequestFilter, AnonymousAuthenticationFilter, ExceptionTranslationFilter, AuthorizationFilter

.well-known/openid-configuration
的请求匹配器在此处设置: debugger

如果我将

pre
方法更改为以下,它仍然不起作用:

    @Bean
    @Order(1)
    fun pre(http: HttpSecurity, authManager: AuthenticationManager, authProvider: AuthenticationProvider): SecurityFilterChain {
        return http.build()
    }

但是,如果我完全删除

pre
方法,甚至只是删除
@Bean
@Order(1)
,它确实有效。为什么在
oidc
过滤器之前安装安全过滤器会导致问题?

spring spring-boot kotlin spring-security openid-connect
1个回答
0
投票

当您想要使用多个安全过滤器时,您需要至少在除第一个过滤器之外的所有过滤器中指定

securityMatcher

示例:

    @Bean
    @Order(3)
    fun post(http: HttpSecurity): SecurityFilterChain {
        logger.info { "chain: post" }
        http {
            securityMatcher("**")
            authorizeHttpRequests {
                authorize("$baseUrl/login/**", permitAll)
                authorize("$baseUrl/oauth2/**", permitAll)
                authorize("/error", permitAll)
                authorize(anyRequest, authenticated)
            }
        }
        return http.build()
    }

securityMatcher("**")
匹配所有请求,从而处理安全规则。如果省略,则仅使用第一个安全过滤器。 [1]

文档:https://docs.spring.io/spring-security/reference/servlet/authorization/authorize-http-requests.html#security-matchers

© www.soinside.com 2019 - 2024. All rights reserved.