From fd30e8258a1d991913a1ab6b754d315fd45f13c3 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 29 Sep 2023 13:52:10 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20JWT=E3=83=88=E3=83=BC=E3=82=AF=E3=83=B3?= =?UTF-8?q?=E3=81=AE=E6=9C=89=E5=8A=B9=E6=9C=9F=E9=99=90=E3=82=92=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dev/usbharu/hideout/config/SecurityConfig.kt | 14 +++++++++----- .../mastodon/MastodonStatusesApiContoller.kt | 15 ++++++++++----- .../hideout/service/api/mastodon/AppApiService.kt | 10 ++++++++++ .../service/api/mastodon/StatusesApiService.kt | 15 ++++++++------- src/main/resources/application.yml | 6 ++++++ src/main/resources/openapi/mastodon.yaml | 3 +++ 6 files changed, 46 insertions(+), 17 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/config/SecurityConfig.kt b/src/main/kotlin/dev/usbharu/hideout/config/SecurityConfig.kt index 95a2dc4c..38424b81 100644 --- a/src/main/kotlin/dev/usbharu/hideout/config/SecurityConfig.kt +++ b/src/main/kotlin/dev/usbharu/hideout/config/SecurityConfig.kt @@ -6,12 +6,14 @@ 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.util.RsaUtil import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.boot.autoconfigure.security.servlet.PathRequest import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.core.annotation.Order +import org.springframework.http.HttpMethod import org.springframework.security.config.Customizer import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity @@ -33,7 +35,7 @@ import java.security.interfaces.RSAPrivateKey import java.security.interfaces.RSAPublicKey import java.util.* -@EnableWebSecurity(debug = false) +@EnableWebSecurity(debug = true) @Configuration class SecurityConfig { @@ -88,6 +90,7 @@ class SecurityConfig { .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()) } @@ -103,6 +106,7 @@ class SecurityConfig { fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder() @Bean + @ConditionalOnProperty(name = ["hideout.security.jwt.generate"], havingValue = "false", matchIfMissing = true) fun genJwkSource(): JWKSource { val keyPairGenerator = KeyPairGenerator.getInstance("RSA") keyPairGenerator.initialize(2048) @@ -122,8 +126,8 @@ class SecurityConfig { @Bean @ConditionalOnProperty(name = ["hideout.security.jwt.generate"], havingValue = "") fun loadJwkSource(jwkConfig: JwkConfig): JWKSource { - val rsaKey = RSAKey.Builder(jwkConfig.publicKey) - .privateKey(jwkConfig.privateKey) + val rsaKey = RSAKey.Builder(RsaUtil.decodeRsaPublicKey(jwkConfig.publicKey)) + .privateKey(RsaUtil.decodeRsaPrivateKey(jwkConfig.privateKey)) .keyID(jwkConfig.keyId) .build() return ImmutableJWKSet(JWKSet(rsaKey)) @@ -157,6 +161,6 @@ class SecurityConfig { @ConditionalOnProperty(name = ["hideout.security.jwt.generate"], havingValue = "") data class JwkConfig( val keyId: String, - val publicKey: RSAPublicKey, - val privateKey: RSAPrivateKey + val publicKey: String, + val privateKey: String ) diff --git a/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonStatusesApiContoller.kt b/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonStatusesApiContoller.kt index b75a7da0..0d40471e 100644 --- a/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonStatusesApiContoller.kt +++ b/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonStatusesApiContoller.kt @@ -3,19 +3,24 @@ package dev.usbharu.hideout.controller.mastodon import dev.usbharu.hideout.controller.mastodon.generated.StatusApi import dev.usbharu.hideout.domain.mastodon.model.generated.Status import dev.usbharu.hideout.domain.mastodon.model.generated.StatusesRequest -import dev.usbharu.hideout.domain.model.UserDetailsImpl import dev.usbharu.hideout.service.api.mastodon.StatusesApiService import kotlinx.coroutines.runBlocking import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.security.oauth2.jwt.Jwt import org.springframework.stereotype.Controller +import org.springframework.web.bind.annotation.ModelAttribute @Controller class MastodonStatusesApiContoller(private val statusesApiService: StatusesApiService) : StatusApi { - override fun apiV1StatusesPost(statusesRequest: StatusesRequest): ResponseEntity = runBlocking { - val principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal() - require(principal is UserDetailsImpl) - ResponseEntity(statusesApiService.postStatus(statusesRequest, principal), HttpStatus.OK) + override fun apiV1StatusesPost(@ModelAttribute statusesRequest: StatusesRequest): ResponseEntity = + runBlocking { + val jwt = SecurityContextHolder.getContext().authentication.principal as Jwt + + ResponseEntity( + statusesApiService.postStatus(statusesRequest, jwt.getClaim("uid").toLong()), + HttpStatus.OK + ) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/AppApiService.kt b/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/AppApiService.kt index 9d4f3e15..6d1cb252 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/AppApiService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/AppApiService.kt @@ -10,7 +10,10 @@ import org.springframework.security.oauth2.core.ClientAuthenticationMethod 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.settings.ClientSettings +import org.springframework.security.oauth2.server.authorization.settings.TokenSettings import org.springframework.stereotype.Service +import java.time.Duration +import java.time.Instant import java.util.* @Service @@ -40,6 +43,13 @@ class AppApiServiceImpl( .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .redirectUri(appsRequest.redirectUris) + .tokenSettings( + TokenSettings.builder() + .accessTokenTimeToLive( + Duration.ofSeconds((Instant.MAX.epochSecond - Instant.now().epochSecond - 10000) / 1000) + ) + .build() + ) .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()) .scopes { it.addAll(parseScope(appsRequest.scopes.orEmpty())) } .build() diff --git a/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/StatusesApiService.kt b/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/StatusesApiService.kt index d058ed13..4fd1e4fa 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/StatusesApiService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/StatusesApiService.kt @@ -2,12 +2,12 @@ package dev.usbharu.hideout.service.api.mastodon import dev.usbharu.hideout.domain.mastodon.model.generated.Status import dev.usbharu.hideout.domain.mastodon.model.generated.StatusesRequest -import dev.usbharu.hideout.domain.model.UserDetailsImpl import dev.usbharu.hideout.domain.model.hideout.dto.PostCreateDto import dev.usbharu.hideout.domain.model.hideout.entity.Visibility import dev.usbharu.hideout.exception.FailedToGetResourcesException import dev.usbharu.hideout.query.PostQueryService import dev.usbharu.hideout.query.UserQueryService +import dev.usbharu.hideout.service.core.Transaction import dev.usbharu.hideout.service.mastodon.AccountService import dev.usbharu.hideout.service.post.PostService import org.springframework.stereotype.Service @@ -15,7 +15,7 @@ import java.time.Instant @Service interface StatusesApiService { - suspend fun postStatus(statusesRequest: StatusesRequest, user: UserDetailsImpl): Status + suspend fun postStatus(statusesRequest: StatusesRequest, userId: Long): Status } @Service @@ -23,11 +23,12 @@ class StatsesApiServiceImpl( private val postService: PostService, private val accountService: AccountService, private val postQueryService: PostQueryService, - private val userQueryService: UserQueryService + private val userQueryService: UserQueryService, + private val transaction: Transaction ) : StatusesApiService { @Suppress("LongMethod") - override suspend fun postStatus(statusesRequest: StatusesRequest, user: UserDetailsImpl): Status { + override suspend fun postStatus(statusesRequest: StatusesRequest, userId: Long): Status = transaction.transaction { val visibility = when (statusesRequest.visibility) { StatusesRequest.Visibility.public -> Visibility.PUBLIC StatusesRequest.Visibility.unlisted -> Visibility.UNLISTED @@ -43,10 +44,10 @@ class StatsesApiServiceImpl( visibility = visibility, repostId = null, repolyId = statusesRequest.inReplyToId?.toLongOrNull(), - userId = user.id + userId = userId ) ) - val account = accountService.findById(user.id) + val account = accountService.findById(userId) val postVisibility = when (statusesRequest.visibility) { StatusesRequest.Visibility.public -> Status.Visibility.public @@ -66,7 +67,7 @@ class StatsesApiServiceImpl( null } - return Status( + Status( id = post.id.toString(), uri = post.apId, createdAt = Instant.ofEpochMilli(post.createdAt).toString(), diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e764c7cf..4d052250 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -7,6 +7,12 @@ hideout: password: "" job-queue: type: "nosql" + security: + jwt: + generate: true + key-id: a + private-key: "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC7VJTUt9Us8cKjMzEfYyjiWA4R4/M2bS1GB4t7NXp98C3SC6dVMvDuictGeurT8jNbvJZHtCSuYEvuNMoSfm76oqFvAp8Gy0iz5sxjZmSnXyCdPEovGhLa0VzMaQ8s+CLOyS56YyCFGeJZqgtzJ6GR3eqoYSW9b9UMvkBpZODSctWSNGj3P7jRFDO5VoTwCQAWbFnOjDfH5Ulgp2PKSQnSJP3AJLQNFNe7br1XbrhV//eO+t51mIpGSDCUv3E0DDFcWDTH9cXDTTlRZVEiR2BwpZOOkE/Z0/BVnhZYL71oZV34bKfWjQIt6V/isSMahdsAASACp4ZTGtwiVuNd9tybAgMBAAECggEBAKTmjaS6tkK8BlPXClTQ2vpz/N6uxDeS35mXpqasqskVlaAidgg/sWqpjXDbXr93otIMLlWsM+X0CqMDgSXKejLS2jx4GDjI1ZTXg++0AMJ8sJ74pWzVDOfmCEQ/7wXs3+cbnXhKriO8Z036q92Qc1+N87SI38nkGa0ABH9CN83HmQqt4fB7UdHzuIRe/me2PGhIq5ZBzj6h3BpoPGzEP+x3l9YmK8t/1cN0pqI+dQwYdgfGjackLu/2qH80MCF7IyQaseZUOJyKrCLtSD/Iixv/hzDEUPfOCjFDgTpzf3cwta8+oE4wHCo1iI1/4TlPkwmXx4qSXtmw4aQPz7IDQvECgYEA8KNThCO2gsC2I9PQDM/8Cw0O983WCDY+oi+7JPiNAJwv5DYBqEZB1QYdj06YD16XlC/HAZMsMku1na2TN0driwenQQWzoev3g2S7gRDoS/FCJSI3jJ+kjgtaA7Qmzlgk1TxODN+G1H91HW7t0l7VnL27IWyYo2qRRK3jzxqUiPUCgYEAx0oQs2reBQGMVZnApD1jeq7n4MvNLcPvt8b/eU9iUv6Y4Mj0Suo/AU8lYZXm8ubbqAlwz2VSVunD2tOplHyMUrtCtObAfVDUAhCndKaA9gApgfb3xw1IKbuQ1u4IF1FJl3VtumfQn//LiH1B3rXhcdyo3/vIttEk48RakUKClU8CgYEAzV7W3COOlDDcQd935DdtKBFRAPRPAlspQUnzMi5eSHMD/ISLDY5IiQHbIH83D4bvXq0X7qQoSBSNP7Dvv3HYuqMhf0DaegrlBuJllFVVq9qPVRnKxt1Il2HgxOBvbhOT+9in1BzA+YJ99UzC85O0Qz06A+CmtHEy4aZ2kj5hHjECgYEAmNS4+A8Fkss8Js1RieK2LniBxMgmYml3pfVLKGnzmng7H2+cwPLhPIzIuwytXywh2bzbsYEfYx3EoEVgMEpPhoarQnYPukrJO4gwE2o5Te6T5mJSZGlQJQj9q4ZB2Dfzet6INsK0oG8XVGXSpQvQh3RUYekCZQkBBFcpqWpbIEsCgYAnM3DQf3FJoSnXaMhrVBIovic5l0xFkEHskAjFTevO86Fsz1C2aSeRKSqGFoOQ0tmJzBEs1R6KqnHInicDTQrKhArgLXX4v3CddjfTRJkFWDbE/CkvKZNOrcf1nhaGCPspRJj2KUkj1Fhl9Cncdn/RsYEONbwQSjIfMPkvxF+8HQ==" + public-key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyehkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdgcKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbcmwIDAQAB" spring: jackson: serialization: diff --git a/src/main/resources/openapi/mastodon.yaml b/src/main/resources/openapi/mastodon.yaml index 246a15a5..94b34dd6 100644 --- a/src/main/resources/openapi/mastodon.yaml +++ b/src/main/resources/openapi/mastodon.yaml @@ -137,6 +137,9 @@ paths: application/json: schema: $ref: "#/components/schemas/StatusesRequest" + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/StatusesRequest" responses: 200: description: 成功