mirror of https://github.com/usbharu/Hideout.git
fix: JWTトークンの有効期限を設定
This commit is contained in:
parent
d3a84b3157
commit
c880e9c5d1
|
@ -6,12 +6,14 @@ import com.nimbusds.jose.jwk.source.ImmutableJWKSet
|
||||||
import com.nimbusds.jose.jwk.source.JWKSource
|
import com.nimbusds.jose.jwk.source.JWKSource
|
||||||
import com.nimbusds.jose.proc.SecurityContext
|
import com.nimbusds.jose.proc.SecurityContext
|
||||||
import dev.usbharu.hideout.domain.model.UserDetailsImpl
|
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.condition.ConditionalOnProperty
|
||||||
import org.springframework.boot.autoconfigure.security.servlet.PathRequest
|
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.core.annotation.Order
|
import org.springframework.core.annotation.Order
|
||||||
|
import org.springframework.http.HttpMethod
|
||||||
import org.springframework.security.config.Customizer
|
import org.springframework.security.config.Customizer
|
||||||
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
|
||||||
|
@ -33,7 +35,7 @@ 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
|
||||||
class SecurityConfig {
|
class SecurityConfig {
|
||||||
|
|
||||||
|
@ -88,6 +90,7 @@ class SecurityConfig {
|
||||||
.formLogin(Customizer.withDefaults())
|
.formLogin(Customizer.withDefaults())
|
||||||
.csrf {
|
.csrf {
|
||||||
it.ignoringRequestMatchers(builder.pattern("/users/*/inbox"))
|
it.ignoringRequestMatchers(builder.pattern("/users/*/inbox"))
|
||||||
|
it.ignoringRequestMatchers(builder.pattern(HttpMethod.POST, "/api/v1/apps"))
|
||||||
it.ignoringRequestMatchers(builder.pattern("/inbox"))
|
it.ignoringRequestMatchers(builder.pattern("/inbox"))
|
||||||
it.ignoringRequestMatchers(PathRequest.toH2Console())
|
it.ignoringRequestMatchers(PathRequest.toH2Console())
|
||||||
}
|
}
|
||||||
|
@ -103,6 +106,7 @@ class SecurityConfig {
|
||||||
fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder()
|
fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder()
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
@ConditionalOnProperty(name = ["hideout.security.jwt.generate"], havingValue = "false", matchIfMissing = true)
|
||||||
fun genJwkSource(): JWKSource<SecurityContext> {
|
fun genJwkSource(): JWKSource<SecurityContext> {
|
||||||
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
|
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
|
||||||
keyPairGenerator.initialize(2048)
|
keyPairGenerator.initialize(2048)
|
||||||
|
@ -122,8 +126,8 @@ class SecurityConfig {
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(name = ["hideout.security.jwt.generate"], havingValue = "")
|
@ConditionalOnProperty(name = ["hideout.security.jwt.generate"], havingValue = "")
|
||||||
fun loadJwkSource(jwkConfig: JwkConfig): JWKSource<SecurityContext> {
|
fun loadJwkSource(jwkConfig: JwkConfig): JWKSource<SecurityContext> {
|
||||||
val rsaKey = RSAKey.Builder(jwkConfig.publicKey)
|
val rsaKey = RSAKey.Builder(RsaUtil.decodeRsaPublicKey(jwkConfig.publicKey))
|
||||||
.privateKey(jwkConfig.privateKey)
|
.privateKey(RsaUtil.decodeRsaPrivateKey(jwkConfig.privateKey))
|
||||||
.keyID(jwkConfig.keyId)
|
.keyID(jwkConfig.keyId)
|
||||||
.build()
|
.build()
|
||||||
return ImmutableJWKSet(JWKSet(rsaKey))
|
return ImmutableJWKSet(JWKSet(rsaKey))
|
||||||
|
@ -157,6 +161,6 @@ class SecurityConfig {
|
||||||
@ConditionalOnProperty(name = ["hideout.security.jwt.generate"], havingValue = "")
|
@ConditionalOnProperty(name = ["hideout.security.jwt.generate"], havingValue = "")
|
||||||
data class JwkConfig(
|
data class JwkConfig(
|
||||||
val keyId: String,
|
val keyId: String,
|
||||||
val publicKey: RSAPublicKey,
|
val publicKey: String,
|
||||||
val privateKey: RSAPrivateKey
|
val privateKey: String
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,19 +3,24 @@ package dev.usbharu.hideout.controller.mastodon
|
||||||
import dev.usbharu.hideout.controller.mastodon.generated.StatusApi
|
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.Status
|
||||||
import dev.usbharu.hideout.domain.mastodon.model.generated.StatusesRequest
|
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 dev.usbharu.hideout.service.api.mastodon.StatusesApiService
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.springframework.http.HttpStatus
|
import org.springframework.http.HttpStatus
|
||||||
import org.springframework.http.ResponseEntity
|
import org.springframework.http.ResponseEntity
|
||||||
import org.springframework.security.core.context.SecurityContextHolder
|
import org.springframework.security.core.context.SecurityContextHolder
|
||||||
|
import org.springframework.security.oauth2.jwt.Jwt
|
||||||
import org.springframework.stereotype.Controller
|
import org.springframework.stereotype.Controller
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
class MastodonStatusesApiContoller(private val statusesApiService: StatusesApiService) : StatusApi {
|
class MastodonStatusesApiContoller(private val statusesApiService: StatusesApiService) : StatusApi {
|
||||||
override fun apiV1StatusesPost(statusesRequest: StatusesRequest): ResponseEntity<Status> = runBlocking {
|
override fun apiV1StatusesPost(@ModelAttribute statusesRequest: StatusesRequest): ResponseEntity<Status> =
|
||||||
val principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal()
|
runBlocking {
|
||||||
require(principal is UserDetailsImpl)
|
val jwt = SecurityContextHolder.getContext().authentication.principal as Jwt
|
||||||
ResponseEntity(statusesApiService.postStatus(statusesRequest, principal), HttpStatus.OK)
|
|
||||||
|
ResponseEntity(
|
||||||
|
statusesApiService.postStatus(statusesRequest, jwt.getClaim<String>("uid").toLong()),
|
||||||
|
HttpStatus.OK
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.RegisteredClient
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository
|
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.ClientSettings
|
||||||
|
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
import java.time.Duration
|
||||||
|
import java.time.Instant
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@ -40,6 +43,13 @@ class AppApiServiceImpl(
|
||||||
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
|
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
|
||||||
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
|
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
|
||||||
.redirectUri(appsRequest.redirectUris)
|
.redirectUri(appsRequest.redirectUris)
|
||||||
|
.tokenSettings(
|
||||||
|
TokenSettings.builder()
|
||||||
|
.accessTokenTimeToLive(
|
||||||
|
Duration.ofSeconds((Instant.MAX.epochSecond - Instant.now().epochSecond - 10000) / 1000)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
|
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
|
||||||
.scopes { it.addAll(parseScope(appsRequest.scopes.orEmpty())) }
|
.scopes { it.addAll(parseScope(appsRequest.scopes.orEmpty())) }
|
||||||
.build()
|
.build()
|
||||||
|
|
|
@ -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.Status
|
||||||
import dev.usbharu.hideout.domain.mastodon.model.generated.StatusesRequest
|
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.dto.PostCreateDto
|
||||||
import dev.usbharu.hideout.domain.model.hideout.entity.Visibility
|
import dev.usbharu.hideout.domain.model.hideout.entity.Visibility
|
||||||
import dev.usbharu.hideout.exception.FailedToGetResourcesException
|
import dev.usbharu.hideout.exception.FailedToGetResourcesException
|
||||||
import dev.usbharu.hideout.query.PostQueryService
|
import dev.usbharu.hideout.query.PostQueryService
|
||||||
import dev.usbharu.hideout.query.UserQueryService
|
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.mastodon.AccountService
|
||||||
import dev.usbharu.hideout.service.post.PostService
|
import dev.usbharu.hideout.service.post.PostService
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
@ -15,7 +15,7 @@ import java.time.Instant
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
interface StatusesApiService {
|
interface StatusesApiService {
|
||||||
suspend fun postStatus(statusesRequest: StatusesRequest, user: UserDetailsImpl): Status
|
suspend fun postStatus(statusesRequest: StatusesRequest, userId: Long): Status
|
||||||
}
|
}
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@ -23,11 +23,12 @@ class StatsesApiServiceImpl(
|
||||||
private val postService: PostService,
|
private val postService: PostService,
|
||||||
private val accountService: AccountService,
|
private val accountService: AccountService,
|
||||||
private val postQueryService: PostQueryService,
|
private val postQueryService: PostQueryService,
|
||||||
private val userQueryService: UserQueryService
|
private val userQueryService: UserQueryService,
|
||||||
|
private val transaction: Transaction
|
||||||
) :
|
) :
|
||||||
StatusesApiService {
|
StatusesApiService {
|
||||||
@Suppress("LongMethod")
|
@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) {
|
val visibility = when (statusesRequest.visibility) {
|
||||||
StatusesRequest.Visibility.public -> Visibility.PUBLIC
|
StatusesRequest.Visibility.public -> Visibility.PUBLIC
|
||||||
StatusesRequest.Visibility.unlisted -> Visibility.UNLISTED
|
StatusesRequest.Visibility.unlisted -> Visibility.UNLISTED
|
||||||
|
@ -43,10 +44,10 @@ class StatsesApiServiceImpl(
|
||||||
visibility = visibility,
|
visibility = visibility,
|
||||||
repostId = null,
|
repostId = null,
|
||||||
repolyId = statusesRequest.inReplyToId?.toLongOrNull(),
|
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) {
|
val postVisibility = when (statusesRequest.visibility) {
|
||||||
StatusesRequest.Visibility.public -> Status.Visibility.public
|
StatusesRequest.Visibility.public -> Status.Visibility.public
|
||||||
|
@ -66,7 +67,7 @@ class StatsesApiServiceImpl(
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
return Status(
|
Status(
|
||||||
id = post.id.toString(),
|
id = post.id.toString(),
|
||||||
uri = post.apId,
|
uri = post.apId,
|
||||||
createdAt = Instant.ofEpochMilli(post.createdAt).toString(),
|
createdAt = Instant.ofEpochMilli(post.createdAt).toString(),
|
||||||
|
|
|
@ -7,6 +7,12 @@ hideout:
|
||||||
password: ""
|
password: ""
|
||||||
job-queue:
|
job-queue:
|
||||||
type: "nosql"
|
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:
|
spring:
|
||||||
jackson:
|
jackson:
|
||||||
serialization:
|
serialization:
|
||||||
|
|
|
@ -137,6 +137,9 @@ paths:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/StatusesRequest"
|
$ref: "#/components/schemas/StatusesRequest"
|
||||||
|
application/x-www-form-urlencoded:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/StatusesRequest"
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: 成功
|
description: 成功
|
||||||
|
|
Loading…
Reference in New Issue