Flex Gateway新着情報
Governance新着情報
Monitoring API Manager| Flex Gateway ポリシー開発キット (PDK) の JWT ライブラリを使用したポリシープロジェクトの例は、 「JWT Validation Policy Example (JWT 検証ポリシーの例)」を参照してください。 |
JSON Web Token (JWT) は、2 者間で転送されるクレームを表す URL セキュアな方法です。JWT トークンには、JSON Web Signature (JWS) のペイロードとして、またはプレーンテキストの JSON Web Encryption (JWE) 構造体として JSON オブジェクトでエンコードされたクレームが含まれており、これによりクレームはデジタル署名され、メッセージ認証コード (MAC) で保護されます。トークンは署名されるので、その情報と情報源を信頼できます。
PDK の JWT ライブラリには、JWT シグネチャーを検証し、すべての受信要求のクレームを抽出して検証するためのツールセットが用意されています。
PDK は、認証ヘッダーにある Bearer <token> という形式の JWT トークンを抽出するための bearer 関数を持つ TokenProvider 構造体を提供しています。次の例は、bearer 関数の使い方を示しています。
// [...]
use pdk::jwt::*;
// [...]
async fn filter(
state: RequestState,
) -> Flow<()> {
let headers_state = state.into_headers_state().await;
// Extract token
let token = TokenProvider::bearer(headers_state.handler())?;
// [...]
異なるソースや異なる形式の JWT トークンを使用する場合は、要求の他の部分を取得する Rust ロジックを実装できます。DataWeave パラメーターを使用して抽出を構成し、JWT ソースを動的に変更できるポリシーを作成します。
JWT ライブラリは、受信要求の認証トークンの署名検証を実行するためのヘルパーを提供しています。ポリシーで使用されるアルゴリズムに応じて、対応する設定で SignatureValidator を初期化します。
pub fn new(algorithm: SigningAlgorithm, key_length: SigningKeyLength, key: String) -> Result<SignatureValidator, JWTError>
PDK は以下の署名アルゴリズムとキー長をサポートしています。
署名アルゴリズム |
署名キーの長さ |
RSA |
256、384、512 |
HMAC |
256、384、512 |
ES |
256、384 |
SigningAlgorithm 列挙型は、利用可能な署名アルゴリズムを定義します。
pub enum SigningAlgorithm {
Rsa,
Hmac,
Es,
}
SigningKeyLength 列挙型は、利用可能な署名キーの長さを定義します。
pub enum SigningKeyLength {
Len256,
Len384,
Len512,
}
SignatureValidator インスタンスは SignatureValidation trait を実装します。この SignatureValidation trait は、トークンを文字列型のパラメーターとして受け取り、解析された JWTClaims を含む結果を返すか、エラーの場合は JWTError を返す validate 関数を提供します。
pub trait SignatureValidation {
fn validate(&self, token: String) -> Result<JWTClaims, JWTError>;
}
次のコード例は、SignatureValidator を初期化し、256 バイトの HMAC アルゴリズムで署名された受信トークンを検証する方法を示しています。
use anyhow::Result;
use pdk::hl::*;
use pdk::jwt::*;
// [...]
async fn filter(
state: RequestState,
signature_validator: &SignatureValidator,
) -> Flow<()> {
let headers_state = state.into_headers_state().await;
// Extract token
let token = TokenProvider::bearer(headers_state.handler())?;
if token.is_err() {
return Flow::Break(Response::new(401).with_body("Bearer not found"));
}
// Validating signature
let claims = signature_validator.validate(token.unwrap());
if claims.is_err() {
return Flow::Break(Response::new(401).with_body("Invalid token"));
}
// [...]
}
#[entrypoint]
async fn configure(launcher: Launcher, Configuration(configuration): Configuration) -> Result<()> {
let config: Config = serde_json::from_slice(&configuration)?;
let signature_validator = SignatureValidator::new(
model::SigningAlgorithm::Hmac,
model::SigningKeyLength::Len256,
config.secret.clone(),
)?;
launcher
.launch(on_request(|request| {
filter(request, &signature_validator)
}))
.await?;
Ok(())
}
SignatureValidator は初期化時にポリシーの設定が正しいことを確認するため、#[entrypoint] 関数で SignatureValidator を初期化すると、API が要求を受信したときではなく、ポリシーが適用されたときに早期にエラーがスローされます。
初期化後、ポリシーはラップされた関数の on_request に SignatureValidator を渡し、受信要求に対して validate メソッドを実行します。
署名検証の実行後、結果には JWT トークンから解析された JWT クレームが含まれます。
署名が無効な場合、または署名を検証しないことを選択した場合、PDK は JWTClaimsParser によってクレームを解析します。この構造体は、Result<JWTClaims, JWTError> を返す parse メソッドを提供します。
// Being "token" a String that contains a JWT token
let parsed_claims = JWTClaimsParser::parse(token);
if claims.is_err() {
return Flow::Break(Response::new(401).with_body("Invalid token"));
}
署名検証または解析メソッドを実行したら、JWTClaims 構造体が提供する以下のメソッドを使用して JWT クレームにアクセスします。
pub fn audience(&self) -> Option<Result<Vec<String>, JWTError>>
pub fn not_before(&self) -> Option<DateTime<Utc>>
pub fn expiration(&self) -> Option<DateTime<Utc>>
pub fn issued_at(&self) -> Option<DateTime<Utc>>
pub fn issuer(&self) -> Option<String>
pub fn jti(&self) -> Option<String>
pub fn nonce(&self) -> Option<String>
pub fn subject(&self) -> Option<String>
pub fn has_claim(&self, name: &str) -> bool
pub fn get_claim<T>(&self, name: &str) -> Option<T> where T: ValueRetrieval,
pub fn has_header(&self, name: &str) -> bool
pub fn get_header(&self, name: &str) -> Option<String>
pub fn get_claims(&self) -> pdk_script::Value
pub fn get_headers(&self) -> pdk_script::Value
提供されているメソッドは、それぞれの標準 JWT クレームを返すように設計されています。
get_claim メソッドは、標準またはカスタムクレームを返します。get_claim は異なる対象変数型をサポートしているため、ユーザーは出力種別を指定する必要があります。 get_claim は、出力種別として String、f64、Vec<String>、chrono::DateTime<chono::Utc>、serde_json::Value をサポートしています。クレームがトークン中に存在しない場合に備えて、Option で種別をラップする必要があります。例:
let some_custom_claim: Option<String> = claims.get_claim("username");
次の例では、ラップされた関数から JWT トークンを解析し、カスタムクレームを取得して、リクエストヘッダーに伝播する方法を示しています。
async fn filter(
state: RequestState,
) -> Flow<()> {
let headers_state = state.into_headers_state().await;
// Extract token
let token = TokenProvider::bearer(headers_state.handler())?;
if token.is_err() {
return Flow::Break(Response::new(401).with_body("Bearer not found"));
}
// Being "token" a String that contains a JWT token
let parsed_claims = JWTClaimsParser::parse(token.unwrap());
if claims.is_err() {
return Flow::Break(Response::new(401).with_body("Invalid token"));
}
let claims = claims.unwrap();
let username: Option<String> = claims.get_claim("username");
if let Some(custom_claim) = some_custom_claim {
headers_state
.handler()
.set_header("username", custom_claim.as_str());
}
Flow::Continue(())
}
通常、JWT トークンには、検証可能な標準および非標準クレームが含まれています。JWT トークンのヘッダーまたはペイロードに存在するクレームを取得するには、クレームへのアクセスとヘッダーへの伝搬 のメソッドを使用します。取得したら、クレームの値を検証します。
次のコード例では、JWT トークンについて以下を検証しています。
exp クレームによって期限切れではないことを検証。
aud クレームによって正しいオーディエンス値を持っていることを検証。
DataWeave 式ポリシーパラメーターとして設定されたカスタムバリデーターを使用して、正しいカスタム role クレームを持っていることを検証。
use chrono::Utc;
use pdk::hl::*;
use pdk::jwt::*;
use pdk::logger::info;
use pdk::script::Evaluator;
async fn filter(
state: RequestState,
mut custom_validator: Evaluator<'_>,
) -> Flow<()> {
let headers_state = state.into_headers_state().await;
let token = TokenProvider::bearer(headers_state.handler());
if token.is_err() {
return Flow::Break(Response::new(400).with_body("Bearer not found"));
}
// Being "token" a String that contains a JWT token
let parsed_claims = JWTClaimsParser::parse(token.unwrap());
if claims.is_err() {
return Flow::Break(Response::new(401).with_body("Invalid token"));
}
let claims = claims.unwrap();
// Validating token expiration
if let Some(exp) = claims.expiration() {
if exp < Utc::now() {
return Flow::Break(Response::new(400).with_body("token expired"));
}
} else {
return Flow::Break(Response::new(400).with_body("token missing exp claim"));
}
// Validating audience
if let Some(aud) = claims.audience() {
if !aud_value.iter_mut().any(|a| a == "myAudience") {
return Flow::Break(Response::new(400).with_body("token does not have the expected audience"));
}
}
// Custom claim validation
custom_validator.bind_vars("claimSet", claims.get_claims());
let result = custom_validator_role_claim.eval();
if !result.ok().and_then(|value| value.as_bool()).unwrap_or_default() {
return Flow::Break(Response::new(400).with_body("custom token validations failed."));
}
Flow::Continue(())
}
#[entrypoint]
async fn configure(launcher: Launcher, Configuration(configuration): Configuration) -> Result<()> {
let config: Config = serde_json::from_slice(&configuration)?;
launcher
.launch(on_request(|request| {
filter(request, config.custom_validator_role.evaluator())
}))
.await?;
Ok(())
}
上の例では、gcl.yaml スキーマ定義で次のように入力パラメーターとして設定されている DataWeave 式によってカスタム role クレームが定義されています。
# [...]
spec:
extends:
- name: extension-definition
namespace: default
properties:
customValidatorRole:
type: string
format: dataweave
default: "#[vars.claimSet.role == 'superRole']"
# [...]
required:
- customValidatorRole