Oracle Cloud Hangout Cafe Season4 #4「マイクロサービスの認証・認可とJWT」(2021年7月7日開催)
MP-JWTに含まれる標準的なクレーム(Payload)
ペイロードに含まれる標準クレームは、下表のようになっています(*: 必須項目)。
claim name | description |
---|---|
* iss | Issue Identifier: ID Token の発行者 |
* iat | Issuer At: JWT の発行時刻 |
* exp | Expiration Time: ID Token の有効期限 |
* upn | User Principal Name: End-User の識別子(Human Readable) |
sub | Subject Identifier: End-User の識別子 |
jti | JWT ID: JWT の識別子 |
aud | Audience: ID Token の発行対象で、OAuth 2.0 の client_id を含む必要がある |
groups | End-User に割り当てられているグループ名のリスト |
標準的なIDトークンとの差分としてはupn、groupsが挙げられますが、それぞれ必須というわけではなく、別のクレームを用いたり、カスタムマッパーを作成することで代用できます。
MP-JWT検証の要件
次に、MP-JWTの検証に必要な要件を記載します。
- JOSE alg ヘッダーが含まれていること
- 暗号化された MP-JWT を期待する場合には、さらに JOSE enc ヘッダーが含まれていること
- iss クレームが含み、期待通りの iss であること
- iat、exp クレームを含み、そこから計算される有効期限が有効であること
- upn、preferred_username、sub のうち少なくとも 1 つのクレームを含み、期待通りであること
MP-JWTを用いてJakarta RESTful Web ServicesアプリケーションでRBACをする
MP-JWTを用いて、Jakarta RESTful Web Services(以下、Jakarta REST)アプリケーションでRBACをするために必要な設定周りを記載します。
まずは、jakarta.ws.rs.core.Applicationを拡張したクラスに@LoginConfig(authMethod="MP-JWT")というアノテーションが必要です。具体的には以下のように実装します。
import org.eclipse.microprofile.annotation.LoginConfig; import jakarta.ws.rs.ApplicationPath; import jakarta.ws.rs.core.Application; @LoginConfig(authMethod = "MP-JWT", realmName = "TCK-MP-JWT") @ApplicationPath("/") public class TCKApplication extends Application { }
また、アプリケーションの設定ファイル(application.yaml/microprofile-config.properties)に、IssuerやJWTの検証に用いる公開鍵の所在を定義します。
# configure JWT handling mp.jwt.verify.issuer: https://server.example.com mp.jwt.verify.publickey.location: publicKey.pem
アプリケーション内でMP-JWTを使うには以下のようにします。まずは、CDIによりJsonWebToken全体のインスタンスをインジェクションするか、個別のクレームをインジェクションするか選択します。
【MP-JWT全体をインジェクションするパターン】@Path("/endp") @DenyAll @ApplicationScoped public class RolesEndpoint { @Inject private JsonWebToken jwt; // ... }【MP-JWTの一部のクレームをインジェクションするパターン】
@ApplicationScoped public class MyEndpoint { @Inject @Claim(value="exp", standard=Claims.iat) private Long timeClaim; // ... }
また、jakarta.ws.rs.core.SecurityContextでも、いくつかAPIが用意されています。
// org.eclipse.microprofile.jwt.JsonWebToken のインスタンスが返却される jakarta.ws.rs.core.SecurityContext.getUserPrincipal() // 渡した文字列が MP-JWT groups に含まれるかどうかを確認する jakarta.ws.rs.core.SecurityContext.isUserInRole(String role)
次に、Jakarta RESTのエンドポイントに対して、RBAC制御を行うためのアノテーションを紹介します。
jakarta.annotation.security.RolesAllowedは、エンドポイントのアクセスに必要なロールを記載します。MP-JWT groupsクレームに指定した文字列が少なくとも1つ含まれていれば、このエンドポイントの実行を認可します。
public class MyEndpoint { @RolesAllowed({"Admin", "Guest"}) public String greet() { return "Hello, world"; } }
また、エンドポイント全体の制御として、jakarta.annotation.security.PermitAllや jakarta.annotation.security.DenyAllといったアノテーションも存在します。
【jakarta.annotation.security.PermitAll】// MyEndpoint に定義したすべてのエンドポイントの実行を認可します @PermitAll public class MyEndpoint { public String greet1() { return "Hello, world"; } public String greet2() { return "Hello, world"; } }【jakarta.annotation.security.DenyAll】
// MyEndpoint に定義したすべてのエンドポイントの実行を認可しません @DenyAll public class MyEndpoint { public String greet1() { return "Hello, world"; } public String greet2() { return "Hello, world"; } }
実装する際には、これらのアノテーションやjakarta.ws.rs.core.SecurityContextから提供されているAPIで行うことになります。
Helidon + IDCSを用いたデモ
当日は、下図のような構成でデモを実施しました。
- IDCS(Identity Domains)
- Oracle が提供する IDaaS。今回はOP(OpenID Provider)として使用
- 今回はFIDO 2 を用いたパスワードレス認証でエンドユーザーの認証を実施
- auth
- Helidon*2で実装された RP(Relying Party)
- IDCS に対して、認可コードフローを用いて ID/アクセス トークンを取得
- event
- MP-JWT を用いた RBAC を行うための設定と実装がされたアプリケーションで、Helidon で実装されている
- 提供しているエンドポイントと各エンドポイントの実行に必要な Role は図中下部の表に記載
- Oracle Container Engine for Kubernetes
- auth/event のアプリケーションの実行環境として、Oracle Cloud Infrastructure が提供するマネージド Kubernetes サービスである OKE を使用
*2:Eclipse MicroProfile に準拠した Java のアプリケーションフレームワーク
当日のデモについては、動画リンクから参照ください。
おわりに
最後に、今回解説した技術仕様に関して、簡単にまとめておきます。
- Eclipse MicroProfile - JWT Propagation
- マイクロサービスのエンドポイント(Jakarta REST)に対して、RBAC を行うために OIDC の ID トークンに追加するクレーム(upn、groups)を定義
- OAuth 2.0
- 認可のプロトコル
- ユーザーの認証が目的なら独自実装(OAuth 認証)等はせずに OpenID Connect を使う
- OpenID Connect 1.0
- 認可 + 認証連携(ID トークン) + Profile API
- OAuth で使われる JWT なアクセストークンと ID トークンの違いを理解する