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 findByIds(ids: List<Long>): List<User>
|
||||
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 =
|
||||
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
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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