feat: HttpSignatureで署名を検証する実装を追加

This commit is contained in:
usbharu 2023-10-20 11:05:02 +09:00
parent 0021061aa2
commit d6fe604253
6 changed files with 154 additions and 7 deletions

View File

@ -12,4 +12,5 @@ interface UserQueryService {
suspend fun findByUrl(url: String): User
suspend fun findByIds(ids: List<Long>): List<User>
suspend fun existByNameAndDomain(name: String, domain: String): Boolean
suspend fun findByKeyId(keyId: String): User
}

View File

@ -44,4 +44,10 @@ class UserQueryServiceImpl : UserQueryService {
override suspend fun existByNameAndDomain(name: String, domain: String): Boolean =
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()
}
}

View File

@ -1,12 +1,49 @@
package dev.usbharu.hideout.service.signature
import jakarta.servlet.Filter
import jakarta.servlet.FilterChain
import jakarta.servlet.ServletRequest
import jakarta.servlet.ServletResponse
import dev.usbharu.httpsignature.common.HttpHeaders
import dev.usbharu.httpsignature.common.HttpMethod
import dev.usbharu.httpsignature.common.HttpRequest
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 {
override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
chain.doFilter(request, response)
class HttpSignatureFilter(private val httpSignatureHeaderParser: SignatureHeaderParser) :
AbstractPreAuthenticatedProcessingFilter() {
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
)
}
}

View File

@ -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
}
}

View File

@ -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()
)
}
}

View File

@ -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}")
}
}