mirror of https://github.com/usbharu/Hideout.git
feat: HttpSignatureで署名を検証する実装を追加
This commit is contained in:
parent
0021061aa2
commit
d6fe604253
|
@ -12,4 +12,5 @@ interface UserQueryService {
|
||||||
suspend fun findByUrl(url: String): User
|
suspend fun findByUrl(url: String): User
|
||||||
suspend fun findByIds(ids: List<Long>): List<User>
|
suspend fun findByIds(ids: List<Long>): List<User>
|
||||||
suspend fun existByNameAndDomain(name: String, domain: String): Boolean
|
suspend fun existByNameAndDomain(name: String, domain: String): Boolean
|
||||||
|
suspend fun findByKeyId(keyId: String): User
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,4 +44,10 @@ class UserQueryServiceImpl : UserQueryService {
|
||||||
|
|
||||||
override suspend fun existByNameAndDomain(name: String, domain: String): Boolean =
|
override suspend fun existByNameAndDomain(name: String, domain: String): Boolean =
|
||||||
Users.select { Users.name eq name and (Users.domain eq domain) }.empty().not()
|
Users.select { Users.name eq name and (Users.domain eq domain) }.empty().not()
|
||||||
|
|
||||||
|
override suspend fun findByKeyId(keyId: String): User {
|
||||||
|
return Users.select { Users.keyId eq keyId }
|
||||||
|
.singleOr { FailedToGetResourcesException("keyId: $keyId is duplicate or does not exist.", it) }
|
||||||
|
.toUser()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,49 @@
|
||||||
package dev.usbharu.hideout.service.signature
|
package dev.usbharu.hideout.service.signature
|
||||||
|
|
||||||
import jakarta.servlet.Filter
|
import dev.usbharu.httpsignature.common.HttpHeaders
|
||||||
import jakarta.servlet.FilterChain
|
import dev.usbharu.httpsignature.common.HttpMethod
|
||||||
import jakarta.servlet.ServletRequest
|
import dev.usbharu.httpsignature.common.HttpRequest
|
||||||
import jakarta.servlet.ServletResponse
|
import dev.usbharu.httpsignature.verify.SignatureHeaderParser
|
||||||
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
|
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
class HttpSignatureFilter : Filter {
|
class HttpSignatureFilter(private val httpSignatureHeaderParser: SignatureHeaderParser) :
|
||||||
override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
|
AbstractPreAuthenticatedProcessingFilter() {
|
||||||
chain.doFilter(request, response)
|
override fun getPreAuthenticatedPrincipal(request: HttpServletRequest?): Any {
|
||||||
|
|
||||||
|
val headersList = request?.headerNames?.toList().orEmpty()
|
||||||
|
|
||||||
|
val headers =
|
||||||
|
headersList.associateWith { header -> request?.getHeaders(header)?.toList().orEmpty() }
|
||||||
|
|
||||||
|
|
||||||
|
val signature = httpSignatureHeaderParser.parse(HttpHeaders(headers))
|
||||||
|
return signature.keyId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getPreAuthenticatedCredentials(request: HttpServletRequest?): Any {
|
||||||
|
requireNotNull(request)
|
||||||
|
val url = request.requestURL.toString()
|
||||||
|
|
||||||
|
val headersList = request.headerNames?.toList().orEmpty()
|
||||||
|
|
||||||
|
val headers =
|
||||||
|
headersList.associateWith { header -> request.getHeaders(header)?.toList().orEmpty() }
|
||||||
|
|
||||||
|
val method = when (val method = request.method.lowercase()) {
|
||||||
|
"get" -> HttpMethod.GET
|
||||||
|
"post" -> HttpMethod.POST
|
||||||
|
else -> {
|
||||||
|
throw IllegalArgumentException("Unsupported method: $method")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return HttpRequest(
|
||||||
|
URL(url + request.queryString),
|
||||||
|
HttpHeaders(headers),
|
||||||
|
method
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package dev.usbharu.hideout.service.signature
|
||||||
|
|
||||||
|
import org.springframework.security.core.GrantedAuthority
|
||||||
|
import org.springframework.security.core.userdetails.User
|
||||||
|
import java.io.Serial
|
||||||
|
|
||||||
|
class HttpSignatureUser(
|
||||||
|
username: String,
|
||||||
|
val domain: String,
|
||||||
|
credentialsNonExpired: Boolean,
|
||||||
|
accountNonLocked: Boolean,
|
||||||
|
authorities: MutableCollection<out GrantedAuthority>?
|
||||||
|
) : User(
|
||||||
|
username,
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
credentialsNonExpired,
|
||||||
|
accountNonLocked,
|
||||||
|
authorities
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
@Serial
|
||||||
|
private const val serialVersionUID: Long = -3330552099960982997L
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
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.util.RsaUtil
|
||||||
|
import dev.usbharu.httpsignature.common.HttpRequest
|
||||||
|
import dev.usbharu.httpsignature.common.PublicKey
|
||||||
|
import dev.usbharu.httpsignature.verify.FailedVerification
|
||||||
|
import dev.usbharu.httpsignature.verify.HttpSignatureVerifier
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException
|
||||||
|
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken
|
||||||
|
|
||||||
|
class HttpSignatureUserDetailsService(
|
||||||
|
private val userQueryService: UserQueryService,
|
||||||
|
private val httpSignatureVerifier: HttpSignatureVerifier
|
||||||
|
) :
|
||||||
|
AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package dev.usbharu.hideout.service.signature
|
||||||
|
|
||||||
|
import dev.usbharu.httpsignature.common.HttpRequest
|
||||||
|
import dev.usbharu.httpsignature.common.PublicKey
|
||||||
|
import dev.usbharu.httpsignature.verify.HttpSignatureVerifier
|
||||||
|
import dev.usbharu.httpsignature.verify.SignatureHeaderParser
|
||||||
|
import dev.usbharu.httpsignature.verify.VerificationResult
|
||||||
|
|
||||||
|
class HttpSignatureVerifierComposite(
|
||||||
|
private val map: Map<String, HttpSignatureVerifier>,
|
||||||
|
private val httpSignatureHeaderParser: SignatureHeaderParser
|
||||||
|
) : HttpSignatureVerifier {
|
||||||
|
override fun verify(httpRequest: HttpRequest, key: PublicKey): VerificationResult {
|
||||||
|
val signature = httpSignatureHeaderParser.parse(httpRequest.headers)
|
||||||
|
val verify = map[signature.algorithm]?.verify(httpRequest, key)
|
||||||
|
if (verify != null) {
|
||||||
|
return verify
|
||||||
|
}
|
||||||
|
|
||||||
|
throw IllegalArgumentException("Unsupported algorithm. ${signature.algorithm}")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue