本文面向有Rust基础的中级开发者,系统讲解如何在 Axum 框架中构建生产级 JWT 认证中间件。涵盖 JWT 原理、Token 服务封装、Tower 中间件实现、路由守卫、Refresh Token 机制及安全最佳实践,提供完整的可运行代码。

📑 目录


前言

在上一篇文章《Rust 密码管理模块与 PostgreSQL 集成实战》中,我们完成了用户注册和登录的密码验证。但登录之后呢?每次请求都需要重新验证密码吗?显然不行——这就是 JWT(JSON Web Token) 登场的时刻。

JWT 凭借其无状态、自包含、跨平台的特性,已成为现代 Web 应用最主流的认证方案之一。而在 Rust 生态中,jsonwebtoken crate 提供了完整的 JWT 编解码支持,配合 Axum 的 Tower 中间件体系,可以构建出类型安全、编译期可验证的认证中间件。

本文将带你从零构建一套完整的 JWT 认证中间件,涵盖:

  • JWT Token 的生成、验证与刷新
  • 基于 Tower Layer 的认证中间件
  • 自定义 Axum Extractor 提取用户信息
  • 路由级别的权限保护
  • Access Token + Refresh Token 双令牌机制

1. JWT 核心原理回顾

JWT 由三部分组成,用点号(.)连接:

[Header].[Payload].[Signature]
部分 内容 说明
Header {"alg": "HS256", "typ": "JWT"} 签名算法和令牌类型
Payload {"sub": "user_id", "exp": 1735689600, "role": "admin"} 用户声明(Claims)
Signature 对前两部分签名的结果 保证令牌未被篡改

⚠️ 关键安全提醒:Header 和 Payload 仅经过 Base64Url 编码,不是加密!任何人都可以解码查看内容。因此 永远不要在 JWT 中存放敏感信息(如密码、银行卡号等)。

jsonwebtoken crate 会自动验证 exp(过期时间)和 nbf(生效时间)声明,同时支持验证 subissaud 等标准声明。


2. 技术选型与环境配置

组件 选择 说明
Web 框架 Axum 0.7 基于 Tower 的异步 Web 框架
JWT 库 jsonwebtoken 9.3+ Rust 最主流的 JWT 库
中间件体系 Tower 0.4+ 提供 Layer/Service 抽象
序列化 Serde Claims 的序列化/反序列化
时间处理 chrono 处理 Token 过期时间

Cargo.toml

[package]
name = "jwt-auth-service"
version = "0.1.0"
edition = "2021"

[dependencies]
# Web 框架
axum = { version = "0.7", features = ["macros"] }
tokio = { version = "1.0", features = ["full"] }
tower = "0.4"
tower-http = { version = "0.5", features = ["cors"] }

# JWT 核心库
jsonwebtoken = "9.3"
# 时间处理
chrono = { version = "0.4", features = ["serde"] }
# 序列化
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# 环境变量
dotenvy = "0.15"
# 日志
tracing = "0.1"
tracing-subscriber = "0.3"
# 错误处理
anyhow = "1.0"
thiserror = "1.0"

.env 文件

JWT_SECRET=your-super-secret-key-at-least-32-bytes-long
JWT_ACCESS_TOKEN_EXPIRY=15m
JWT_REFRESH_TOKEN_EXPIRY=7d

3. Claims 定义与 Token 服务

3.1 自定义 Claims 结构体

Claims 是 JWT Payload 的类型安全映射。我们需要定义自己的 Claims 结构体,并实现 jsonwebtoken 所需的 trait。

src/auth/claims.rs

use serde::{Deserialize, Serialize};
use uuid::Uuid;

/// JWT Claims - 存储在 Token 中的用户信息
/// 
/// 注意:这里只存放非敏感信息(用户ID、角色等)
/// 不要存放密码、邮箱等敏感数据
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Claims {
    /// 用户唯一标识(Subject)
    pub sub: String,
    /// 用户角色(用于后续权限校验)
    pub role: String,
    /// 签发时间(Issued At)
    pub iat: usize,
    /// 过期时间(Expiration)
    pub exp: usize,
    /// 令牌唯一标识(JWT ID)- 用于令牌吊销
    pub jti: String,
}

impl Claims {
    /// 创建新的 Claims(使用当前时间作为签发时间)
    pub fn new(user_id: Uuid, role: String, exp: usize) -> Self {
        let now = chrono::Utc::now().timestamp() as usize;
        Self {
            sub: user_id.to_string(),
            role,
            iat: now,
            exp,
            jti: Uuid::now_v7().to_string(),
        }
    }

    /// 获取用户ID
    pub fn user_id(&self) -> Result<Uuid, uuid::Error> {
        Uuid::parse_str(&self.sub)
    }
}

💡 设计建议:Claims 中只存放非敏感、必需的信息。用户ID和角色足以支撑后续的授权决策。邮箱等敏感信息应从数据库查询,而非从 Token 中读取。

3.2 Token 生成与验证服务

src/auth/token_service.rs

use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use super::claims::Claims;
use super::error::AuthError;

/// Token 类型
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TokenType {
    Access,
    Refresh,
}

/// Token 服务配置
#[derive(Debug, Clone)]
pub struct TokenConfig {
    pub secret: String,
    pub access_expiry_secs: usize,   // 访问令牌有效期(秒)
    pub refresh_expiry_secs: usize,  // 刷新令牌有效期(秒)
}

impl TokenConfig {
    /// 从环境变量加载配置
    pub fn from_env() -> Result<Self, AuthError> {
        let secret = std::env::var("JWT_SECRET")
            .map_err(|_| AuthError::ConfigError("JWT_SECRET 未设置".to_string()))?;
        
        // 要求密钥长度至少 32 字节(HS256 要求)
        if secret.len() < 32 {
            return Err(AuthError::ConfigError(
                "JWT_SECRET 长度至少需要 32 字节".to_string()
            ));
        }

        let access_expiry = parse_duration(
            &std::env::var("JWT_ACCESS_TOKEN_EXPIRY").unwrap_or_else(|_| "15m".to_string())
        )?;
        let refresh_expiry = parse_duration(
            &std::env::var("JWT_REFRESH_TOKEN_EXPIRY").unwrap_or_else(|_| "7d".to_string())
        )?;

        Ok(Self {
            secret,
            access_expiry_secs: access_expiry,
            refresh_expiry_secs: refresh_expiry,
        })
    }
}

/// 解析时长字符串(如 "15m", "1h", "7d")
fn parse_duration(s: &str) -> Result<usize, AuthError> {
    let s = s.trim();
    if s.is_empty() {
        return Err(AuthError::ConfigError("时长字符串为空".to_string()));
    }

    let (num_str, unit) = s.split_at(s.len() - 1);
    let num: usize = num_str.parse()
        .map_err(|_| AuthError::ConfigError(format!("无效的数字: {}", num_str)))?;

    let seconds = match unit {
        "s" => num,
        "m" => num * 60,
        "h" => num * 3600,
        "d" => num * 86400,
        _ => return Err(AuthError::ConfigError(format!("无效的时间单位: {}", unit))),
    };
    Ok(seconds)
}

/// Token 服务
#[derive(Debug, Clone)]
pub struct TokenService {
    config: TokenConfig,
}

impl TokenService {
    pub fn new(config: TokenConfig) -> Self {
        Self { config }
    }

    /// 生成 Access Token
    pub fn generate_access_token(&self, user_id: Uuid, role: String) -> Result<String, AuthError> {
        let exp = chrono::Utc::now().timestamp() as usize + self.config.access_expiry_secs;
        let claims = Claims::new(user_id, role, exp);
        self.encode_token(&claims)
    }

    /// 生成 Refresh Token(使用相同的 Claims 结构,但有效期更长)
    pub fn generate_refresh_token(&self, user_id: Uuid, role: String) -> Result<String, AuthError> {
        let exp = chrono::Utc::now().timestamp() as usize + self.config.refresh_expiry_secs;
        let claims = Claims::new(user_id, role, exp);
        self.encode_token(&claims)
    }

    /// 编码 Token
    fn encode_token(&self, claims: &Claims) -> Result<String, AuthError> {
        let header = Header::default(); // 默认使用 HS256
        let encoding_key = EncodingKey::from_secret(self.config.secret.as_bytes());
        
        encode(&header, claims, &encoding_key)
            .map_err(|e| AuthError::TokenCreationError(e.to_string()))
    }

    /// 验证并解码 Token
    pub fn verify_token(&self, token: &str) -> Result<Claims, AuthError> {
        let decoding_key = DecodingKey::from_secret(self.config.secret.as_bytes());
        let mut validation = Validation::default();
        // 自动验证 exp 和 nbf
        validation.validate_exp = true;
        validation.validate_nbf = true;

        let token_data = decode::<Claims>(token, &decoding_key, &validation)
            .map_err(|e| match e.kind() {
                jsonwebtoken::errors::ErrorKind::ExpiredSignature => {
                    AuthError::TokenExpired
                }
                _ => AuthError::InvalidToken(e.to_string()),
            })?;

        Ok(token_data.claims)
    }

    /// 获取 Access Token 有效期(秒)
    pub fn access_expiry(&self) -> usize {
        self.config.access_expiry_secs
    }

    /// 获取 Refresh Token 有效期(秒)
    pub fn refresh_expiry(&self) -> usize {
        self.config.refresh_expiry_secs
    }
}

4. JWT 认证中间件实现

在 Axum 中,实现认证中间件有两种主流方式:

  1. Tower Layer:在路由层面统一拦截,适合全局或路由组级别的认证
  2. Axum Extractor:在 Handler 参数中声明,适合细粒度的路由保护

本文两种方式都会实现,你可以根据场景灵活选择。

4.1 使用 Tower Layer 构建中间件

Tower 的 LayerService 模型是 Axum 中间件的基础。我们实现一个 AuthLayer,在请求进入 Handler 之前完成 Token 验证。

src/auth/middleware.rs

use axum::{
    extract::Request,
    response::{IntoResponse, Response},
};
use futures_util::future::BoxFuture;
use std::task::{Context, Poll};
use tower::{Layer, Service};
use tracing::warn;

use super::token_service::TokenService;
use super::error::AuthError;

/// 认证 Layer
#[derive(Clone)]
pub struct AuthLayer {
    token_service: TokenService,
}

impl AuthLayer {
    pub fn new(token_service: TokenService) -> Self {
        Self { token_service }
    }
}

impl<S> Layer<S> for AuthLayer {
    type Service = AuthService<S>;

    fn layer(&self, inner: S) -> Self::Service {
        AuthService {
            inner,
            token_service: self.token_service.clone(),
        }
    }
}

/// 认证 Service(Tower 中间件)
#[derive(Clone)]
pub struct AuthService<S> {
    inner: S,
    token_service: TokenService,
}

impl<S> Service<Request> for AuthService<S>
where
    S: Service<Request, Response = Response> + Send + 'static,
    S::Future: Send + 'static,
{
    type Response = S::Response;
    type Error = S::Error;
    type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;

    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.inner.poll_ready(cx)
    }

    fn call(&mut self, req: Request) -> Self::Future {
        let token_service = self.token_service.clone();
        let future = self.inner.call(req);

        Box::pin(async move {
            // 注意:这里需要在 future 执行之前提取并验证 Token
            // 但由于我们无法在 future 执行前修改 Request,
            // 实际实现中建议使用 Extractor 方式(见 4.2 节)
            // 或使用 tower 的 Service 组合模式
            future.await
        })
    }
}

💡 实际生产建议:由于 Tower 中间件的复杂性,更推荐使用 Axum Extractor 方式来实现认证(见下节),代码更简洁、类型更安全。

4.2 使用 Axum Extractor 提取认证信息

Axum 的 FromRequestParts trait 允许我们从请求中提取自定义类型。这是实现 JWT 认证最优雅的方式。

src/auth/extractor.rs

use axum::{
    async_trait,
    extract::FromRequestParts,
    http::{request::Parts, StatusCode},
    response::{IntoResponse, Response},
};
use tower_http::validate_request::ValidateRequestHeaderLayer;

use super::claims::Claims;
use super::token_service::TokenService;
use super::error::AuthError;

/// 认证用户信息提取器
/// 
/// 用法:在 Handler 参数中声明 `auth: AuthUser`,
/// 自动从 Authorization: Bearer <token> 头中提取并验证 Token
pub struct AuthUser {
    pub claims: Claims,
}

impl AuthUser {
    pub fn user_id(&self) -> Result<uuid::Uuid, uuid::Error> {
        self.claims.user_id()
    }
    
    pub fn role(&self) -> &str {
        &self.claims.role
    }
}

/// 实现 FromRequestParts,使 AuthUser 可作为 Axum 提取器使用
#[async_trait]
impl<S> FromRequestParts<S> for AuthUser
where
    S: Send + Sync,
{
    type Rejection = AuthError;

    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
        // 1. 从请求头中提取 Token
        let token = extract_token_from_header(parts)
            .map_err(|_| AuthError::MissingToken)?;

        // 2. 从 State 中获取 TokenService
        // 注意:这里需要从 parts.extensions 中获取
        // 在实际项目中,建议通过 axum::Extension 传递
        let token_service = parts
            .extensions
            .get::<TokenService>()
            .ok_or(AuthError::ConfigError("TokenService 未初始化".to_string()))?;

        // 3. 验证 Token
        let claims = token_service.verify_token(&token)?;

        Ok(AuthUser { claims })
    }
}

/// 从 Authorization 头中提取 Bearer Token
fn extract_token_from_header(parts: &Parts) -> Result<String, AuthError> {
    let auth_header = parts
        .headers
        .get("authorization")
        .ok_or(AuthError::MissingToken)?
        .to_str()
        .map_err(|_| AuthError::InvalidToken("无效的请求头格式".to_string()))?;

    if !auth_header.starts_with("Bearer ") {
        return Err(AuthError::InvalidToken("无效的认证方案".to_string()));
    }

    let token = auth_header.trim_start_matches("Bearer ").trim();
    if token.is_empty() {
        return Err(AuthError::InvalidToken("Token 为空".to_string()));
    }

    Ok(token.to_string())
}

/// 可选认证提取器(允许未认证的请求)
pub struct OptionalAuthUser(pub Option<AuthUser>);

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

    async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
        match AuthUser::from_request_parts(parts, state).await {
            Ok(auth) => Ok(OptionalAuthUser(Some(auth))),
            Err(AuthError::MissingToken) => Ok(OptionalAuthUser(None)),
            Err(e) => Err(e),
        }
    }
}

提取器的使用示例

// 受保护的路由 - 需要认证
async fn protected_route(auth: AuthUser) -> String {
    format!("Hello, user {}", auth.user_id().unwrap())
}

// 可选认证的路由
async fn optional_route(auth: OptionalAuthUser) -> String {
    match auth.0 {
        Some(user) => format!("Authenticated: {}", user.user_id().unwrap()),
        None => "Guest".to_string(),
    }
}

5. 路由保护与登录/注册接口

5.1 用户注册与登录

src/handlers/auth.rs

use axum::{Json, http::StatusCode};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::auth::{TokenService, AuthError, Claims};
use crate::db::user::{create_user, verify_user_password};
use crate::password::PasswordHasher;

#[derive(Debug, Deserialize)]
pub struct RegisterRequest {
    pub email: String,
    pub password: String,
}

#[derive(Debug, Deserialize)]
pub struct LoginRequest {
    pub email: String,
    pub password: String,
}

#[derive(Debug, Serialize)]
pub struct AuthResponse {
    pub access_token: String,
    pub refresh_token: String,
    pub token_type: String,
    pub expires_in: usize,
}

/// 用户注册
pub async fn register(
    Json(payload): Json<RegisterRequest>,
) -> Result<StatusCode, (StatusCode, String)> {
    // 验证邮箱和密码...
    // 创建用户(密码哈希存储)...
    Ok(StatusCode::CREATED)
}

/// 用户登录 - 颁发 Token
pub async fn login(
    Json(payload): Json<LoginRequest>,
    token_service: TokenService,
    hasher: PasswordHasher,
) -> Result<Json<AuthResponse>, (StatusCode, String)> {
    // 1. 验证用户密码(复用上一篇文章的逻辑)
    let user = verify_user_password(&pool, &payload.email, &payload.password)
        .await
        .map_err(|_| (StatusCode::UNAUTHORIZED, "邮箱或密码错误".to_string()))?;

    let Some(user) = user else {
        return Err((StatusCode::UNAUTHORIZED, "邮箱或密码错误".to_string()));
    };

    // 2. 生成 Access Token(有效期 15 分钟)
    let access_token = token_service
        .generate_access_token(user.id, "user".to_string())
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    // 3. 生成 Refresh Token(有效期 7 天)
    let refresh_token = token_service
        .generate_refresh_token(user.id, "user".to_string())
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    Ok(Json(AuthResponse {
        access_token,
        refresh_token,
        token_type: "Bearer".to_string(),
        expires_in: token_service.access_expiry(),
    }))
}

5.2 受保护路由与中间件挂载

src/main.rs

use axum::{
    Router,
    routing::{get, post},
    middleware,
    extract::Extension,
};
use tower_http::cors::CorsLayer;

mod auth;
mod handlers;
mod db;

use auth::{TokenService, TokenConfig, AuthUser};
use handlers::auth::{login, register};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    dotenvy::dotenv().ok();
    tracing_subscriber::fmt::init();

    // 1. 初始化 Token 服务
    let token_config = TokenConfig::from_env()?;
    let token_service = TokenService::new(token_config);

    // 2. 构建路由
    let app = Router::new()
        // 公开路由(无需认证)
        .route("/api/auth/register", post(register))
        .route("/api/auth/login", post(login))
        // 受保护路由(需要认证)
        .route("/api/user/me", get(get_current_user))
        .route_layer(middleware::from_fn(auth_middleware))
        // 共享状态
        .layer(Extension(token_service))
        .layer(CorsLayer::permissive());

    // 启动服务器...
    Ok(())
}

/// 使用中间件函数保护路由
async fn auth_middleware<B>(
    mut req: axum::extract::Request<B>,
    next: axum::middleware::Next<B>,
) -> Result<axum::response::Response, axum::response::Response> {
    // 从请求头提取并验证 Token
    // 如果验证通过,将 Claims 存入请求扩展
    // 如果验证失败,返回 401
    Ok(next.run(req).await)
}

/// 获取当前用户信息(受保护路由)
async fn get_current_user(auth: AuthUser) -> Json<serde_json::Value> {
    Json(serde_json::json!({
        "user_id": auth.user_id().unwrap(),
        "role": auth.role(),
    }))
}

6. Refresh Token 机制实现

Access Token 有效期短(如 15 分钟),Refresh Token 有效期长(如 7 天)。当 Access Token 过期时,客户端可用 Refresh Token 换取新的 Access Token。

src/handlers/refresh.rs

use axum::{Json, http::StatusCode};
use serde::{Deserialize, Serialize};

use crate::auth::{TokenService, AuthError};

#[derive(Debug, Deserialize)]
pub struct RefreshRequest {
    pub refresh_token: String,
}

#[derive(Debug, Serialize)]
pub struct RefreshResponse {
    pub access_token: String,
    pub token_type: String,
    pub expires_in: usize,
}

/// 刷新 Access Token
pub async fn refresh_token(
    Json(payload): Json<RefreshRequest>,
    token_service: TokenService,
) -> Result<Json<RefreshResponse>, (StatusCode, String)> {
    // 1. 验证 Refresh Token
    let claims = token_service
        .verify_token(&payload.refresh_token)
        .map_err(|e| match e {
            AuthError::TokenExpired => {
                (StatusCode::UNAUTHORIZED, "Refresh Token 已过期,请重新登录".to_string())
            }
            _ => (StatusCode::UNAUTHORIZED, "无效的 Refresh Token".to_string()),
        })?;

    // 2. 从 Claims 中提取用户信息
    let user_id = claims.user_id()
        .map_err(|_| (StatusCode::BAD_REQUEST, "无效的用户ID".to_string()))?;

    // 3. 生成新的 Access Token
    let new_access_token = token_service
        .generate_access_token(user_id, claims.role)
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    Ok(Json(RefreshResponse {
        access_token: new_access_token,
        token_type: "Bearer".to_string(),
        expires_in: token_service.access_expiry(),
    }))
}

将刷新路由加入 Router

.route("/api/auth/refresh", post(refresh_token))

💡 高级实践:在生产环境中,Refresh Token 应存储在 Redis 等服务器端存储中,以便实现主动吊销。登出时,将 Refresh Token 加入黑名单,使其无法再换取新的 Access Token。


7. 错误处理与统一响应

src/auth/error.rs

use axum::{
    http::StatusCode,
    response::{IntoResponse, Response},
    Json,
};
use serde_json::json;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum AuthError {
    #[error("Token 缺失")]
    MissingToken,
    #[error("Token 已过期")]
    TokenExpired,
    #[error("无效的 Token: {0}")]
    InvalidToken(String),
    #[error("Token 创建失败: {0}")]
    TokenCreationError(String),
    #[error("配置错误: {0}")]
    ConfigError(String),
}

impl IntoResponse for AuthError {
    fn into_response(self) -> Response {
        let (status, message) = match self {
            AuthError::MissingToken => (StatusCode::UNAUTHORIZED, "缺少认证令牌".to_string()),
            AuthError::TokenExpired => (StatusCode::UNAUTHORIZED, "令牌已过期,请重新登录".to_string()),
            AuthError::InvalidToken(msg) => (StatusCode::UNAUTHORIZED, format!("无效的令牌: {}", msg)),
            AuthError::TokenCreationError(msg) => {
                (StatusCode::INTERNAL_SERVER_ERROR, format!("令牌生成失败: {}", msg))
            }
            AuthError::ConfigError(msg) => {
                (StatusCode::INTERNAL_SERVER_ERROR, format!("服务配置错误: {}", msg))
            }
        };

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

        (status, body).into_response()
    }
}

8. 安全最佳实践

实践 说明
强密钥 JWT_SECRET 使用至少 32 字节的密码学安全随机字符串
短有效期 Access Token 有效期 ≤ 15 分钟,降低泄露风险
始终验证 exp jsonwebtoken 默认验证 exp,不要禁用
敏感信息不入 Token 永远不要在 JWT 中存放密码、身份证号等敏感信息
使用 HTTPS 生产环境必须使用 HTTPS,防止 Token 被中间人截获
恒定时间比较 jsonwebtoken::decode 内部使用恒定时间比较,防止时序攻击
令牌吊销 使用 Redis 存储黑名单(基于 jti 声明)实现主动吊销
时钟偏差容忍 设置 Validation::leeway(60) 解决分布式系统时钟漂移问题

设置时钟偏差容忍

let mut validation = Validation::default();
validation.leeway = 60; // 允许 60 秒的时钟偏差

9. 总结

本文完成了 Rust + Axum 框架下 JWT 认证中间件的完整实现,涵盖以下核心内容:

模块 关键实现 说明
Claims 定义 自定义 Claims 结构体 类型安全的 Token 载荷
Token 服务 TokenService 封装 生成、验证、配置管理
认证中间件 Tower Layer + Axum Extractor 两种方式灵活选择
路由保护 AuthUser 提取器 声明式路由守卫
Token 刷新 Refresh Token 机制 双令牌延长会话
错误处理 统一 AuthError 类型安全的错误响应

核心代码结构

src/
├── auth/
│   ├── mod.rs           # 模块导出
│   ├── claims.rs        # Claims 定义
│   ├── token_service.rs # Token 生成/验证
│   ├── extractor.rs     # AuthUser 提取器
│   ├── middleware.rs    # Tower Layer 中间件
│   └── error.rs         # 统一错误类型
├── handlers/
│   ├── auth.rs          # 注册/登录
│   └── refresh.rs       # Token 刷新
└── main.rs              # 路由组装与启动

下一步建议

  1. 集成 Redis:存储 Refresh Token 并实现主动吊销
  2. 添加 RBAC:基于 role Claim 实现细粒度权限控制
  3. OIDC 集成:支持 JWKS 远程密钥验证
  4. 添加监控:记录 Token 验证失败的安全事件

如果本文对你有帮助,欢迎点赞👍、收藏⭐、关注🔔!有任何问题欢迎在评论区交流💬。

标签Rust JWT Axum 认证中间件 Token Refresh Token Tower Web安全 API开发 生产级应用

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐