Authenticating Clients

预计阅读时间: 7 分钟

Ktor supports authentication out of the box as a standard pluggable feature. It supports mechanisms to read credentials, and to authenticate principals.

It can be used in some cases along with the sessions feature to keep the login information between requests.

Table of contents:

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

Mechanisms

Basic usage

Ktor defines two concepts: credentials and principals.

  • A principal is something that can be authenticated: a user, a computer, a group, etc.
  • A credential is an object that represents a set of properties for the server to authenticate a principal: a user/password, an API key or an authenticated payload signature, etc.

To install it, you have to call to application.install(Authentication). You have to install this feature directly to the application and it won’t work in another ApplicationCallPipeline like Route.

You might still be able to call the install code inside a Route if you have the Application injected in a nested DSL, but it will be applied to the application itself.

Using its DSL, it allows you to configure the authentication providers available:

install(Authentication) {
    basic(name = "myauth1") {
        realm = "Ktor Server"
        validate { credentials ->
            if (credentials.name == credentials.password) {
                UserIdPrincipal(credentials.name)
            } else {
                null
            }
        }
    }
}

After defining one or more authentication providers (named or unnamed), with the routing feature you can create a route group, that will apply that authentication to all the routes defined in that group:

routing {
    authenticate("myauth1") {
        get("/authenticated/route1") {
            // ...
        }    
        get("/other/route2") {
            // ...
        }    
    }
    get("/") {
        // ...
    }
}

You can specify several names to apply several authentication providers, or none or null to use the unnamed one.

You can get the generated Principal instance inside your handler with:

val principal: UserIdPrincipal? = call.authentication.principal<UserIdPrincipal>()

In the generic, you have to put a specific type that must match the generated Principal. It will return null in the case you provide another type.

The handler won’t be executed if the configured authentication fails (when returning null in the authentication mechanism)

Naming the AuthenticationProvider

It is possible to give arbitrary names to the authentication providers you specify, or to not provide a name at all (unnamed provider) by not setting the name argument or passing a null.

You cannot repeat authentication provider names, and you can define just one provider without a name.

In the case you repeat a name for the provider or try to define two unnamed providers, an exception will be thrown:

java.lang.IllegalArgumentException: Provider with the name `authName` is already registered

Summarizing:

install(Authentication) {
    basic { // Unamed `basic` provider
        // ...
    }
    form { // Unamed `form` provider (exception, already defined a provider with name = null) 
        // ...
    }
    basic("name1") { // "name1" provider
        // ...
    }
    basic("name1") { // "name1" provider (exception, already defined a provider with name = "name1")
        // ...
    }
}

Skipping/Omitting Authentication providers

You can also skip an authentication based on a criteria.

/**
 * Authentication filters specifying if authentication is required for particular [ApplicationCall]
 * If there is no filters, authentication is required. If any filter returns true, authentication is not required.
 */
fun AuthenticationProvider.skipWhen(predicate: (ApplicationCall) -> Boolean)

For example, to skip a basic authentication if there is already a session, you could write:

authentication {
    basic {
        skipWhen { call -> call.sessions.get<UserSession>() != null }
    }
}

Advanced

If you want to create custom authentication strategies, you can check the Authentication feature as a reference.

The authentication feature defines two stages as part of its Pipeline: RequestAuthentication and CheckAuthentication.