我有以下安全配置:
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
时,我得到的是:
日志相关部分:
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
的请求匹配器在此处设置:
如果我将
pre
方法更改为以下,它仍然不起作用:
@Bean
@Order(1)
fun pre(http: HttpSecurity, authManager: AuthenticationManager, authProvider: AuthenticationProvider): SecurityFilterChain {
return http.build()
}
但是,如果我完全删除
pre
方法,甚至只是删除 @Bean
和 @Order(1)
,它确实有效。为什么在 oidc
过滤器之前安装安全过滤器会导致问题?
当您想要使用多个安全过滤器时,您需要至少在除第一个过滤器之外的所有过滤器中指定
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]