Contents
AxumのLayer APIを用いたカスタムミドルウェア設計
AxumフレームワークでJWT認証を実装する際、Layer APIによるミドルウェア構築はセキュリティと柔軟性を両立させるための鍵です。このセクションでは、認証ロジックを独立させた設計方法とその利点について解説します。
ミドルウェア構造と実装手順
AxumのLayer APIは、ハンドラに処理チェーンを注入する仕組みとして機能します。これにより、認証ロジックを個別のミドルウェアとして再利用可能に設計できます。
- trait実装:
tower::layer::Layerとtower::service::Serviceトレイトを実装し、リクエストのフィルタリング処理を定義します。 - チェーン構造の作成: 複数のミドルウェアを
.layer()メソッドで積み重ね、認証→ロール検査→アクセス制限などのフローを作り出せます。 - テスト可能な設計: ミドルウェア自体をモック化・単体テストしやすい構造にすることで、品質保証の強化が可能です。
以下のコード例は、認証ミドルウェアの雛形です:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
use axum::{middleware::Next, Router}; use tower::layer::Layer; use std::future::Future; pub struct AuthMiddleware; impl<S> Layer<S> for AuthMiddleware { type Service = AuthMiddlewareService<S>; fn layer(&self, inner: S) -> Self::Service { AuthMiddlewareService { inner } } } pub struct AuthMiddlewareService<S> { inner: S, } #[async_trait] impl<S> axum::middleware::Middleware for AuthMiddlewareService<S> where S: Clone + Send + Sync + 'static, { async fn call(&self, req: Request<Body>, next: Next<'_>) -> Result<Response<Body>, Infallible> { // トークン検証ロジック let result = next.run(req).await; Ok(result) } } |
認証ロジックの注入方法
Layer APIを活用することで、認証処理を任意のエンドポイントに限定して適用できます。この設計により、共通処理と個別処理の分離が可能になり、コードの保守性向上が実現されます。
jsonwebtokenクレートによるJWT生成・署名処理
JSON Web Token(JWT)は軽量で拡張可能な認証方式として広く採用されています。ここでは、Rust環境でのセキュアなトークン発行と署名アルゴリズムの選定について解説します。
セキュリティ強化策の選定基準
JWTの安全性は署名アルゴリズムに依存します。2026年現在、業界で最も推奨されるのは以下の3つです:
| アルゴリズム | 説明 | 推奨度 |
|---|---|---|
| RS256 | RSA非対称暗号(公開鍵による検証) | ✅ 最高 |
| ES256K | ECDSAのセキュアなバリアント | ✅ 高い |
| HS256 | HMAC-SHA256(秘密鍵のみで署名) | ⚠️ 限定的 |
注意: HS256はクライアントとサーバーで同じ秘密鍵を共有する必要があるため、複数のサービス間で使用するのは避けましょう。
jsonwebtokenクレートではEncodingKey::from_secretが利用可能ですが、最新バージョン(0.12以降)ではHmacSigner::new()などの代替APIも存在します。
秘密鍵の安全な管理方法
トークン生成に必要な秘密鍵の保存には、以下のベストプラクティスが重要です:
- 環境変数での保管:
.envファイルに保存し、リポジトリ外で管理 - 暗号化されたシークレットマネージャー使用: AWS Secrets ManagerやHashiCorp Vaultなどを使う
- 例:
AWS_SECRETSMANAGER_ACCESS_KEY_ID=xxxx - アクセス制限の設定: 鍵を参照する際は最小権限の原則に基づく
テスト環境用の秘密鍵生成例(jsonwebtokenクレート):
|
1 2 3 4 5 |
use jsonwebtoken::{EncodingKey, Header}; let secret = "test-secret-1234567890"; let key = EncodingKey::from_secret(secret.as_ref()); |
HTTPヘッダーよりBearerトークンの抽出方法
JWT認証フローにおいて、Authorizationヘッダーからトークンを安全に取得する方法が非常に重要です。不正な解析ロジックはセキュリティリスクにつながります。
Authorizationヘッダーのパースロジック
以下のように、ヘッダー文字列をBearer前後で分割し、トークンを抽出します:
|
1 2 3 4 5 6 7 8 |
fn extract_token(headers: &HeaderMap) -> Result<String, AuthError> { headers.get("Authorization") .and_then(|value| value.to_str().ok()) .filter(|s| s.starts_with("Bearer ")) .map(|s| s["Bearer ".len()..].to_string()) .ok_or(AuthError::MissingToken) } |
この処理では以下の点に注意が必要です:
- 大文字小文字の検証: "bearer"や"Bearer"など、ケース違いを許容
- 空白文字除去: トークン前後のスペースが含まれている場合の対処
- 例:
s.trim_start_matches("Bearer ")を使用 - 認証方式の拡張性: Bearer以外にもAPIキー認証をサポートするための設計
複数認証方式への拡張性
将来的にAPIキー認証など他の方式を導入しやすい構造にするには、認証インターフェースを抽象化しましょう。以下の例では、共通インタフェースで処理を分岐できます:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
trait Authenticator { fn authenticate(&self, req: &Request<Body>) -> Result<Token, AuthError>; } struct BearerAuthenticator; impl Authenticator for BearerAuthenticator { fn authenticate(&self, req: &Request<Body>) -> Result<Token, AuthError> { // Bearerトークン処理 let token = extract_token(req.headers())?; validate_jwt(token).map_err(|e| e.into()) } } |
トークン検証時のエラーハンドリング戦略
JWTの検証が失敗した場合の適切なエラーレスポンス設計は、システムの信頼性向上に直結します。以下のステータスコード選定が重要です:
認証失敗時のステータスコード選定
| エラー種別 | ステータスコード | 対応処理 |
|---|---|---|
| トークン不正(署名エラーや期限切れ) | 401 Unauthorized | リフレッシュトークンの利用を促す |
| 無効なフォーマット(ヘッダー解析失敗) | 400 Bad Request | ユーザーに正しくリクエストを送るよう指示 |
| 不許可ユーザー(ロール不足など) | 403 Forbidden | アクセス権限の説明付きで拒否 |
注意: 401と403は区別が重要です。401は認証が必要、403は認証済みだがアクセス不可を意味します。
ログ出力と監視のベストプラクティス
検証失敗時の情報収集には以下の点に留意してください:
- 送信元IPやリクエストパスを含める
- エラー種別を分類して記録(例: "invalid_signature"、"expired_token")
- ログレベルの設定で「INFO」「WARN」など使い分ける
非対称鍵(RSA/ECDSA)の管理ベストプラクティス
現代のセキュリティでは非対称鍵が必須です。ここでは、公開鍵と秘密鍵を安全に運用するための方法を解説します。
秘密鍵の暗号化保存方法
秘密鍵は以下の手順で管理するのがベストプラクティスです:
- 鍵ペア生成: OpenSSLや
jsonwebtokenクレートのユーティリティを使って作成 - ファイル形式での保存: PEMフォーマットが推奨(
.pemや.key拡張子) - 暗号化されたストレージへの格納: AWS KMS、Vault、または独自の暗号化ライブラリを使用
鍵ペア生成例(Rustコード):
|
1 2 3 |
openssl genrsa -out private_key.pem 2048 openssl rsa -in private_key.pem -pubout > public_key.pem |
公開鍵配布時のセキュリティ設計
公開鍵は第三者に安全に配布できる仕組みが必要です。以下の方法が効果的です:
- CDN経由での静的ホスティング:
https://auth.example.com/public_key.pemなど - 動的に更新される仕組み(例: JWTのISSフィールドで公開鍵のURLを指定)
- 公開鍵のハッシュ値をリポジトリに保管: 更新時の検証に使用
テスト環境でのトークン発行とセキュリティ設計上の注意点
開発・テスト段階では、仮想トークンを使用した認証フローの確認が重要です。以下はその実装例です。
テスト用JWT生成コードの実装例
テスト目的で以下の関数を作成し、jsonwebtokenクレートを使って簡単にトークンを生成できます:
|
1 2 3 4 5 6 7 8 |
use jsonwebtoken::{encode, Header, EncodingKey}; pub fn generate_test_token(payload: &str) -> String { let secret = "test-secret-1234567890"; encode(&Header::default(), payload, &EncodingKey::from_secret(secret.as_ref())) .expect("トークン生成に失敗しました") } |
この関数で発行したトークンは、"alg": "HS256"の署名アルゴリズムを使用しているため、本番環境での使用は避けてください。
クロスサイトスクリプティング対策
JWTを用いた認証では、XSS(クロスサイトスクリプティング)へのリスクが高まるため、以下の対応が必要です:
- HTTPSの強制化:
https://でのみトークンを発行・送信 - HttpOnlyフラグ付きのCookie: クライアントサイドJavaScriptからトークンにアクセスできないようにする
- 例:
Set-Cookie: token=xxxx; HttpOnly; Secure - CSRFトークンの併用: Bearerトークンと併せて使用して、不正リクエストを検知
まとめ
本記事では、AxumフレームワークでのJWT認証実装に必要な以下の手順を解説しました:
- カスタムミドルウェアの設計(Layer APIによる構築)
- 安全なトークン発行と署名アルゴリズム選定(RS256推奨)
- ヘッダー解析ロジックと複数認証方式への拡張性確保
- エラーハンドリングと監視設計のベストプラクティス
- 非対称鍵管理と公開鍵配布のセキュリティ設計
- テスト用トークン生成とXSS/CORSリスクへの対応
これらの知識を活かし、AxumプロジェクトでセキュアなAPI構築を進めてください。