当前位置: 首页 > news >正文

【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加空格
在这里插入图片描述

http://www.lryc.cn/news/577193.html

相关文章:

  • C#跨线程共享变量指南:从静态变量到AsyncLocal的深度解析
  • Excel转pdf实现动态数据绑定
  • Java设计模式之结构型模式(外观模式)介绍与说明
  • BUUCTF在线评测-练习场-WebCTF习题[MRCTF2020]你传你[特殊字符]呢1-flag获取、解析
  • FPGA实现CameraLink视频解码转SDI输出,基于LVDS+GTX架构,提供2套工程源码和技术支持
  • AWS 开源 Strands Agents SDK,简化 AI 代理开发流程
  • python:运行时报错 No module named flask
  • CAU数据挖掘 支持向量机
  • Instruct-GPT奖励模型的损失函数与反向传播机制解析
  • Linux 系统管理:高效运维与性能优化
  • C语言之文件操作详解(文件打开关闭、顺序/随机读写)
  • 本地部署OpenHands AI助手,自动化编程提升开发效率
  • 如何提升 iOS App 全链路体验?从启动到退出的优化调试流程
  • Objective-c把字符解析成字典
  • python包管理工具uv VS pip
  • 在Flutter中生成App Bundle并上架Google Play
  • camera调试:安卓添加xml注册
  • 二刷 苍穹外卖day09
  • 【硬核数学 · LLM篇】3.1 Transformer之心:自注意力机制的线性代数解构《从零构建机器学习、深度学习到LLM的数学认知》
  • 借助 Wisdom SSH,实现 Linux 用户组与权限的精细化智能管控
  • DataGrip测试连接时出现报错解决方案
  • 谷歌高调宣布,Gemini 2.5 Pro正式版,绘制常见图表(折线图、柱状图、PPT等),国内直接使用
  • 将 h264+g711a存为 mp4文件,记录
  • uniapp+vue2 ba-tree-picker下拉项多选 树形层级选择器(支持单选、多选、父级选择、映射)
  • SAP月结问题9-FAGLL03H与损益表中研发费用金额不一致(FAGLL03H Bug)
  • 【数据结构中的位运算】
  • 堆排序实现及复杂度分析
  • AWS WebRTC:通过shell分析并发启动master后产生的日志文件
  • 腾讯云空间,高性能显卡云,安装xinference报错,pip install 空间不够用了
  • 大语言模型(LLM)笔记