API 認証への JWT・OAuth 2.0 の追加
プリザンターの API に Authorization: Bearer <JWT> での認証を追加する場合の検討です。既存の API キー認証を残したまま、認証スキームを追加する形を想定します。
本体の標準機能ではありません
1.5.8.1 のプリザンターは JWT Bearer 認証に対応していません。以下は現行の実装(1.5.8.1)を前提にした改修案です。
前提にした現行の実装
詳細は 認証基盤(Cookie・API キー・Bearer) にあります。
- 認証スキームは Cookie(SAML のときは Saml2 も)だけで、
AddJwtBearerはありません(Startup.cs)。 - API キーは本文の JSON の
ApiKeyで送り、Context.SetUserPropertiesがUsers.ApiKeyと照合します。ApiKeyが無ければ Cookie のログイン ID を使います。 - API コントローラーは
[AllowAnonymous]と[CheckApiContextAttributes]を付け、アクションの中でcontext.Authenticatedを確かめます。
現行の API キーには次の制約があります。
| 制約 | 内容 |
|---|---|
| 有効期限が無い | 削除・再発行するまで有効 |
| スコープが無い | ユーザーの権限すべてを使える |
| 送る場所が本文 | Authorization ヘッダーで送れるのはファイルのアップロード API だけ |
| ローテーションの仕組みが無い | 再発行は本人が画面で行う |
| 外部 IdP を使えない | SAML / LDAP の認証は画面ログイン用で、API には使えない |
| 監査 | API キーの発行・失効の履歴は残らない |
方式 1: プリザンターが JWT を発行する
API キーでトークン発行エンドポイントを呼び、短い有効期限の JWT を受け取って、以後はそれを Authorization: Bearer で送る方式です。
図を読み込み中…
改修箇所
| ファイル | 変更内容 |
|---|---|
Implem.ParameterAccessor/Parts/Authentication.cs | JwtBearerParameters を追加 |
Implem.ParameterAccessor/Parts/JwtBearer.cs(新規) | Enabled・Issuer・Audience・署名鍵(共有鍵または証明書)・アクセストークンの有効期間・リフレッシュトークンの有効期間など |
App_Data/Parameters/Authentication.json | 上記の既定値(Enabled: false) |
Startup.cs | Enabled のときだけ AddJwtBearer を登録。既定のスキームは Cookie のまま |
Controllers/Api/AuthController.cs(新規) | トークン発行。[AllowAnonymous]・[CheckApiContextAttributes] を付け、context.Authenticated なら JWT を返す |
Libraries/Security/JwtTokenService.cs(新規) | クレーム(NameIdentifier に UserId、Name に LoginId、TenantId、DeptId)と有効期限を入れて署名 |
Libraries/Requests/Context.cs | SetUserProperties に、API キーが無く JWT で認証済みならクレームの UserId で GetUser する分岐を追加 |
Filters/CheckApiContextAttributes.cs | JWT で認証したリクエストの扱い(CSRF トークンの確認の要否)を整理 |
Startup.cs と Context.cs の改修イメージ
// Startup.cs: Cookie のスキーム登録の後に追加
if (Parameters.Authentication.JwtBearerParameters?.Enabled == true)
{
var jwt = Parameters.Authentication.JwtBearerParameters;
authBuilder.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = jwt.Issuer,
ValidateAudience = true,
ValidAudience = jwt.Audience,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = GetSigningKey(jwt),
ClockSkew = TimeSpan.FromMinutes(1)
};
});
}
// Context.SetUserProperties: ApiKey の分岐と LoginId の分岐の間に追加
else if (IsJwtAuthenticated())
{
var userId = GetUserIdFromJwtClaims(); // ClaimTypes.NameIdentifier
if (userId > 0)
{
SetUser(userModel: GetUser(where: Rds.UsersWhere().UserId(userId)));
}
}GetUser を通すため、無効化・ロックアウトされたユーザーは JWT の有効期限内でも認証されません。
パッケージ
AddJwtBearer は NuGet パッケージ Microsoft.AspNetCore.Authentication.JwtBearer に含まれ、ASP.NET Core の共有フレームワーク(Microsoft.AspNetCore.App)には入っていません。1.5.8.1 の Implem.Pleasanter.csproj には参照が無いため、追加が必要です。
方式 2: 外部 IdP が発行した JWT を検証する
Microsoft Entra ID・Keycloak・Auth0 などの IdP が発行したアクセストークンを、プリザンターは検証だけする方式です。機械間の連携なら Client Credentials Grant、ユーザーの操作なら Authorization Code Grant + PKCE でトークンを取得します。
図を読み込み中…
改修箇所
Implem.ParameterAccessor/Parts/OAuthProvider.cs(新規):Enabled・Authority・Audience・ClientId・MetadataAddress・ユーザーを特定するクレーム名・LoginId に対応付けるクレーム名・未登録ユーザーを自動作成するか。Authentication.csには複数の IdP を持てるよう配列で追加する。Startup.cs: 有効な IdP ごとにAddJwtBearer(AuthorityとAudienceを指定)を登録し、Cookie と JWT のどちらでも認可を通すポリシーにする。Context.cs: クレームのログイン ID で Users を検索する分岐。未登録ユーザーの自動作成をするなら、SAML のSaml.UpdateOrInsertと同じような同期処理が要る。
パラメータの例(Microsoft Entra ID、値はダミー)
{
"OAuthProviders": [
{
"Enabled": true,
"Authority": "https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0",
"Audience": "api://pleasanter-api",
"ClientId": "11111111-1111-1111-1111-111111111111",
"MetadataAddress": "https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0/.well-known/openid-configuration",
"UserIdClaim": "oid",
"LoginIdClaim": "preferred_username",
"AutoProvision": false
}
]
}方式の比較
| 観点 | 方式 1(自前で発行) | 方式 2(外部 IdP) |
|---|---|---|
| トークンの発行 | プリザンター | 外部 IdP |
| ユーザー管理 | プリザンターの Users | IdP + Users への対応付け |
| 既存の API キー | 併用できる | 併用できる |
| スコープ | 自前で実装 | IdP 側で設定できる |
| 多要素認証 | 別途実装 | IdP が提供 |
| 実装の規模 | 中(発行側を作る) | 小〜中(検証と対応付け) |
| 運用 | 署名鍵・リフレッシュトークンの管理 | IdP の運用に依存 |
| 機械間の連携 | API キーを JWT に交換 | Client Credentials Grant |
段階的な導入
図を読み込み中…
| 段階 | 内容 |
|---|---|
| 1 | 方式 1 の基本。トークン発行・検証・Context への組み込み |
| 2 | リフレッシュトークンを保存するテーブルを追加し、ローテーションと失効を実装 |
| 3 | 方式 2 の IdP 連携とユーザーの対応付け |
| 4 | クレームによる操作範囲の制限、トークン操作の SysLogs への記録 |
既存の仕組みへの影響は次のとおりです。
| 箇所 | 影響 |
|---|---|
| API キー認証 | なし(分岐の順番で API キーを優先したまま) |
| Cookie 認証・SAML | なし(既定のスキームは Cookie のまま、JWT は追加のスキーム) |
CheckApiContextAttributes | JWT のときの CSRF トークンの扱いを決める必要がある |
| 各 API のアクション | なし(context.Authenticated の判定は Context 側で吸収) |
セキュリティ上の注意
| 項目 | 内容 |
|---|---|
| 署名アルゴリズム | HS256(共有鍵)は発行側と検証側が同じ鍵を持つため、漏えい時の影響が大きい。本番は RS256 / ES256 などの非対称鍵にする |
| アクセストークンの有効期限 | 短く(60 分以下)する |
| リフレッシュトークン | DB(または Redis)に保存して失効を管理し、使ったら新しいものに交換する |
対象(aud) | 厳密に検証し、別サービス向けのトークンを受け付けない |
| リプレイ | 必要なら jti で 1 回限りの使用に制限する |
| 署名鍵 | ローテーションの手順を用意し、Azure Key Vault などの鍵管理サービスも検討する |