Ktor Serverにおける認証と認可
必要な依存関係: io.ktor:ktor-server-auth
Ktorは、認証と認可を処理するために Authentication プラグインを提供しています。一般的な使用シナリオには、ユーザーのログイン、特定のリソースへのアクセスの許可、関係者間での情報の安全な送信が含まれます。また、Authentication を セッション と併用して、リクエスト間でユーザー情報を保持することもできます。
クライアント側では、Ktorは認証と認可を処理するための Authentication プラグインを提供しています。
サポートされている認証タイプ
Ktorは、以下の認証および認可スキームをサポートしています。
HTTP認証
HTTPは、アクセス制御と認証のための 一般的なフレームワーク を提供しています。Ktorでは、以下のHTTP認証スキームを使用できます。
- Basic -
Base64エンコードを使用してユーザー名とパスワードを提供します。HTTPSと組み合わせて使用しない場合は、一般的に推奨されません。 - Digest - ユーザー名とパスワードにハッシュ関数を適用することで、暗号化された形式でユーザーの資格情報を通信する認証方法です。
- Bearer - ベアラートークンと呼ばれるセキュリティトークンを含む認証スキームです。 Bearer認証スキームは OAuth や JWT の一部として使用されますが、ベアラートークンの認可にカスタムロジックを提供することもできます。
- API Key - クライアントがヘッダーで秘密鍵を渡すシンプルな認証方法です。
フォームベース認証
フォームベース認証は、Webフォームを使用して資格情報を収集し、ユーザーを認証します。
JSON Web Tokens (JWT)
JSON Web Token は、関係者間で情報をJSONオブジェクトとして安全に送信するためのオープン標準です。認可にJSON Web Tokenを使用できます。ユーザーがログインすると、各リクエストにトークンが含まれ、そのトークンで許可されているリソースにユーザーがアクセスできるようになります。Ktorでは、jwt 認証を使用して、トークンの検証とその中に含まれるクレームの妥当性確認を行うことができます。
LDAP
LDAP は、ディレクトリサービス認証に使用されるオープンでクロスプラットフォームなプロトコルです。Ktorは、指定されたLDAPサーバーに対してユーザー資格情報を認証するための ldapAuthenticate 関数を提供しています。
OAuth
OAuth は、APIへのアクセスを保護するためのオープン標準です。Ktorの oauth プロバイダーを使用すると、Google、Facebook、Twitterなどの外部プロバイダーを使用した認証を実装できます。
セッション
セッション は、異なるHTTPリクエスト間でデータを永続化するためのメカニズムを提供します。一般的なユースケースには、ログインしたユーザーのID、ショッピングバスケットの内容の保存、またはクライアントでのユーザー設定の保持が含まれます。Ktorでは、既に関連付けられたセッションを持つユーザーを session プロバイダーを使用して認証できます。詳細については、Ktor Serverでのセッション認証 を参照してください。
カスタム
Ktorは、認証と認可の動作をカスタマイズするための2つの方法を提供しています。
- カスタム認証プロバイダー を使用する。
- カスタムプラグイン を使用して認可ロジックを実装する。例えば、
AuthenticationCheckedフック を使用してアクセスを検証できます。詳細については、custom-plugin-authorization の例を参照してください。
依存関係の追加
Authentication を使用するには、ビルドスクリプトに ktor-server-auth アーティファクトを含める必要があります。
JWT や LDAP など、一部の認証プロバイダーには追加のアーティファクトが必要であることに注意してください。
Authenticationのインストール
Authentication プラグインをアプリケーションに インストール するには、指定された
install 関数に渡します。 以下のコードスニペットは、Authentication のインストール方法を示しています... - ...
embeddedServer関数の呼び出し内。 - ...
Applicationクラスの拡張関数である明示的に定義されたmodule内。
Authenticationの設定
Authenticationのインストール 後、次のように Authentication を設定して使用できます。
ステップ1: 認証プロバイダーを選択する
basic、digest、または form などの特定の認証プロバイダーを使用するには、install ブロック内で対応する関数を呼び出す必要があります。例えば、Basic認証を使用するには、.basic() 関数を呼び出します。
import io.ktor.server.application.*
import io.ktor.server.auth.*
// ...
install(Authentication) {
basic {
// Basic認証を設定する
}
}この関数内で、このプロバイダーに固有の設定を 行う ことができます。
組み込みのプロバイダーが要件に合わない場合は、カスタム認証プロバイダー を実装できます。
ステップ2: プロバイダー名を指定する
特定のプロバイダーを使用するための関数 では、オプションでプロバイダー名を指定できます。以下のコードサンプルは、basic プロバイダーと form プロバイダーを、それぞれ "auth-basic" と "auth-form" という名前でインストールします。
install(Authentication) {
basic("auth-basic") {
// Basic認証を設定する
}
form("auth-form") {
// フォーム認証を設定する
}
// ...
}これらの名前は、後で異なるプロバイダーを使用して 異なるルートを認証 するために使用できます。
プロバイダー名は一意である必要があり、名前のないプロバイダーは1つしか定義できないことに注意してください。
ステップ3: プロバイダーを設定する
各 プロバイダータイプ には独自の設定があります。例えば、BasicAuthenticationProvider.Config クラスは、.basic() 関数にオプションを提供します。このクラスの主要な関数は validate() で、ユーザー名とパスワードの検証を担当します。次のコード例は、その使用方法を示しています。
install(Authentication) {
basic("auth-basic") {
realm = "Access to the '/' path"
validate { credentials ->
if (credentials.name == "jetbrains" && credentials.password == "foobar") {
UserIdPrincipal(credentials.name)
} else {
null
}
}
}
}validate() 関数の仕組みを理解するために、2つの用語を導入する必要があります。
- プリンシパル (Principal) は、認証可能なエンティティです。ユーザー、コンピューター、サービスなどが該当します。Ktorでは、認証プロバイダーごとに異なるプリンシパルを使用する場合があります。例えば、
basic、digest、formプロバイダーはUserIdPrincipalを認証し、jwtプロバイダーはJWTPrincipalを検証します。カスタムプリンシパルを作成することもできます。これは以下のような場合に役立ちます。
- クレデンシャル (Credential) は、サーバーがプリンシパルを認証するためのプロパティのセットです。ユーザー名とパスワードのペア、APIキーなどが該当します。例えば、
basicプロバイダーとformプロバイダーは、ユーザー名とパスワードを検証するためにUserPasswordCredentialを使用し、jwtはJWTCredentialを検証します。
つまり、validate() 関数は指定されたクレデンシャルをチェックし、認証に成功した場合はプリンシパル Any を返し、失敗した場合は null を返します。
特定の条件に基づいて認証をスキップするには、
skipWhen()を使用します。 例えば、セッション が既に存在する場合にbasic認証をスキップできます。kotlinbasic { skipWhen { call -> call.sessions.get<UserSession>() != null } }
### ステップ4: 特定のリソースを保護する {id="authenticate-route"}
最後のステップは、アプリケーション内の特定のリソースを保護することです。これを行うには、[`authenticate()`](https://api.ktor.io/ktor-server-auth/io.ktor.server.auth/authenticate.html) 関数を使用します。この関数は、2つのオプションパラメータを受け取ります。
- ネストされたルートを認証するために使用される [プロバイダーの名前](#provider-name)。
以下のコードスニペットでは、`auth-basic` という名前のプロバイダーを使用して、`/login` ルートと `/orders` ルートを保護しています。
```kotlin
routing {
authenticate("auth-basic") {
get("/login") {
// ...
}
get("/orders") {
// ...
}
}
get("/") {
// ...
}
}ネストされた認証プロバイダーを解決するために使用される戦略。 この戦略は、
AuthenticationStrategy列挙値によって表されます。例えば、
AuthenticationStrategy.Required戦略で登録されたすべてのプロバイダーに対して、クライアントは認証データを提供する必要があります。 以下のコードスニペットでは、セッション認証 に合格したユーザーのみが、Basic認証を使用して/adminルートへのアクセスを試みることができます。kotlinrouting { authenticate("auth-session", strategy = AuthenticationStrategy.Required) { get("/hello") { // ... } authenticate("auth-basic", strategy = AuthenticationStrategy.Required) { get("/admin") { // ... } } } }
完全な例については、auth-form-session-nested を参照してください。
ステップ5: ルートハンドラー内でプリンシパルを取得する
認証に成功すると、call.principal() 関数を使用してルートハンドラー内で認証されたプリンシパルを取得できます。この関数は、設定された認証プロバイダー によって返される特定のプリンシパル型を受け入れます。次の例では、call.principal() を使用して UserIdPrincipal を取得し、認証されたユーザーの名前を取得しています。
routing {
authenticate("auth-basic") {
get("/") {
call.respondText("Hello, ${call.principal<UserIdPrincipal>()?.name}!")
}
}
}セッション認証 を使用する場合、プリンシパルはセッションデータを格納するデータクラスになることがあります。そのため、このデータクラスを call.principal() に渡す必要があります。
authenticate("auth-session") {
get("/hello") {
val userSession = call.principal<UserSession>()
}
}ネストされた認証プロバイダー の場合、プロバイダー名 を call.principal() に渡すことで、目的のプロバイダーのプリンシパルを取得できます。
以下の例では、最上位のセッションプロバイダーのプリンシパルを取得するために "auth-session" 値が渡されています。
authenticate("auth-session", strategy = AuthenticationStrategy.Required) {
authenticate("auth-basic", strategy = AuthenticationStrategy.Required) {
get("/admin") {
val userSession = call.principal<UserSession>("auth-session")
}
}
}カスタム認証プロバイダー
組み込みのプロバイダーが要件に合わない場合は、provider() 関数を使用してカスタム認証ロジックを実装します。
provider("custom") {
authenticate { context ->
val exampleHeader = context.call.request.headers["Example-Header"]
if (exampleHeader == null) {
val cause = AuthenticationFailedCause.Error("No example header found")
context.challenge(key = this, cause) { challenge, call ->
call.respondText("Challenge")
challenge.complete()
}
}
}
}上の例では、provider("custom") 関数が名前付き認証プロバイダーを登録しており、これは後で authenticate("custom") を使用してルートに適用できます。
プロバイダー内では、リクエストごとに authenticate {} ブロックが実行され、コンテキストオブジェクトを通じて認証プロセスを完全に制御できます。これには、現在のコール (context.call) へのアクセスや、ヘッダー、パラメータ、その他のリクエストデータの検査が含まれます。
DynamicProviderConfig クラスによって提供されるオプションを使用して、追加のプロバイダー動作を設定できます。
