jsonwebtoken (Rust)

認証JWTRustセキュリティトークン暗号化Web認証API認証

認証ライブラリ

jsonwebtoken (Rust)

概要

jsonwebtokenは、Rust言語でJSON Web Token(JWT)の生成と検証を行うための高性能な認証ライブラリです。2025年現在、crates.ioで最も人気のあるJWT実装として、Rustエコシステムにおける認証の標準的選択肢となっています。HS256、RS256、ES256など複数の暗号化アルゴリズムをサポートし、ゼロコスト抽象化によるRustの性能特性を活かした高速なトークン処理を実現します。axum、actix-web、warpなどの主要WebフレームワークとシームレスにMDXファイルアップデート統合可能です。

詳細

jsonwebtoken crateは、Keatsによって開発されたRust用の包括的なJWT実装です。RFC 7519準拠の完全なJWT仕様をサポートし、クレーム検証(exp、nbf、iss、aud、sub)の自動化、カスタムクレームの柔軟なハンドリング、および高度なセキュリティ機能を提供します。

2025年現在のバージョン9.xでは、以下の重要な特徴があります:

技術的特徴

  • ゼロコスト抽象化: Rustの所有権システムを活用した安全で高速な実装
  • 型安全性: コンパイル時の型チェックによるクレーム処理の安全性保証
  • メモリ安全性: バッファオーバーフローやメモリリークの完全な回避
  • 非同期対応: Future/asyncトレイトとの完全な互換性

暗号化アルゴリズムサポート

  • HMAC: HS256、HS384、HS512(共有秘密鍵ベース)
  • RSA: RS256、RS384、RS512(公開鍵暗号方式)
  • ECDSA: ES256、ES384(楕円曲線デジタル署名)
  • EdDSA: Ed25519(エドワーズ曲線デジタル署名)

セキュリティ機能

  • 自動クレーム検証: exp(有効期限)、nbf(有効開始時刻)の自動検証
  • カスタム検証: iss(発行者)、aud(対象者)、sub(主体)の設定可能検証
  • 必須クレーム: 特定クレームの存在を強制するValidation設定
  • 署名検証無効化: デバッグ目的での insecure_disable_signature_validation

メリット・デメリット

メリット

  • 高性能: Rustのゼロコスト抽象化による極めて高速な処理
  • メモリ安全性: Rustの所有権システムによる完全なメモリ安全性
  • 型安全性: コンパイル時型チェックによるランタイムエラー回避
  • 豊富なアルゴリズム: 現代的暗号化標準の包括的サポート
  • 軽量: 最小限の依存関係によるバイナリサイズの抑制
  • 成熟したライブラリ: Rustエコシステムでの豊富な実績と信頼性
  • 非同期対応: async/awaitパターンとの完全な互換性

デメリット

  • Rust専用: 他言語との相互運用性の制限
  • 学習コスト: Rust言語とJWT仕様の両方の理解が必要
  • エコシステム: JavaやPythonに比べてサードパーティライブラリが限定的
  • キーフォーマット: PKCS8フォーマットの EC秘密鍵のみサポート
  • デバッグ複雑性: Rustの借用チェッカーによる初期学習の困難さ

参考ページ

書き方の例

インストールと基本設定

# Cargo.toml
[dependencies]
jsonwebtoken = "9"
serde = { version = "1.0", features = ["derive"] }
chrono = { version = "0.4", features = ["serde"] }
tokio = { version = "1.0", features = ["full"] }

# Web フレームワーク統合用(選択)
axum = "0.7"
tower = "0.4"
tower-http = { version = "0.5", features = ["cors"] }
// src/lib.rs - 基本的なインポートと設定
use jsonwebtoken::{
    encode, decode, Header, Algorithm, Validation, EncodingKey, DecodingKey,
    errors::Result as JwtResult,
};
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc, Duration};
use std::env;

// JWT設定構造体
pub struct JwtConfig {
    pub secret_key: String,
    pub algorithm: Algorithm,
    pub expiration_hours: i64,
    pub issuer: String,
    pub audience: String,
}

impl Default for JwtConfig {
    fn default() -> Self {
        Self {
            secret_key: env::var("JWT_SECRET").unwrap_or_else(|_| {
                "your-super-secret-key-change-this-in-production".to_string()
            }),
            algorithm: Algorithm::HS256,
            expiration_hours: 24,
            issuer: "rust-jwt-example".to_string(),
            audience: "rust-jwt-users".to_string(),
        }
    }
}

// JWT クレーム構造体
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
    pub sub: String,        // Subject (user ID)
    pub iss: String,        // Issuer
    pub aud: String,        // Audience
    pub exp: i64,           // Expiration time (Unix timestamp)
    pub iat: i64,           // Issued at (Unix timestamp)
    pub nbf: i64,           // Not before (Unix timestamp)
    pub jti: String,        // JWT ID
    
    // カスタムクレーム
    pub user_id: u64,
    pub username: String,
    pub email: String,
    pub roles: Vec<String>,
    pub permissions: Vec<String>,
}

impl Claims {
    pub fn new(
        user_id: u64,
        username: String,
        email: String,
        roles: Vec<String>,
        permissions: Vec<String>,
        config: &JwtConfig,
    ) -> Self {
        let now = Utc::now();
        let exp_time = now + Duration::hours(config.expiration_hours);
        
        Self {
            sub: user_id.to_string(),
            iss: config.issuer.clone(),
            aud: config.audience.clone(),
            exp: exp_time.timestamp(),
            iat: now.timestamp(),
            nbf: now.timestamp(),
            jti: uuid::Uuid::new_v4().to_string(),
            user_id,
            username,
            email,
            roles,
            permissions,
        }
    }
}

トークン生成と検証

// src/jwt.rs - JWT サービス実装
use crate::{Claims, JwtConfig};
use jsonwebtoken::{encode, decode, Header, Validation, EncodingKey, DecodingKey};
use std::collections::HashSet;

pub struct JwtService {
    config: JwtConfig,
    encoding_key: EncodingKey,
    decoding_key: DecodingKey,
    validation: Validation,
}

impl JwtService {
    pub fn new(config: JwtConfig) -> Self {
        let encoding_key = EncodingKey::from_secret(config.secret_key.as_bytes());
        let decoding_key = DecodingKey::from_secret(config.secret_key.as_bytes());
        
        let mut validation = Validation::new(config.algorithm);
        validation.set_issuer(&[&config.issuer]);
        validation.set_audience(&[&config.audience]);
        validation.validate_exp = true;
        validation.validate_nbf = true;
        
        // 必須クレームの設定
        let mut required_claims = HashSet::new();
        required_claims.insert("sub".to_string());
        required_claims.insert("user_id".to_string());
        required_claims.insert("username".to_string());
        required_claims.insert("email".to_string());
        validation.required_spec_claims = required_claims;
        
        Self {
            config,
            encoding_key,
            decoding_key,
            validation,
        }
    }

    // アクセストークン生成
    pub fn generate_access_token(&self, claims: &Claims) -> JwtResult<String> {
        let header = Header::new(self.config.algorithm);
        encode(&header, claims, &self.encoding_key)
    }

    // トークン検証と解析
    pub fn verify_token(&self, token: &str) -> JwtResult<Claims> {
        let token_data = decode::<Claims>(token, &self.decoding_key, &self.validation)?;
        Ok(token_data.claims)
    }

    // リフレッシュトークン生成(長期間有効)
    pub fn generate_refresh_token(&self, user_id: u64) -> JwtResult<String> {
        let now = Utc::now();
        let exp_time = now + Duration::days(30); // 30日間有効
        
        let refresh_claims = Claims {
            sub: user_id.to_string(),
            iss: self.config.issuer.clone(),
            aud: format!("{}-refresh", self.config.audience),
            exp: exp_time.timestamp(),
            iat: now.timestamp(),
            nbf: now.timestamp(),
            jti: uuid::Uuid::new_v4().to_string(),
            user_id,
            username: "refresh".to_string(), // リフレッシュトークンでは最小限の情報
            email: "".to_string(),
            roles: vec![],
            permissions: vec![],
        };

        let header = Header::new(self.config.algorithm);
        encode(&header, &refresh_claims, &self.encoding_key)
    }

    // トークンからユーザーIDを抽出(検証なし、ログ用など)
    pub fn extract_user_id_without_verification(&self, token: &str) -> Option<u64> {
        let mut validation = Validation::new(self.config.algorithm);
        validation.insecure_disable_signature_validation();
        validation.validate_exp = false;
        validation.validate_nbf = false;
        validation.required_spec_claims.clear();

        decode::<Claims>(token, &DecodingKey::from_secret(b""), &validation)
            .ok()
            .map(|token_data| token_data.claims.user_id)
    }
}

// 使用例
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_token_generation_and_verification() {
        let config = JwtConfig::default();
        let jwt_service = JwtService::new(config);

        let claims = Claims::new(
            123,
            "testuser".to_string(),
            "[email protected]".to_string(),
            vec!["user".to_string()],
            vec!["read".to_string(), "write".to_string()],
            &jwt_service.config,
        );

        // トークン生成
        let token = jwt_service.generate_access_token(&claims).unwrap();
        assert!(!token.is_empty());

        // トークン検証
        let verified_claims = jwt_service.verify_token(&token).unwrap();
        assert_eq!(verified_claims.user_id, 123);
        assert_eq!(verified_claims.username, "testuser");
        assert_eq!(verified_claims.email, "[email protected]");
        assert_eq!(verified_claims.roles, vec!["user"]);
    }
}

Axum Webフレームワーク統合

// src/auth.rs - Axum認証ミドルウェア
use axum::{
    async_trait,
    extract::{FromRequestParts, TypedHeader},
    headers::{authorization::Bearer, Authorization},
    http::{request::Parts, StatusCode},
    response::{IntoResponse, Response},
    Json,
};
use serde_json::json;
use crate::{Claims, JwtService};

// 認証エラー型
#[derive(Debug)]
pub enum AuthError {
    MissingToken,
    InvalidToken,
    ExpiredToken,
    InsufficientPermissions,
}

impl IntoResponse for AuthError {
    fn into_response(self) -> Response {
        let (status, message) = match self {
            AuthError::MissingToken => (StatusCode::UNAUTHORIZED, "Missing authorization token"),
            AuthError::InvalidToken => (StatusCode::UNAUTHORIZED, "Invalid token"),
            AuthError::ExpiredToken => (StatusCode::UNAUTHORIZED, "Token expired"),
            AuthError::InsufficientPermissions => (StatusCode::FORBIDDEN, "Insufficient permissions"),
        };

        let body = Json(json!({
            "error": message,
            "status": status.as_u16()
        }));

        (status, body).into_response()
    }
}

// 認証済みユーザー情報を表すエクストラクター
pub struct AuthenticatedUser(pub Claims);

#[async_trait]
impl<S> FromRequestParts<S> for AuthenticatedUser
where
    S: Send + Sync,
{
    type Rejection = AuthError;

    async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
        // Authorization ヘッダーからトークンを取得
        let TypedHeader(Authorization(bearer)) = parts
            .extract::<TypedHeader<Authorization<Bearer>>>()
            .await
            .map_err(|_| AuthError::MissingToken)?;

        // JwtService を利用してトークンを検証
        // 実際の実装では、Stateから JwtService を取得
        let jwt_service = JwtService::new(Default::default()); // 簡略化
        
        let claims = jwt_service
            .verify_token(bearer.token())
            .map_err(|_| AuthError::InvalidToken)?;

        Ok(AuthenticatedUser(claims))
    }
}

// ロールベース認証デコレーター的な実装
pub struct RequireRole(pub String);

#[async_trait]
impl<S> FromRequestParts<S> for RequireRole
where
    S: Send + Sync,
{
    type Rejection = AuthError;

    async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
        let AuthenticatedUser(claims) = AuthenticatedUser::from_request_parts(parts, state).await?;
        
        // ロールチェック(実際の実装では設定可能にする)
        let required_role = "admin"; // この例では admin ロールを要求
        
        if !claims.roles.contains(&required_role.to_string()) {
            return Err(AuthError::InsufficientPermissions);
        }

        Ok(RequireRole(required_role.to_string()))
    }
}

RESTful API実装

// src/handlers.rs - API ハンドラー実装
use axum::{
    extract::{Path, Query, State},
    http::StatusCode,
    response::Json,
    routing::{get, post},
    Router,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use crate::{AuthenticatedUser, RequireRole, JwtService, Claims};

// アプリケーション状態
#[derive(Clone)]
pub struct AppState {
    pub jwt_service: Arc<JwtService>,
}

// ログイン要求
#[derive(Deserialize)]
pub struct LoginRequest {
    pub username: String,
    pub password: String,
}

// ログイン応答
#[derive(Serialize)]
pub struct LoginResponse {
    pub access_token: String,
    pub refresh_token: String,
    pub token_type: String,
    pub expires_in: i64,
}

// ユーザー情報
#[derive(Serialize)]
pub struct UserInfo {
    pub user_id: u64,
    pub username: String,
    pub email: String,
    pub roles: Vec<String>,
    pub permissions: Vec<String>,
}

// ログインハンドラー
pub async fn login_handler(
    State(state): State<AppState>,
    Json(login_req): Json<LoginRequest>,
) -> Result<Json<LoginResponse>, StatusCode> {
    // 実際の実装では、データベースでユーザー認証を行う
    if login_req.username != "demo" || login_req.password != "password" {
        return Err(StatusCode::UNAUTHORIZED);
    }

    // クレーム作成
    let claims = Claims::new(
        1,
        login_req.username,
        "[email protected]".to_string(),
        vec!["user".to_string(), "admin".to_string()],
        vec!["read".to_string(), "write".to_string(), "delete".to_string()],
        &JwtConfig::default(),
    );

    // トークン生成
    let access_token = state.jwt_service
        .generate_access_token(&claims)
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

    let refresh_token = state.jwt_service
        .generate_refresh_token(claims.user_id)
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

    let response = LoginResponse {
        access_token,
        refresh_token,
        token_type: "Bearer".to_string(),
        expires_in: 24 * 3600, // 24時間
    };

    Ok(Json(response))
}

// プロファイル取得ハンドラー(認証必須)
pub async fn profile_handler(
    auth_user: AuthenticatedUser,
) -> Json<UserInfo> {
    let claims = auth_user.0;
    
    let user_info = UserInfo {
        user_id: claims.user_id,
        username: claims.username,
        email: claims.email,
        roles: claims.roles,
        permissions: claims.permissions,
    };

    Json(user_info)
}

// 管理者専用ハンドラー(管理者ロール必須)
pub async fn admin_only_handler(
    _: RequireRole, // admin ロールを要求
    auth_user: AuthenticatedUser,
) -> Json<serde_json::Value> {
    Json(json!({
        "message": "Admin area accessed successfully",
        "user": auth_user.0.username,
        "timestamp": chrono::Utc::now().to_rfc3339()
    }))
}

// 保護されたリソースハンドラー
pub async fn protected_resource_handler(
    Path(resource_id): Path<u64>,
    auth_user: AuthenticatedUser,
) -> Result<Json<serde_json::Value>, StatusCode> {
    // リソースアクセス権限チェック
    if !auth_user.0.permissions.contains(&"read".to_string()) {
        return Err(StatusCode::FORBIDDEN);
    }

    Ok(Json(json!({
        "resource_id": resource_id,
        "data": "This is protected resource data",
        "accessed_by": auth_user.0.username,
        "timestamp": chrono::Utc::now().to_rfc3339()
    })))
}

// ルーター設定
pub fn create_router() -> Router {
    let jwt_service = Arc::new(JwtService::new(JwtConfig::default()));
    let app_state = AppState { jwt_service };

    Router::new()
        .route("/login", post(login_handler))
        .route("/profile", get(profile_handler))
        .route("/admin", get(admin_only_handler))
        .route("/resources/:id", get(protected_resource_handler))
        .with_state(app_state)
}

RSA公開鍵暗号化実装

// src/rsa_jwt.rs - RSA公開鍵ペア使用例
use jsonwebtoken::{
    encode, decode, Header, Algorithm, Validation, EncodingKey, DecodingKey,
};
use rsa::{RsaPrivateKey, RsaPublicKey, pkcs8::{EncodePrivateKey, EncodePublicKey}};
use rsa::rand_core::OsRng;

pub struct RsaJwtService {
    private_key: RsaPrivateKey,
    public_key: RsaPublicKey,
    encoding_key: EncodingKey,
    decoding_key: DecodingKey,
}

impl RsaJwtService {
    pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
        // RSAキーペア生成(2048ビット)
        let mut rng = OsRng;
        let private_key = RsaPrivateKey::new(&mut rng, 2048)?;
        let public_key = RsaPublicKey::from(&private_key);

        // PKCS#8 PEM エンコーディング
        let private_pem = private_key.to_pkcs8_pem(rsa::pkcs8::LineEnding::LF)?;
        let public_pem = public_key.to_public_key_pem(rsa::pkcs8::LineEnding::LF)?;

        // JWT キー作成
        let encoding_key = EncodingKey::from_rsa_pem(private_pem.as_bytes())?;
        let decoding_key = DecodingKey::from_rsa_pem(public_pem.as_bytes())?;

        Ok(Self {
            private_key,
            public_key,
            encoding_key,
            decoding_key,
        })
    }

    pub fn generate_token(&self, claims: &Claims) -> JwtResult<String> {
        let header = Header::new(Algorithm::RS256);
        encode(&header, claims, &self.encoding_key)
    }

    pub fn verify_token(&self, token: &str) -> JwtResult<Claims> {
        let validation = Validation::new(Algorithm::RS256);
        let token_data = decode::<Claims>(token, &self.decoding_key, &validation)?;
        Ok(token_data.claims)
    }

    // 公開鍵をJWKS形式で出力(OAuth2対応)
    pub fn get_public_key_jwks(&self) -> serde_json::Value {
        // 実際の実装では、RSA公開鍵をJWKS形式に変換
        json!({
            "keys": [{
                "kty": "RSA",
                "use": "sig",
                "kid": "rsa-key-1",
                "alg": "RS256",
                // "n": base64url(public_key.n),
                // "e": base64url(public_key.e),
            }]
        })
    }
}

テストとパフォーマンス測定

// src/tests.rs - 包括的テストスイート
#[cfg(test)]
mod tests {
    use super::*;
    use std::time::Instant;

    #[test]
    fn test_jwt_lifecycle() {
        let jwt_service = JwtService::new(JwtConfig::default());
        
        let claims = Claims::new(
            42,
            "testuser".to_string(),
            "[email protected]".to_string(),
            vec!["user".to_string()],
            vec!["read".to_string()],
            &jwt_service.config,
        );

        // トークン生成
        let token = jwt_service.generate_access_token(&claims).unwrap();
        assert!(!token.is_empty());

        // トークン検証
        let verified_claims = jwt_service.verify_token(&token).unwrap();
        assert_eq!(verified_claims.user_id, 42);
        assert_eq!(verified_claims.username, "testuser");
    }

    #[test]
    fn test_expired_token() {
        let mut config = JwtConfig::default();
        config.expiration_hours = -1; // 過去の時刻
        
        let jwt_service = JwtService::new(config);
        let claims = Claims::new(
            1,
            "user".to_string(),
            "[email protected]".to_string(),
            vec![],
            vec![],
            &jwt_service.config,
        );

        let token = jwt_service.generate_access_token(&claims).unwrap();
        
        // 期限切れトークンの検証は失敗するはず
        assert!(jwt_service.verify_token(&token).is_err());
    }

    #[test]
    fn test_invalid_signature() {
        let jwt_service1 = JwtService::new(JwtConfig {
            secret_key: "secret1".to_string(),
            ..Default::default()
        });
        
        let jwt_service2 = JwtService::new(JwtConfig {
            secret_key: "secret2".to_string(),
            ..Default::default()
        });

        let claims = Claims::new(
            1,
            "user".to_string(),
            "[email protected]".to_string(),
            vec![],
            vec![],
            &jwt_service1.config,
        );

        let token = jwt_service1.generate_access_token(&claims).unwrap();
        
        // 異なる秘密鍵で検証すると失敗するはず
        assert!(jwt_service2.verify_token(&token).is_err());
    }

    #[tokio::test]
    async fn benchmark_token_operations() {
        let jwt_service = JwtService::new(JwtConfig::default());
        let claims = Claims::new(
            1,
            "benchmark_user".to_string(),
            "[email protected]".to_string(),
            vec!["user".to_string()],
            vec!["read".to_string()],
            &jwt_service.config,
        );

        // トークン生成ベンチマーク
        let start = Instant::now();
        let mut tokens = Vec::new();
        for _ in 0..10_000 {
            let token = jwt_service.generate_access_token(&claims).unwrap();
            tokens.push(token);
        }
        let generation_time = start.elapsed();
        println!("10,000 token generation: {:?}", generation_time);

        // トークン検証ベンチマーク
        let start = Instant::now();
        for token in &tokens {
            jwt_service.verify_token(token).unwrap();
        }
        let verification_time = start.elapsed();
        println!("10,000 token verification: {:?}", verification_time);

        // メモリ使用量確認
        let token_size = tokens[0].len();
        println!("Average token size: {} bytes", token_size);
    }

    // 並行処理テスト
    #[tokio::test]
    async fn test_concurrent_operations() {
        use tokio::task;
        
        let jwt_service = Arc::new(JwtService::new(JwtConfig::default()));
        let mut handles = vec![];

        for i in 0..100 {
            let service = jwt_service.clone();
            let handle = task::spawn(async move {
                let claims = Claims::new(
                    i,
                    format!("user{}", i),
                    format!("user{}@example.com", i),
                    vec!["user".to_string()],
                    vec!["read".to_string()],
                    &JwtConfig::default(),
                );

                let token = service.generate_access_token(&claims).unwrap();
                let verified = service.verify_token(&token).unwrap();
                assert_eq!(verified.user_id, i);
            });
            handles.push(handle);
        }

        // すべてのタスクの完了を待機
        for handle in handles {
            handle.await.unwrap();
        }
    }
}

このコード例では、Rust jsonwebtokenライブラリの包括的な実装を示しており、2025年現在の最新機能とベストプラクティスを含んでいます。Rustの特性を活かした高性能で安全な認証システムの構築方法を提供しています。