Ktor で JWT の認証を行う
日本語の記事がなさそうだったので Ktor 1.4.0 を使って JWT 認証を行う実装を記事にしたいと思います!
調べながらだったので、認識が異なる箇所がありましたらご指摘いただけますと幸いです。
ソースコードは実際に私が作ったコードから必要箇所を抜粋しただけなので、動作しないなどもありましたらご指摘ください<(_ _)>
参考
実際動かしながら「こういうことか!」と理解した内容になります。
処理内容
今回は Ktor で JWT を使うことを目的として、次のような簡素なものにしたいと思います。
エンドポイント
post /user/auth
認証して token を返す
request
内容 | 値 | 説明 |
---|---|---|
test@example.com | ||
pass | test3r2trnmthjtyjr | パスワード |
http://127.0.0.1:8080/user/auth
-d '{ "email":"test@example.com", "pass":"test" }'
response
内容 | 値 | 説明 |
---|---|---|
access_token | token68 | 有効期限 1時間の Bearer トークン |
{
"access_token" : "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI0IiwiYXVkIjoic2hhcmVib29rbWFya3MiLCJleHAiOjE1OTg3NTg0NDd9.C-Irgo599czcbemvJ0yKywL6M8w3oh2VI0Cgala-wQA"
}
get /user/id
token を元にユーザーの ID を返す
request
内容 | 値 | 説明 |
---|---|---|
Authorization | Bearer | ヘッダに設定 |
http://127.0.0.1:8080/user/id
-H 'Authorization: Bearer XXXX'
response
内容 | 値 | 説明 |
---|---|---|
id | 10 | ユーザーの ID |
{ "id":10 }
実装
バージョン
以下のバージョンを利用しています。
- Kotlin 1.4.10
- Ktor 1.4.0
build.gradle
必要箇所だけを抜粋したつもりです。
ktor-auth
と ktor-auth-jwt
を追加するイメージです。
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'kotlin'
apply plugin: 'application'
group 'group'
version '1.0.0'
mainClassName = "group.ApplicationKt"
sourceSets {
main.kotlin.srcDirs = main.java.srcDirs = ['src']
test.kotlin.srcDirs = test.java.srcDirs = ['test']
main.resources.srcDirs = ['resources']
test.resources.srcDirs = ['testresources']
}
repositories {
mavenLocal()
jcenter()
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "io.ktor:ktor-server-netty:$ktor_version"
implementation "io.ktor:ktor-locations:$ktor_version"
implementation "io.ktor:ktor-jackson:$ktor_version"
implementation "io.ktor:ktor-auth:$ktor_version"
implementation "io.ktor:ktor-auth-jwt:$ktor_version"
implementation "ch.qos.logback:logback-classic:$logback_version"
implementation "org.koin:koin-ktor:$koin_version"
testImplementation "io.ktor:ktor-server-tests:$ktor_version"
}
kotlin.experimental.coroutines = 'enable'
task stage {
dependsOn assemble
}
Application.kt
最終的には定数や Router など各ファイルに分かれると思いますが、1ファイルで全体を見通せた方が分かりやすいかと思ったので 1ファイルにまとめています。
val algorithm = Algorithm.HMAC256("secret 的なもの")
val issuer = "ISSUER"
val audience = "audience"
val userId = "user_id"
fun Application.module() {
install(Authentication) {
jwt {
realm = javaClass.packageName
verifier(
JWT.require(algorithm)
.withAudience(audience)
.withIssuer(issuer)
.build()
)
validate {
it.payload.getClaim(userId).let { claim ->
if (!claim.isNull) {
AuthUser(claim.asInt())
} else {
null
}
}
}
}
}
routing {
route("/user") {
post("/auth") {
call.receive<UserRequest>().let {
call.respond(transaction {
// DB の ID と pass を確認
val id = 10 // と取れたと仮定
AuthResponse(
JWT.create()
.withAudience(audience)
.withExpiresAt(Date.from(LocalDateTime.now().plusHours(1).toInstant(ZoneOffset.UTC))) // 有効期限
.withClaim(userId, id)
.withIssuer(issuer)
.sign(algorithm)
)
})
}
}
authenticate {
get("/id") {
call.respond(UserResponse(call.principal<AuthUser>()!!.id))
}
}
}
}
}
data class AuthUser(val id: Int): Principal
data class UserRequest(
val email: String,
val pass: String
)
data class AuthResponse(
@JsonProperty("access_token") val accessToken: String
)
data class UserResponse(
val id: Int
)
fun main(args: Array<String>) {
embeddedServer(Netty, commandLineEnvironment(args)).start()
}
ポイント
Application.kt の中で、個人的にここを抑えておけばよさそう…と思うポイントを上げていきます。
install(Authentication) {
Ktor らしい「認証機能を使うよ」としている箇所です。
その中で jwt をどう扱うかを設定してあげます。
ktor-auth
を dependencies に含めてあげることで扱えるようになります。
今回は jwt なので、その中で必要情報を設定していきます。
validate {
認証(ヘッダに設定した情報を解析)した結果はこの validate
内で payload から取得できます。
ここではその結果を Principal型に変換して返してあげることで router内の call.principal
で取得できるようになります。
参考ページにもあるように、実際は audience 等もチェックした方がよいかと思います。
authenticate {
この中に設定された router では、上記の Principal型に変換された値を call.principal
にて取得できます。
/user/auth
のように認証しなくてよいページは authenticate の外側に配置することで処理を通らなくなります。
data class AuthUser(val id: Int): Principal
上記で出てきている Principal型
です。
継承することで利用できるようになります。
全体を通して…
全体のイメージは掴めたでしょうか?
validate
と authenticate
のあたりの動きが掴めるとすぐに理解できるかと思います。
以前は intercept
を使い自身で認証機能を割り込ませる必要がありましたが、機能が提供されたことで簡単に利用できるようになっています。
また、今回は JWT ですが OAuth なども同様に実装者が内部を意識することなく手軽に使えそうに感じました!
Ktor について
PHP の Slim や Ruby の Sinatra といったような軽量系が好きな私としては、とても使いやすい FW に感じています!
ちょっとした API サーバーなどにいかがでしょうか??
Springboot もですが、Kotlin でサーバーが書けますよ〜
Discussion