【Rust + JWT 认证中间件设计与实现:从原理到生产级落地】
本文面向有Rust基础的中级开发者,系统讲解如何在 Axum 框架中构建生产级 JWT 认证中间件。涵盖 JWT 原理、Token 服务封装、Tower 中间件实现、路由守卫、Refresh Token 机制及安全最佳实践,提供完整的可运行代码。
📑 目录
- 前言
- 1. JWT 核心原理回顾
- 2. 技术选型与环境配置
- 3. Claims 定义与 Token 服务
- 4. JWT 认证中间件实现
- 5. 路由保护与登录/注册接口
- 6. Refresh Token 机制实现
- 7. 错误处理与统一响应
- 8. 安全最佳实践
- 9. 总结
前言
在上一篇文章《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(生效时间)声明,同时支持验证 sub、iss、aud 等标准声明。
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 中,实现认证中间件有两种主流方式:
- Tower Layer:在路由层面统一拦截,适合全局或路由组级别的认证
- Axum Extractor:在 Handler 参数中声明,适合细粒度的路由保护
本文两种方式都会实现,你可以根据场景灵活选择。
4.1 使用 Tower Layer 构建中间件
Tower 的 Layer 和 Service 模型是 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 # 路由组装与启动
下一步建议:
- 集成 Redis:存储 Refresh Token 并实现主动吊销
- 添加 RBAC:基于
roleClaim 实现细粒度权限控制 - OIDC 集成:支持 JWKS 远程密钥验证
- 添加监控:记录 Token 验证失败的安全事件
如果本文对你有帮助,欢迎点赞👍、收藏⭐、关注🔔!有任何问题欢迎在评论区交流💬。
标签:Rust JWT Axum 认证中间件 Token Refresh Token Tower Web安全 API开发 生产级应用
更多推荐


所有评论(0)