JWT and JWK authentication

预计阅读时间: 3 分钟

Ktor supports JWT (JSON Web Tokens), which is a mechanism for authenticating JSON-encoded payloads. It is useful to create stateless authenticated APIs in the standard way, since there are client libraries for it in a myriad of languages.

This feature will handle Authorization: Bearer <JWT-TOKEN>.

本特性在构件 io.ktor:ktor-auth-jwt:$ktor_version 中的 io.ktor.auth.jwt.jwt 方法中定义
dependencies { implementation "io.ktor:ktor-auth-jwt:$ktor_version" }
dependencies { implementation("io.ktor:ktor-auth-jwt:$ktor_version") }
<project> ... <dependencies> <dependency> <groupId>io.ktor</groupId> <artifactId>ktor-auth-jwt</artifactId> <version>${ktor.version}</version> <scope>compile</scope> </dependency> </dependencies> </project>

Ktor has a couple of classes to use the JWT Payload as Credential or as Principal.

class JWTCredential(val payload: Payload) : Credential
class JWTPrincipal(val payload: Payload) : Principal

Configuring server/routes:

JWT and JWK each have their own method with slightly different parameters. Both require the realm parameter, which is used in the WWW-Authenticate response header.

Using a verifier and a validator:

The verifier will use the secret to verify the signature to trust the source. You can also check the payload within validate callback to ensure everything is right and to produce a Principal.

application.conf:

jwt {
    domain = "https://jwt-provider-domain/"
    audience = "jwt-audience"
    realm = "ktor sample app"
}

JWT auth:

val jwtIssuer = environment.config.property("jwt.domain").getString()
val jwtAudience = environment.config.property("jwt.audience").getString()
val jwtRealm = environment.config.property("jwt.realm").getString()

install(Authentication) {
    jwt {
        realm = jwtRealm
        verifier(makeJwtVerifier(jwtIssuer, jwtAudience))
        validate { credential ->
            if (credential.payload.audience.contains(jwtAudience)) JWTPrincipal(credential.payload) else null
        }
    }
}

private val algorithm = Algorithm.HMAC256("secret")
private fun makeJwtVerifier(issuer: String, audience: String): JWTVerifier = JWT
        .require(algorithm)
        .withAudience(audience)
        .withIssuer(issuer)
        .build()

Using a JWK provider:

fun AuthenticationPipeline.jwtAuthentication(jwkProvider: JwkProvider, issuer: String, realm: String, validate: (JWTCredential) -> Principal?)
val jwkIssuer = "https://jwt-provider-domain/"
val jwkRealm = "ktor jwt auth test"
val jwkProvider = JwkProviderBuilder(jwkIssuer)
            .cached(10, 24, TimeUnit.HOURS)
            .rateLimited(10, 1, TimeUnit.MINUTES)
            .build()
install(Authentication) {
    jwt {
        verifier(jwkProvider, jwkIssuer)
        realm = jwkRealm
        validate { credentials ->
            if (credentials.payload.audience.contains(audience)) JWTPrincipal(credentials.payload) else null
        }
    }
}