如何在Actix-web中间件中修改响应类型及优化JWT验证
问题解答
1. 返回JSON格式的未授权响应
actix-web默认的ErrorUnauthorized返回纯文本响应,要改成JSON格式,需要自定义错误类型并实现ResponseError trait,步骤如下:
定义自定义错误类型
创建枚举表示JWT验证错误,并实现响应转换逻辑:
use actix_web::{http::StatusCode, HttpResponse, ResponseError}; use serde::Serialize; #[derive(Debug, Serialize)] struct ErrorResponse { error: String, message: String, } #[derive(Debug)] enum AuthError { InvalidToken, ExpiredToken, Unauthorized, MissingAuthHeader, } impl ResponseError for AuthError { fn error_response(&self) -> HttpResponse { let (status_code, error, message) = match self { AuthError::InvalidToken => ( StatusCode::UNAUTHORIZED, "invalid_token", "提供的JWT令牌无效", ), AuthError::ExpiredToken => ( StatusCode::UNAUTHORIZED, "expired_token", "JWT令牌已过期", ), AuthError::Unauthorized => ( StatusCode::UNAUTHORIZED, "unauthorized", "无权访问此资源", ), AuthError::MissingAuthHeader => ( StatusCode::BAD_REQUEST, "missing_auth_header", "缺少Authorization请求头", ), }; HttpResponse::build(status_code) .json(ErrorResponse { error: error.to_string(), message: message.to_string(), }) } } // 实现转换为actix_web::Error impl From<AuthError> for actix_web::Error { fn from(err: AuthError) -> Self { actix_web::error::ErrorUnauthorized(err.error_response()) } }
修改中间件错误返回
在JWTMiddleware的call方法中,替换默认错误为自定义错误,并优化Header处理逻辑:
fn call(&self, req: ServiceRequest) -> Self::Future { info!("JWT verification started..."); let auth_header = req.headers().get("Authorization"); // 提前处理Header错误 let token = match auth_header { Some(header_value) => { let value_str = match header_value.to_str() { Ok(s) => s, Err(_) => return Box::pin(async move { Err(AuthError::Unauthorized.into()) }), }; value_str.strip_prefix("Bearer ").ok_or(AuthError::InvalidToken) } None => Err(AuthError::MissingAuthHeader), }; let token = match token { Ok(t) => t, Err(e) => return Box::pin(async move { Err(e.into()) }), }; // 提前加载的密钥(建议在main中初始化全局变量) let secret_key = var("SECRET_KEY").expect("SECRET_KEY not set"); let decoding_key = DecodingKey::from_secret(secret_key.as_ref()); let fut = self.service.call(req); match decode::<Claims>(&token, &decoding_key, &Validation::new(Algorithm::HS256)) { Ok(_) => Box::pin(async move { Ok(fut.await?) }), Err(err) => { let auth_error = match err.kind() { ErrorKind::InvalidToken => AuthError::InvalidToken, ErrorKind::ExpiredSignature => AuthError::ExpiredToken, _ => AuthError::Unauthorized, }; Box::pin(async move { Err(auth_error.into()) }) } } }
2. Rust中更简便的JWT验证方式
你的实现已经基础正确,可通过以下方式优化或简化:
优化现有代码
提前加载环境变量:不要在
call方法中重复调用dotenv()和var(),在main函数中初始化全局变量:use once_cell::sync::Lazy; dotenv().expect("Failed to load .env file"); pub static SECRET_KEY: Lazy<String> = Lazy::new(|| var("SECRET_KEY").expect("SECRET_KEY not set"));需在
Cargo.toml添加依赖:once_cell = "1.19.0"避免unwrap:用
expect()或错误处理替代unwrap(),防止panic。
使用提取器简化验证
实现FromRequest trait做JWT提取器,可直接在路由函数中获取验证后的Claims:
use actix_web::{dev::Payload, FromRequest, HttpRequest}; use std::future::{ready, Ready}; pub struct JwtClaims(pub Claims); impl FromRequest for JwtClaims { type Error = actix_web::Error; type Future = Ready<Result<Self, Self::Error>>; fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future { let auth_header = req.headers().get("Authorization"); let token = match auth_header { Some(header) => header.to_str().map_err(|_| AuthError::Unauthorized)?, None => return ready(Err(AuthError::MissingAuthHeader.into())), }; let token = token.strip_prefix("Bearer ").ok_or(AuthError::InvalidToken)?; let decoding_key = DecodingKey::from_secret(SECRET_KEY.as_ref()); let validation = Validation::new(Algorithm::HS256); match decode::<Claims>(token, &decoding_key, &validation) { Ok(claims) => ready(Ok(JwtClaims(claims.claims))), Err(err) => { let auth_error = match err.kind() { ErrorKind::InvalidToken => AuthError::InvalidToken, ErrorKind::ExpiredSignature => AuthError::ExpiredToken, _ => AuthError::Unauthorized, }; ready(Err(auth_error.into())) } } } }
路由中直接使用:
#[get("/protected")] async fn protected_route(claims: JwtClaims) -> HttpResponse { HttpResponse::Ok().json(claims.0) }
3. 中间件vs工具函数:推荐方案
中间件优势
- 统一处理多路由验证,符合DRY原则,减少重复代码
- 全局控制验证规则,统一错误响应格式
- 维护成本低,无需在每个控制器中重复编写验证逻辑
工具函数优势
- 适合少数路由需要自定义验证逻辑的场景
- 局部使用更灵活,无需全局配置
推荐选择
优先使用中间件,因为JWT验证是API通用逻辑,中间件能集中管理规则和响应,提升可维护性。若有特殊路由需要自定义验证,可在中间件中排除这些路由,再单独用工具函数处理。
内容的提问来源于stack exchange,提问作者Tycho
相关产品推荐
相关产品推荐

