test: リフレッシュトークンのテストを追加

This commit is contained in:
usbharu 2023-05-03 15:32:13 +09:00
parent c50f8a0594
commit 2734ea3ffc
2 changed files with 288 additions and 25 deletions

View File

@ -35,7 +35,7 @@ fun Application.configureSecurity(
acceptLeeway(3)
}
validate { jwtCredential ->
if (jwtCredential.payload.getClaim("username").asString().isNotEmpty()) {
if (jwtCredential.payload.getClaim("username")?.asString().isNullOrBlank().not()) {
JWTPrincipal(jwtCredential.payload)
} else {
null

View File

@ -11,7 +11,9 @@ import dev.usbharu.hideout.config.ConfigData
import dev.usbharu.hideout.domain.model.hideout.dto.JwtToken
import dev.usbharu.hideout.domain.model.hideout.entity.Jwt
import dev.usbharu.hideout.domain.model.hideout.entity.User
import dev.usbharu.hideout.domain.model.hideout.form.RefreshToken
import dev.usbharu.hideout.domain.model.hideout.form.UserLogin
import dev.usbharu.hideout.exception.InvalidRefreshTokenException
import dev.usbharu.hideout.repository.IUserRepository
import dev.usbharu.hideout.service.IJwtService
import dev.usbharu.hideout.service.IMetaService
@ -21,17 +23,11 @@ import dev.usbharu.hideout.util.JsonWebKeyUtil
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.config.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.testing.*
import org.junit.jupiter.api.Test
import org.mockito.ArgumentMatchers.anyString
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.*
import java.security.KeyPairGenerator
import java.security.interfaces.RSAPrivateKey
import java.security.interfaces.RSAPublicKey
@ -256,21 +252,6 @@ class SecurityKtTest {
configureSerialization()
configureSecurity(mock(), metaService, userRepository, jwtService, jwkProvider)
}
externalServices {
hosts("http://localhost:8080") {
routing {
get("/.well-known/jwks.json") {
call.application.log.info("aaaaaaaaaaaaaa")
println("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
call.respondText(
contentType = ContentType.Application.Json,
text = JsonWebKeyUtil.publicKeyToJwk(rsaPublicKey, kid.toString())
)
}
}
}
}
client.get("/auth-check") {
header("Authorization", "Bearer $token")
@ -279,4 +260,286 @@ class SecurityKtTest {
assertEquals("Hello \"test\"", call.response.bodyAsText())
}
}
@Test
fun `auth-check 期限切れのトークンではアクセスできない`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(2048)
val keyPair = keyPairGenerator.generateKeyPair()
val rsaPublicKey = keyPair.public as RSAPublicKey
Config.configData = ConfigData(url = "https://localhost", objectMapper = jacksonObjectMapper())
val now = Instant.now()
val kid = UUID.randomUUID()
val token = JWT.create()
.withAudience("${Config.configData.url}/users/test")
.withIssuer(Config.configData.url)
.withKeyId(kid.toString())
.withClaim("username", "test")
.withExpiresAt(now.minus(30, ChronoUnit.MINUTES))
.sign(Algorithm.RSA256(rsaPublicKey, keyPair.private as RSAPrivateKey))
val metaService = mock<IMetaService> {
onBlocking { getJwtMeta() }.doReturn(
Jwt(
kid,
Base64Util.encode(keyPair.private.encoded),
Base64Util.encode(rsaPublicKey.encoded)
)
)
}
val readValue = Config.configData.objectMapper.readerFor(Map::class.java)
.readValue<MutableMap<String, Any>?>(
JsonWebKeyUtil.publicKeyToJwk(
rsaPublicKey,
kid.toString()
)
)
val jwkProvider = mock<JwkProvider> {
onBlocking { get(anyString()) }.doReturn(
Jwk.fromValues(
(readValue["keys"] as List<Map<String, Any>>)[0]
)
)
}
val userRepository = mock<IUserRepository>()
val jwtService = mock<IJwtService>()
application {
configureSerialization()
configureSecurity(mock(), metaService, userRepository, jwtService, jwkProvider)
}
client.get("/auth-check") {
header("Authorization", "Bearer $token")
}.apply {
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
}
}
@Test
fun `auth-check issuerが間違っているとアクセスできない`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(2048)
val keyPair = keyPairGenerator.generateKeyPair()
val rsaPublicKey = keyPair.public as RSAPublicKey
Config.configData = ConfigData(url = "https://localhost", objectMapper = jacksonObjectMapper())
val now = Instant.now()
val kid = UUID.randomUUID()
val token = JWT.create()
.withAudience("${Config.configData.url}/users/test")
.withIssuer("https://example.com")
.withKeyId(kid.toString())
.withClaim("username", "test")
.withExpiresAt(now.plus(30, ChronoUnit.MINUTES))
.sign(Algorithm.RSA256(rsaPublicKey, keyPair.private as RSAPrivateKey))
val metaService = mock<IMetaService> {
onBlocking { getJwtMeta() }.doReturn(
Jwt(
kid,
Base64Util.encode(keyPair.private.encoded),
Base64Util.encode(rsaPublicKey.encoded)
)
)
}
val readValue = Config.configData.objectMapper.readerFor(Map::class.java)
.readValue<MutableMap<String, Any>?>(
JsonWebKeyUtil.publicKeyToJwk(
rsaPublicKey,
kid.toString()
)
)
val jwkProvider = mock<JwkProvider> {
onBlocking { get(anyString()) }.doReturn(
Jwk.fromValues(
(readValue["keys"] as List<Map<String, Any>>)[0]
)
)
}
val userRepository = mock<IUserRepository>()
val jwtService = mock<IJwtService>()
application {
configureSerialization()
configureSecurity(mock(), metaService, userRepository, jwtService, jwkProvider)
}
client.get("/auth-check") {
header("Authorization", "Bearer $token")
}.apply {
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
}
}
@Test
fun `auth-check usernameが空だと失敗する`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(2048)
val keyPair = keyPairGenerator.generateKeyPair()
val rsaPublicKey = keyPair.public as RSAPublicKey
Config.configData = ConfigData(url = "https://localhost", objectMapper = jacksonObjectMapper())
val now = Instant.now()
val kid = UUID.randomUUID()
val token = JWT.create()
.withAudience("${Config.configData.url}/users/test")
.withIssuer(Config.configData.url)
.withKeyId(kid.toString())
.withClaim("username", "")
.withExpiresAt(now.plus(30, ChronoUnit.MINUTES))
.sign(Algorithm.RSA256(rsaPublicKey, keyPair.private as RSAPrivateKey))
val metaService = mock<IMetaService> {
onBlocking { getJwtMeta() }.doReturn(
Jwt(
kid,
Base64Util.encode(keyPair.private.encoded),
Base64Util.encode(rsaPublicKey.encoded)
)
)
}
val readValue = Config.configData.objectMapper.readerFor(Map::class.java)
.readValue<MutableMap<String, Any>?>(
JsonWebKeyUtil.publicKeyToJwk(
rsaPublicKey,
kid.toString()
)
)
val jwkProvider = mock<JwkProvider> {
onBlocking { get(anyString()) }.doReturn(
Jwk.fromValues(
(readValue["keys"] as List<Map<String, Any>>)[0]
)
)
}
val userRepository = mock<IUserRepository>()
val jwtService = mock<IJwtService>()
application {
configureSerialization()
configureSecurity(mock(), metaService, userRepository, jwtService, jwkProvider)
}
client.get("/auth-check") {
header("Authorization", "Bearer $token")
}.apply {
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
}
}
@Test
fun `auth-check usernameが存在しないと失敗する`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(2048)
val keyPair = keyPairGenerator.generateKeyPair()
val rsaPublicKey = keyPair.public as RSAPublicKey
Config.configData = ConfigData(url = "https://localhost", objectMapper = jacksonObjectMapper())
val now = Instant.now()
val kid = UUID.randomUUID()
val token = JWT.create()
.withAudience("${Config.configData.url}/users/test")
.withIssuer(Config.configData.url)
.withKeyId(kid.toString())
.withExpiresAt(now.plus(30, ChronoUnit.MINUTES))
.sign(Algorithm.RSA256(rsaPublicKey, keyPair.private as RSAPrivateKey))
val metaService = mock<IMetaService> {
onBlocking { getJwtMeta() }.doReturn(
Jwt(
kid,
Base64Util.encode(keyPair.private.encoded),
Base64Util.encode(rsaPublicKey.encoded)
)
)
}
val readValue = Config.configData.objectMapper.readerFor(Map::class.java)
.readValue<MutableMap<String, Any>?>(
JsonWebKeyUtil.publicKeyToJwk(
rsaPublicKey,
kid.toString()
)
)
val jwkProvider = mock<JwkProvider> {
onBlocking { get(anyString()) }.doReturn(
Jwk.fromValues(
(readValue["keys"] as List<Map<String, Any>>)[0]
)
)
}
val userRepository = mock<IUserRepository>()
val jwtService = mock<IJwtService>()
application {
configureSerialization()
configureSecurity(mock(), metaService, userRepository, jwtService, jwkProvider)
}
client.get("/auth-check") {
header("Authorization", "Bearer $token")
}.apply {
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
}
}
@Test
fun `refresh-token リフレッシュトークンが正当だとトークンを発行する`() = testApplication {
Config.configData = ConfigData(url = "https://localhost", objectMapper = jacksonObjectMapper())
environment {
config = ApplicationConfig("empty.conf")
}
val jwtService = mock<IJwtService> {
onBlocking { refreshToken(any()) }.doReturn(JwtToken("token", "refreshToken2"))
}
application {
configureSerialization()
configureSecurity(mock(), mock(), mock(), jwtService, mock())
}
client.post("/refresh-token") {
header("Content-Type", "application/json")
setBody(Config.configData.objectMapper.writeValueAsString(RefreshToken("refreshToken")))
}.apply {
assertEquals(HttpStatusCode.OK, call.response.status)
}
}
@Test
fun `refresh-token リフレッシュトークンが不正だと失敗する`() = testApplication {
Config.configData = ConfigData(url = "https://localhost", objectMapper = jacksonObjectMapper())
environment {
config = ApplicationConfig("empty.conf")
}
val jwtService = mock<IJwtService> {
onBlocking { refreshToken(any()) } doThrow InvalidRefreshTokenException("Invalid Refresh Token")
}
application {
configureStatusPages()
configureSerialization()
configureSecurity(mock(), mock(), mock(), jwtService, mock())
}
client.post("/refresh-token") {
header("Content-Type", "application/json")
setBody(Config.configData.objectMapper.writeValueAsString(RefreshToken("InvalidRefreshToken")))
}.apply {
assertEquals(HttpStatusCode.BadRequest, call.response.status)
}
}
}