diff --git a/src/main/kotlin/dev/usbharu/hideout/config/SecurityConfig.kt b/src/main/kotlin/dev/usbharu/hideout/config/SecurityConfig.kt index 56bafa6c..6e5ef8b0 100644 --- a/src/main/kotlin/dev/usbharu/hideout/config/SecurityConfig.kt +++ b/src/main/kotlin/dev/usbharu/hideout/config/SecurityConfig.kt @@ -7,7 +7,16 @@ import com.nimbusds.jose.jwk.source.ImmutableJWKSet import com.nimbusds.jose.jwk.source.JWKSource import com.nimbusds.jose.proc.SecurityContext import dev.usbharu.hideout.domain.model.UserDetailsImpl +import dev.usbharu.hideout.query.UserQueryService +import dev.usbharu.hideout.service.core.Transaction +import dev.usbharu.hideout.service.signature.HttpSignatureFilter +import dev.usbharu.hideout.service.signature.HttpSignatureUserDetailsService +import dev.usbharu.hideout.service.signature.HttpSignatureVerifierComposite import dev.usbharu.hideout.util.RsaUtil +import dev.usbharu.httpsignature.sign.RsaSha256HttpSignatureSigner +import dev.usbharu.httpsignature.verify.DefaultSignatureHeaderParser +import dev.usbharu.httpsignature.verify.RsaSha256HttpSignatureVerifier +import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer import org.springframework.boot.autoconfigure.security.servlet.PathRequest @@ -19,9 +28,13 @@ import org.springframework.core.annotation.Order import org.springframework.http.HttpMethod import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter +import org.springframework.security.authentication.AccountStatusUserDetailsChecker +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.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity +import org.springframework.security.config.http.SessionCreationPolicy import org.springframework.security.core.Authentication import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder import org.springframework.security.crypto.password.PasswordEncoder @@ -33,6 +46,7 @@ import org.springframework.security.oauth2.server.authorization.token.JwtEncodin import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer import org.springframework.security.web.SecurityFilterChain import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint +import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher import org.springframework.web.servlet.handler.HandlerMappingIntrospector import java.security.KeyPairGenerator @@ -42,11 +56,63 @@ import java.util.* @EnableWebSecurity(debug = false) @Configuration -@Suppress("FunctionMaxLength ") +@Suppress("FunctionMaxLength") class SecurityConfig { + @Autowired + private lateinit var userQueryService: UserQueryService + + @Bean + fun authenticationManager(authenticationConfiguration: AuthenticationConfiguration): AuthenticationManager? { + return authenticationConfiguration.authenticationManager + } + @Bean @Order(1) + fun httpSignatureFilterChain(http: HttpSecurity, httpSignatureFilter: HttpSignatureFilter): SecurityFilterChain { + http.securityMatcher("/inbox", "/outbox", "/users/*/inbox", "/users/*/outbox") + .addFilter(httpSignatureFilter) + .authorizeHttpRequests { + it.anyRequest().permitAll() + } + .csrf { + it.disable() + } + .sessionManagement { + it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) + } + return http.build() + } + + + @Bean + fun getHttpSignatureFilter(authenticationManager: AuthenticationManager): HttpSignatureFilter { + val httpSignatureFilter = HttpSignatureFilter(DefaultSignatureHeaderParser()) + httpSignatureFilter.setAuthenticationManager(authenticationManager) + return httpSignatureFilter + } + + @Bean + fun httpSignatureAuthenticationProvider(transaction: Transaction): PreAuthenticatedAuthenticationProvider { + val provider = PreAuthenticatedAuthenticationProvider() + provider.setPreAuthenticatedUserDetailsService( + HttpSignatureUserDetailsService( + userQueryService, HttpSignatureVerifierComposite( + mapOf( + "rsa-sha256" to RsaSha256HttpSignatureVerifier( + DefaultSignatureHeaderParser(), + RsaSha256HttpSignatureSigner() + ) + ), DefaultSignatureHeaderParser() + ), transaction + ) + ) + provider.setUserDetailsChecker(AccountStatusUserDetailsChecker()) + return provider + } + + @Bean + @Order(2) fun oauth2SecurityFilterChain(http: HttpSecurity, introspector: HandlerMappingIntrospector): SecurityFilterChain { val builder = MvcRequestMatcher.Builder(introspector) @@ -64,7 +130,7 @@ class SecurityConfig { } @Bean - @Order(2) + @Order(4) fun defaultSecurityFilterChain(http: HttpSecurity, introspector: HandlerMappingIntrospector): SecurityFilterChain { val builder = MvcRequestMatcher.Builder(introspector) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureFilter.kt b/src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureFilter.kt index d5b633fd..0094e812 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureFilter.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureFilter.kt @@ -40,7 +40,7 @@ class HttpSignatureFilter(private val httpSignatureHeaderParser: SignatureHeader } return HttpRequest( - URL(url + request.queryString), + URL(url + request.queryString.orEmpty()), HttpHeaders(headers), method ) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureUserDetailsService.kt b/src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureUserDetailsService.kt index f35799cf..591cd26d 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureUserDetailsService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureUserDetailsService.kt @@ -3,6 +3,7 @@ package dev.usbharu.hideout.service.signature import dev.usbharu.hideout.exception.FailedToGetResourcesException import dev.usbharu.hideout.exception.HttpSignatureVerifyException import dev.usbharu.hideout.query.UserQueryService +import dev.usbharu.hideout.service.core.Transaction import dev.usbharu.hideout.util.RsaUtil import dev.usbharu.httpsignature.common.HttpRequest import dev.usbharu.httpsignature.common.PublicKey @@ -16,39 +17,44 @@ import org.springframework.security.web.authentication.preauth.PreAuthenticatedA class HttpSignatureUserDetailsService( private val userQueryService: UserQueryService, - private val httpSignatureVerifier: HttpSignatureVerifier + private val httpSignatureVerifier: HttpSignatureVerifier, + private val transaction: Transaction ) : AuthenticationUserDetailsService { override fun loadUserDetails(token: PreAuthenticatedAuthenticationToken): UserDetails = runBlocking { - if (token.principal !is String) { - throw IllegalStateException("Token is not String") - } - if (token.credentials !is HttpRequest) { - throw IllegalStateException("Credentials is not HttpRequest") - } + transaction.transaction { - val keyId = token.principal as String - val findByKeyId = try { - userQueryService.findByKeyId(keyId) - } catch (e: FailedToGetResourcesException) { - throw UsernameNotFoundException("User not found", e) + + if (token.principal !is String) { + throw IllegalStateException("Token is not String") + } + if (token.credentials !is HttpRequest) { + throw IllegalStateException("Credentials is not HttpRequest") + } + + val keyId = token.principal as String + val findByKeyId = try { + userQueryService.findByKeyId(keyId) + } catch (e: FailedToGetResourcesException) { + throw UsernameNotFoundException("User not found", e) + } + + val verify = httpSignatureVerifier.verify( + token.credentials as HttpRequest, + PublicKey(RsaUtil.decodeRsaPublicKeyPem(findByKeyId.publicKey), keyId) + ) + + if (verify is FailedVerification) { + throw HttpSignatureVerifyException(verify.reason) + } + + HttpSignatureUser( + username = findByKeyId.name, + domain = findByKeyId.domain, + credentialsNonExpired = true, + accountNonLocked = true, + authorities = mutableListOf() + ) } - - val verify = httpSignatureVerifier.verify( - token.credentials as HttpRequest, - PublicKey(RsaUtil.decodeRsaPublicKeyPem(findByKeyId.publicKey), keyId) - ) - - if (verify is FailedVerification) { - throw HttpSignatureVerifyException(verify.reason) - } - - HttpSignatureUser( - username = findByKeyId.name, - domain = findByKeyId.domain, - credentialsNonExpired = true, - accountNonLocked = true, - authorities = mutableListOf() - ) } } diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 5853405c..73cc4b4a 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -13,4 +13,5 @@ +