refactor: Spring SecurityのコンフィグをKotlin DSLで記述するように

This commit is contained in:
usbharu 2023-11-30 13:15:02 +09:00
parent 6af7cd67c8
commit 56458fc53c
3 changed files with 84 additions and 79 deletions

View File

@ -13,28 +13,29 @@ import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.Htt
import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.UserDetailsImpl import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.UserDetailsImpl
import dev.usbharu.hideout.core.query.UserQueryService import dev.usbharu.hideout.core.query.UserQueryService
import dev.usbharu.hideout.util.RsaUtil import dev.usbharu.hideout.util.RsaUtil
import dev.usbharu.hideout.util.hasAnyScope
import dev.usbharu.httpsignature.sign.RsaSha256HttpSignatureSigner import dev.usbharu.httpsignature.sign.RsaSha256HttpSignatureSigner
import dev.usbharu.httpsignature.verify.DefaultSignatureHeaderParser import dev.usbharu.httpsignature.verify.DefaultSignatureHeaderParser
import dev.usbharu.httpsignature.verify.RsaSha256HttpSignatureVerifier import dev.usbharu.httpsignature.verify.RsaSha256HttpSignatureVerifier
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer
import org.springframework.boot.autoconfigure.security.servlet.PathRequest
import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Primary import org.springframework.context.annotation.Primary
import org.springframework.core.annotation.Order import org.springframework.core.annotation.Order
import org.springframework.http.HttpMethod import org.springframework.http.HttpMethod.GET
import org.springframework.http.HttpMethod.POST
import org.springframework.http.HttpStatus import org.springframework.http.HttpStatus
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
import org.springframework.security.authentication.AccountStatusUserDetailsChecker import org.springframework.security.authentication.AccountStatusUserDetailsChecker
import org.springframework.security.authentication.AuthenticationManager import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.config.Customizer
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration
import org.springframework.security.config.annotation.web.builders.HttpSecurity 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.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.invoke
import org.springframework.security.config.http.SessionCreationPolicy import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.core.Authentication import org.springframework.security.core.Authentication
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
@ -51,15 +52,14 @@ import org.springframework.security.web.authentication.AuthenticationEntryPointF
import org.springframework.security.web.authentication.HttpStatusEntryPoint import org.springframework.security.web.authentication.HttpStatusEntryPoint
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher import org.springframework.security.web.savedrequest.RequestCacheAwareFilter
import org.springframework.security.web.util.matcher.AnyRequestMatcher import org.springframework.security.web.util.matcher.AnyRequestMatcher
import org.springframework.web.servlet.handler.HandlerMappingIntrospector
import java.security.KeyPairGenerator import java.security.KeyPairGenerator
import java.security.interfaces.RSAPrivateKey import java.security.interfaces.RSAPrivateKey
import java.security.interfaces.RSAPublicKey import java.security.interfaces.RSAPublicKey
import java.util.* import java.util.*
@EnableWebSecurity(debug = false) @EnableWebSecurity(debug = true)
@Configuration @Configuration
@Suppress("FunctionMaxLength", "TooManyFunctions") @Suppress("FunctionMaxLength", "TooManyFunctions")
class SecurityConfig { class SecurityConfig {
@ -75,40 +75,26 @@ class SecurityConfig {
@Order(1) @Order(1)
fun httpSignatureFilterChain( fun httpSignatureFilterChain(
http: HttpSecurity, http: HttpSecurity,
httpSignatureFilter: HttpSignatureFilter, httpSignatureFilter: HttpSignatureFilter
introspector: HandlerMappingIntrospector
): SecurityFilterChain { ): SecurityFilterChain {
val builder = MvcRequestMatcher.Builder(introspector) http {
http securityMatcher("/users/*/posts/*")
.securityMatcher("/users/*/posts/*") addFilterAt<RequestCacheAwareFilter>(httpSignatureFilter)
.addFilter(httpSignatureFilter) addFilterBefore<HttpSignatureFilter>(ExceptionTranslationFilter(HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)))
.addFilterBefore( authorizeHttpRequests {
ExceptionTranslationFilter(HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)), authorize(anyRequest, permitAll)
HttpSignatureFilter::class.java
)
.authorizeHttpRequests {
it.requestMatchers(
builder.pattern("/inbox"),
builder.pattern("/outbox"),
builder.pattern("/users/*/inbox"),
builder.pattern("/users/*/outbox")
).authenticated()
it.anyRequest().permitAll()
} }
.csrf { exceptionHandling {
it.disable() authenticationEntryPoint = HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)
} defaultAuthenticationEntryPointFor(
.exceptionHandling {
it.authenticationEntryPoint(HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
it.defaultAuthenticationEntryPointFor(
HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED), HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
AnyRequestMatcher.INSTANCE AnyRequestMatcher.INSTANCE
) )
} }
.sessionManagement { sessionManagement {
it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) sessionCreationPolicy = SessionCreationPolicy.STATELESS
}
} }
return http.build() return http.build()
} }
@ -152,59 +138,65 @@ class SecurityConfig {
@Bean @Bean
@Order(2) @Order(2)
fun oauth2SecurityFilterChain(http: HttpSecurity, introspector: HandlerMappingIntrospector): SecurityFilterChain { fun oauth2SecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
val builder = MvcRequestMatcher.Builder(introspector)
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http) OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http)
http.exceptionHandling { http {
it.authenticationEntryPoint( exceptionHandling {
LoginUrlAuthenticationEntryPoint("/login") authenticationEntryPoint = LoginUrlAuthenticationEntryPoint("/login")
) }
}.oauth2ResourceServer { oauth2ResourceServer {
it.jwt(Customizer.withDefaults()) jwt {
}
}
} }
return http.build() return http.build()
} }
@Bean @Bean
@Order(4) @Order(4)
fun defaultSecurityFilterChain(http: HttpSecurity, introspector: HandlerMappingIntrospector): SecurityFilterChain { fun defaultSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
val builder = MvcRequestMatcher.Builder(introspector) http {
authorizeHttpRequests {
http.authorizeHttpRequests { authorize("/error", permitAll)
it.requestMatchers(PathRequest.toH2Console()).permitAll() authorize("/login", permitAll)
it.requestMatchers( authorize(GET, "/.well-known/**", permitAll)
builder.pattern("/inbox"), authorize(GET, "/nodeinfo/2.0", permitAll)
builder.pattern("/users/*/inbox"),
builder.pattern("/api/v1/apps"), authorize(POST, "/inbox", permitAll)
builder.pattern("/api/v1/instance/**"), authorize(POST, "/users/*/inbox", permitAll)
builder.pattern("/.well-known/**"),
builder.pattern("/error"), authorize(POST, "/api/v1/apps", permitAll)
builder.pattern("/nodeinfo/2.0"), authorize(GET, "/api/v1/instance/**", permitAll)
builder.pattern("/api/v1/accounts") authorize(POST, "/api/v1/accounts", permitAll)
).permitAll()
it.requestMatchers( authorize("/auth/sign_up", hasRole("ANONYMOUS"))
builder.pattern("/auth/**")
).anonymous() authorize(GET, "/api/v1/accounts/verify_credentials", hasAnyScope("read", "read:accounts"))
it.requestMatchers(builder.pattern("/change-password")).authenticated()
it.requestMatchers(builder.pattern("/api/v1/accounts/verify_credentials")) authorize(POST, "/api/v1/media", hasAnyScope("write", "write:media"))
.hasAnyAuthority("SCOPE_read", "SCOPE_read:accounts") authorize(POST, "/api/v1/statuses", hasAnyScope("write", "write:statuses"))
it.requestMatchers(builder.pattern(HttpMethod.POST, "/api/v1/media"))
.hasAnyAuthority("SCOPE_write", "SCOPE_write:media") authorize(anyRequest, authenticated)
it.requestMatchers(builder.pattern(HttpMethod.POST, "/api/v1/statuses")) }
.hasAnyAuthority("SCOPE_write", "SCOPE_write:statuses")
it.anyRequest().authenticated() oauth2ResourceServer {
jwt { }
}
formLogin {
}
csrf {
ignoringRequestMatchers("/users/*/inbox", "/inbox", "/api/v1/apps", "/api/v1/accounts")
}
headers {
frameOptions {
sameOrigin = true
} }
http.oauth2ResourceServer {
it.jwt(Customizer.withDefaults())
}.passwordManagement { }.formLogin(Customizer.withDefaults()).csrf {
it.ignoringRequestMatchers(builder.pattern("/users/*/inbox"))
it.ignoringRequestMatchers(builder.pattern(HttpMethod.POST, "/api/v1/apps"))
it.ignoringRequestMatchers(builder.pattern("/inbox"))
it.ignoringRequestMatchers(PathRequest.toH2Console())
}.headers {
it.frameOptions { frameOptionsConfig ->
frameOptionsConfig.sameOrigin()
} }
} }
return http.build() return http.build()

View File

@ -0,0 +1,13 @@
package dev.usbharu.hideout.util
import org.springframework.security.authorization.AuthorizationManager
import org.springframework.security.config.annotation.web.AuthorizeHttpRequestsDsl
import org.springframework.security.web.access.intercept.RequestAuthorizationContext
fun AuthorizeHttpRequestsDsl.hasScope(scope: String): AuthorizationManager<RequestAuthorizationContext> =
hasAuthority("SCOPE_$scope")
fun AuthorizeHttpRequestsDsl.hasAnyScope(vararg scopes: String): AuthorizationManager<RequestAuthorizationContext> =
hasAnyAuthority(
*scopes.map { "SCOPE_$it" }.toTypedArray()
)

View File

@ -36,7 +36,7 @@ spring:
max-request-size: 40MB max-request-size: 40MB
h2: h2:
console: console:
enabled: true enabled: false
server: server:
tomcat: tomcat:
basedir: tomcat basedir: tomcat