【Actix Web】Rust Web开发JWT认证
✨✨ 欢迎大家来到景天科技苑✨✨
🎈🎈 养成好习惯,先赞后看哦~🎈🎈
🏆 作者简介:景天科技苑
🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。
🏆《博客》:Rust开发,Python全栈,Golang开发,云原生开发,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。所属的专栏:Rust语言通关之路
景天的主页:景天科技苑
文章目录
- Rust JWT
- 1. 介绍
- 2. JWT 概念与工作原理
- 3. 环境准备
- 4. 创建 Actix Web 项目
- 5. JWT 库选择与集成
- 6. 生成 JWT
- 7. 解析与验证 JWT
- 8. 中间件实现用户身份认证
- 9. 结合数据库实现用户注册与登录
- 10. 权限控制示例
- 11. 错误处理
- 12. 完整代码
Rust JWT
1. 介绍
在现代 Web 开发中,JWT(JSON Web Token)是一种常用的无状态认证方式。通过 JWT,服务端可以发放加密令牌给客户端,客户端每次请求时携带此令牌,服务器通过验证令牌来识别用户身份,省去了服务器端存储会话信息的负担。
Rust 作为一门性能与安全兼备的系统级编程语言,结合 Actix Web 框架开发高性能 Web 应用,成为后端开发的新趋势。
2. JWT 概念与工作原理
JWT 是一个由三部分组成的字符串,格式如下:
header.payload.signature
Header:描述算法和类型,通常是 {“alg”: “HS256”, “typ”: “JWT”}
Payload:存储用户信息与自定义声明(Claims)
Signature:对 Header 和 Payload 用密钥生成的哈希值,用于验证数据完整性和真实性
工作流程:
用户登录,服务器验证用户信息后,生成带有用户信息的 JWT 令牌返回给客户端
客户端请求时将令牌放入请求头(一般是 Authorization: Bearer )
服务器中间件截获请求,验证 JWT 的合法性和有效性
验证通过后允许访问受保护资源,否则返回未授权错误
3. 环境准备
确保本地已安装:
Rust(推荐 stable 版本)
Cargo
IDE(如 VSCode,配置好 Rust 插件)
4. 创建 Actix Web 项目
使用 Cargo 新建项目:
在 Cargo.toml 添加依赖:
[dependencies]
actix-rt = "2.10.0"
actix-web = "4.11.0"
bcrypt = "0.17.0"
chrono = "0.4.41"
dotenv = "0.15.0"
futures-util = "0.3.31"
jsonwebtoken = "9.3.1"
lazy_static = "1.5.0"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
thiserror = "2.0.12"
解释:
actix-web:Web 框架
serde、serde_json:序列化库,用于处理 JSON
jsonwebtoken:Rust JWT 库,处理 token 生成和验证
dotenv:加载 .env 文件中环境变量
actix-rt:异步运行时
5. JWT 库选择与集成
我们使用 jsonwebtoken 库,支持多种算法,使用简单。
创建 .env 文件用于存储密钥:
JWT_SECRET=your_secret_key_here
在代码中加载:
use dotenv::dotenv;
use std::env;fn main() {dotenv().ok();let secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set");println!("Secret: {}", secret);
}
6. 生成 JWT
定义数据结构和生成函数:
use jsonwebtoken::{encode, EncodingKey, Header};
use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};#[derive(Debug, Serialize, Deserialize)]
struct Claims {sub: String, // 用户IDexp: usize, // 过期时间 (时间戳)iat: usize, // 签发时间 (时间戳)
}fn create_jwt(user_id: &str, secret: &str) -> Result<String, jsonwebtoken::errors::Error> {let start = SystemTime::now();let iat = start.duration_since(UNIX_EPOCH).unwrap().as_secs() as usize;let exp = iat + 60 * 60; // 1小时后过期let claims = Claims {sub: user_id.to_owned(),exp,iat,};let token = encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_ref()))?;Ok(token)
}
示例调用:
fn main() {dotenv().ok();let secret = env::var("JWT_SECRET").unwrap();let token = create_jwt("user123", &secret).unwrap();println!("Token: {}", token);
}
7. 解析与验证 JWT
验证逻辑需要用 DecodingKey 和 Validation:
use jsonwebtoken::{decode, DecodingKey, Validation};fn validate_jwt(token: &str, secret: &str) -> Result<Claims, jsonwebtoken::errors::Error> {let token_data = decode::<Claims>(token,&DecodingKey::from_secret(secret.as_ref()),&Validation::default(),)?;Ok(token_data.claims)
}
8. 中间件实现用户身份认证
为简化流程,我们实现一个验证 JWT 的中间件。Actix Web 4 提供了 FromRequest trait,可以自定义请求提取器。
use actix_web::{dev::Payload, Error, FromRequest, HttpRequest};
use futures_util::future::{ready, Ready};pub struct AuthenticatedUser {pub user_id: String,
}impl FromRequest for AuthenticatedUser {type Error = Error;type Future = Ready<Result<Self, Self::Error>>;fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {use actix_web::http::header;let secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set");if let Some(auth_header) = req.headers().get(header::AUTHORIZATION) {if let Ok(auth_str) = auth_header.to_str() {if auth_str.starts_with("Bearer ") {let token = &auth_str[7..];match validate_jwt(token, &secret) {Ok(claims) => {return ready(Ok(AuthenticatedUser { user_id: claims.sub }));}Err(_) => {}}}}}// 认证失败返回错误ready(Err(actix_web::error::ErrorUnauthorized("Invalid token")))}
}使用示例:
use actix_web::{get, web, App, HttpServer, Responder};#[get("/protected")]
async fn protected_route(user: AuthenticatedUser) -> impl Responder {format!("Welcome, user: {}", user.user_id)
}#[actix_web::main]
async fn main() -> std::io::Result<()> {dotenv().ok();HttpServer::new(|| App::new().service(protected_route)).bind(("127.0.0.1", 8080))?.run().await
}
9. 结合数据库实现用户注册与登录
真实项目中,用户信息保存在数据库(例如 PostgreSQL、MySQL、SQLite)。这里示范使用内存模拟。
用户结构与存储
use std::sync::Mutex;
use lazy_static::lazy_static;#[derive(Debug, Clone)]
struct User {username: String,password_hash: String,
}lazy_static! {static ref USERS: Mutex<Vec<User>> = Mutex::new(Vec::new());
}
注册接口
use actix_web::{post, web, HttpResponse};
use serde::Deserialize;
use bcrypt::{hash, verify, DEFAULT_COST};#[derive(Deserialize)]
struct RegisterInfo {username: String,password: String,
}#[post("/register")]
async fn register(info: web::Json<RegisterInfo>) -> HttpResponse {let hashed = hash(&info.password, DEFAULT_COST).unwrap();let mut users = USERS.lock().unwrap();if users.iter().any(|u| u.username == info.username) {return HttpResponse::BadRequest().body("User exists");}users.push(User {username: info.username.clone(),password_hash: hashed,});HttpResponse::Ok().body("Registered successfully")
}
登录接口
#[derive(Deserialize)]
struct LoginInfo {username: String,password: String,
}#[post("/login")]
async fn login(info: web::Json<LoginInfo>) -> HttpResponse {let users = USERS.lock().unwrap();if let Some(user) = users.iter().find(|u| u.username == info.username) {if verify(&info.password, &user.password_hash).unwrap() {let secret = env::var("JWT_SECRET").unwrap();let token = create_jwt(&user.username, &secret).unwrap();return HttpResponse::Ok().json(serde_json::json!({ "token": token }));}}HttpResponse::Unauthorized().body("Invalid credentials")
}
整合到 main:
HttpServer::new(|| {App::new().service(register).service(login).service(protected_route)
})
10. 权限控制示例
扩展 Claims 结构,加入角色:
#[derive(Debug, Serialize, Deserialize)]
struct Claims {sub: String,exp: usize,iat: usize,role: String,
}
生成 JWT 时设置角色:
let claims = Claims {sub: user_id.to_owned(),exp,iat,role: "admin".to_string(),
};
中间件中解析 role,根据角色限制接口访问。
11. 错误处理
使用 Actix Web 自带的错误处理机制,封装错误响应:
use actix_web::{HttpResponse, ResponseError};
use thiserror::Error;#[derive(Debug, Error)]
pub enum JwtError {#[error("Invalid token")]InvalidToken,#[error("Token expired")]Expired,
}impl ResponseError for JwtError {fn error_response(&self) -> HttpResponse {match *self {JwtError::InvalidToken => HttpResponse::Unauthorized().body("Invalid token"),JwtError::Expired => HttpResponse::Unauthorized().body("Token expired"),}}
}
12. 完整代码
use actix_web::{ get, post, web, App, Error, HttpRequest, HttpResponse, HttpServer, Responder };
use actix_web::dev::Payload;
use actix_web::http::header;
use bcrypt::{ hash, verify, DEFAULT_COST }; // 密码哈希
use dotenv::dotenv;
use futures_util::future::{ ready, Ready };
use jsonwebtoken::{ decode, encode, DecodingKey, EncodingKey, Header, Validation };
use lazy_static::lazy_static;
use serde::{ Deserialize, Serialize };
use std::env;
use std::sync::Mutex;
use std::time::{ SystemTime, UNIX_EPOCH };
use thiserror::Error;#[derive(Debug, Serialize, Deserialize)]
struct Claims {sub: String, // 用户IDexp: usize, // 过期时间iat: usize, // 签发时间role: String, // 用户角色
}// 创建 JWT 并返回其字符串表示
fn create_jwt(user_id: &str,role: &str,secret: &str
) -> Result<String, jsonwebtoken::errors::Error> {let start = SystemTime::now();let iat = start.duration_since(UNIX_EPOCH).unwrap().as_secs() as usize;let exp = iat + 60 * 60; // 1小时有效期let claims = Claims {sub: user_id.to_owned(),exp,iat,role: role.to_owned(),};// 使用 HS256 算法对声明进行签名encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_ref()))
}// 验证 JWT 并返回其中的声明
fn validate_jwt(token: &str, secret: &str) -> Result<Claims, JwtError> {let validation = Validation::default();let token_data = decode::<Claims>(token,&DecodingKey::from_secret(secret.as_ref()),&validation).map_err(|err| {match *err.kind() {jsonwebtoken::errors::ErrorKind::ExpiredSignature => JwtError::Expired,_ => JwtError::InvalidToken,}})?;Ok(token_data.claims)
}// JWT 验证错误
#[derive(Debug, Error)]
pub enum JwtError {#[error("Invalid token")]InvalidToken,#[error("Token expired")]Expired,
}// 将 JWT 错误转换为 HTTP 响应
impl actix_web::ResponseError for JwtError {fn error_response(&self) -> HttpResponse {match self {JwtError::InvalidToken => HttpResponse::Unauthorized().body("Invalid token"),JwtError::Expired => HttpResponse::Unauthorized().body("Token expired"),}}
}// 已认证的用户信息
pub struct AuthenticatedUser {pub user_id: String,pub role: String,
}// 为 AuthenticatedUser 实现 FromRequest,以便在处理程序中使用
impl actix_web::FromRequest for AuthenticatedUser {type Error = Error;type Future = Ready<Result<Self, Self::Error>>;fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {// 从环境变量中获取 JWT 秘钥let secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set");// 从请求头中获取 JWTif let Some(auth_header) = req.headers().get(header::AUTHORIZATION) {if let Ok(auth_str) = auth_header.to_str() {// 检查是否为 Bearer 类型的 JWTif auth_str.starts_with("Bearer ") {// 提取 JWTlet token = &auth_str[7..];// 验证 JWTmatch validate_jwt(token, &secret) {// JWT 验证成功,返回已认证的用户信息Ok(claims) => {return ready(Ok(AuthenticatedUser {user_id: claims.sub,role: claims.role,}));}Err(_) => {}}}}}// JWT 验证失败,返回错误ready(Err(actix_web::error::ErrorUnauthorized("Invalid or missing token")))}
}// 用户信息
#[derive(Debug, Clone)]
struct User {username: String,password_hash: String,role: String,
}// 存储用户信息的全局变量
lazy_static! {static ref USERS: Mutex<Vec<User>> = Mutex::new(Vec::new());
}// 注册用户信息
#[derive(Deserialize)]
struct RegisterInfo {username: String,password: String,role: Option<String>, // 可选,默认普通用户
}// 注册用户
#[post("/register")]
async fn register(info: web::Json<RegisterInfo>) -> impl Responder {let role = info.role.clone().unwrap_or_else(|| "user".to_string());let hashed = match hash(&info.password, DEFAULT_COST) {Ok(h) => h,Err(_) => {return HttpResponse::InternalServerError().body("Error hashing password");}};let mut users = USERS.lock().unwrap();if users.iter().any(|u| u.username == info.username) {return HttpResponse::BadRequest().body("User already exists");}users.push(User {username: info.username.clone(),password_hash: hashed,role,});HttpResponse::Ok().body("User registered")
}// 登录用户信息
#[derive(Deserialize)]
struct LoginInfo {username: String,password: String,
}// 登录用户
// 登录成功后返回 JWT
#[post("/login")]
async fn login(info: web::Json<LoginInfo>) -> impl Responder {let users = USERS.lock().unwrap();if let Some(user) = users.iter().find(|u| u.username == info.username) {if verify(&info.password, &user.password_hash).unwrap_or(false) {//从.env 文件中加载 JWT 秘钥let secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set");match create_jwt(&user.username, &user.role, &secret) {Ok(token) => {return HttpResponse::Ok().json(serde_json::json!({ "token": token }));}Err(_) => {return HttpResponse::InternalServerError().body("Token creation error");}}}}HttpResponse::Unauthorized().body("Invalid username or password")
}// 保护路由
#[get("/protected")]
async fn protected_route(user: AuthenticatedUser) -> impl Responder {format!("Welcome {}, your role is {}", user.user_id, user.role)
}#[get("/admin")]
async fn admin_route(user: AuthenticatedUser) -> impl Responder {if user.role != "admin" {return HttpResponse::Forbidden().body("You are not an admin");}HttpResponse::Ok().body(format!("Hello Admin: {}", user.user_id))
}#[actix_web::main]
async fn main() -> std::io::Result<()> {// 加载 .env 文件内容到当前进程的环境变量中。dotenv().ok();//设置运行ip//获取本机iplet server_ip = "10.10.0.2";println!("{}", format!("Starting server at http://{}:8080", server_ip));HttpServer::new(|| {App::new().service(register).service(login).service(protected_route).service(admin_route)}).bind((server_ip, 8080))?.run().await
}
运行说明
准备 .env 文件并设置 JWT_SECRET
编译运行:cargo run
注册用户(POST /register),json body 示例:
{"username": "jigntian","password": "123456",
}
成功登录返回 JWT token:
访问受保护接口:
curl -H "Authorization: Bearer <token>" http://127.0.0.1:8080/protected
验证token成功
使用postman请求,也可以token前面加上Bearer加空格