< 返回版块

llc-993 发表于 2025-12-03 20:45

Tags:分布式、Token 验证、Session 管理、Nonce 防重放攻击、Refresh Token 刷新机制、跨服务 Session 共享

sa-token-rust 是一个轻量级、高性能的 Rust 认证授权框架,灵感来源于 Java 生态中广受欢迎的 sa-token 框架。

该框架专为 Rust Web 应用设计,提供了完整的认证(Authentication)和授权(Authorization)解决方案,帮助开发者快速构建安全的 Web 应用系统。

核心定位

  • 轻量级: 核心功能精简,不依赖重型库,快速编译
  • 高性能: 零拷贝设计,充分利用 Rust 的性能优势,支持异步/等待(async/await)
  • 易用性: 提供过程宏和工具类,简化集成流程,降低学习成本
  • 灵活性: 支持多种存储后端和 Web 框架,适配不同业务场景

✨ 特性

  • 🚀 多框架支持: Axum, Actix-web, Poem, Rocket, Warp, Salvo, Tide, Gotham, Ntex
  • 🔐 完整的认证: 登录、登出、Token 验证、Session 管理
  • 🛡️ 细粒度授权: 基于权限和角色的访问控制
  • 💾 灵活存储: 内存、Redis 和数据库存储后端
  • 🎯 易于使用: 过程宏和工具类简化集成
  • 高性能: 零拷贝设计,支持 async/await
  • 🔧 高度可配置: Token 超时、Cookie 选项、自定义 Token 名称
  • 🎧 事件监听: 监听登录、登出、踢出下线等认证事件
  • 🔑 JWT 支持: 完整的 JWT (JSON Web Token) 实现,支持多种算法
  • 🔒 安全特性: Nonce 防重放攻击、Refresh Token 刷新机制
  • 🌐 OAuth2 支持: 完整的 OAuth2 授权码模式实现
  • 🌐 WebSocket 认证: 安全的 WebSocket 连接认证,支持多种 Token 来源
  • 👥 在线用户管理: 实时在线状态跟踪和消息推送
  • 🔄 分布式 Session: 跨服务 Session 共享,适用于微服务架构
  • 🎫 SSO 单点登录: 完整的 SSO 实现,支持票据认证和统一登出

架构说明:

从架构图中可以看出,sa-token-rust 采用了分层设计理念:

  1. 核心层(sa-token-core):提供所有认证授权的核心逻辑,包括 Token 管理、Session 管理、权限控制等。这一层与具体的 Web 框架无关,保证了核心功能的复用性。

  2. 适配层(sa-token-adapter):定义了存储和请求/响应的抽象接口,使得核心层可以适配不同的存储后端和 Web 框架。

  3. 插件层(sa-token-plugin-*):针对不同 Web 框架的集成插件,每个插件都实现了框架特定的中间件和提取器,但对外提供统一的 API。

  4. 存储层(sa-token-storage-*):多种存储后端实现,包括内存存储、Redis 存储和数据库存储,用户可以根据实际需求选择。

  5. 工具层(sa-token-macro):提供过程宏,简化开发者的使用,通过注解式编程实现认证授权的声明式配置。

这种分层架构设计的优势在于:

  • 高内聚低耦合:每一层只关注自己的职责,层与层之间通过接口交互
  • 易于扩展:可以轻松添加新的框架插件或存储后端
  • 框架无关:核心功能不依赖任何 Web 框架,保证了代码的可移植性

🎯 核心组件

1. sa-token-core

核心认证授权逻辑:

  • SaTokenManager: Token 和 Session 操作的主管理器
  • StpUtil: 提供简化 API 的工具类 (文档)
  • Token 生成、验证和刷新
  • 多种 Token 风格(UUID、Random、JWT、Hash、Timestamp、Tik)
  • Session 管理
  • 权限和角色检查
  • 事件监听系统 (文档)
  • JWT 支持,多种算法 (JWT 指南)
  • 安全特性:Nonce 防重放攻击、Refresh Token 刷新机制
  • OAuth2 授权码模式 (OAuth2 指南)
  • WebSocket 认证 (WebSocket 指南)
  • 在线用户管理和实时推送 (在线用户指南)
  • 微服务分布式 Session (分布式 Session 指南)
  • SSO 单点登录 (SSO 指南)

2. sa-token-adapter

框架集成的抽象层:

  • SaStorage: Token 和 Session 的存储接口
  • SaRequest / SaResponse: 请求/响应抽象

3. sa-token-macro

用于注解式认证的过程宏:

  • #[sa_check_login]: 要求登录
  • #[sa_check_permission("user:list")]: 检查权限 (匹配规则)
  • #[sa_check_role("admin")]: 检查角色
  • #[sa_check_permissions_and(...)]: 检查多个权限(AND)
  • #[sa_check_permissions_or(...)]: 检查多个权限(OR)
  • #[sa_ignore]: 跳过认证

4. Web 框架插件

支持的框架:Axum, Actix-web, Poem, Rocket, Warp, Salvo, Tide, Gotham, Ntex

所有插件都提供:

  • 使用 Builder 模式的状态管理
  • 双重中间件(基础 + 强制登录)
  • 三种提取器(必须、可选、LoginId)
  • 请求/响应适配器
  • 从 Header/Cookie/Query 提取 Token
  • Bearer Token 支持

🚀 快速开始

⚡ 简化使用方式(推荐)

新功能! 只需一个依赖即可导入所有功能:

[dependencies]
# 一站式包 - 包含核心、宏和存储
sa-token-plugin-axum = "0.1.11"  # 默认:内存存储
tokio = { version = "1", features = ["full"] }
axum = "0.8"

一行导入:

use sa_token_plugin_axum::*;  // ✨ 你需要的一切!

// 现在你可以直接使用:
// - SaTokenManager, StpUtil
// - MemoryStorage, RedisStorage(通过 features)
// - 所有宏:#[sa_check_login], #[sa_check_permission]
// - JWT, OAuth2, WebSocket, 在线用户等

通过 features 选择存储后端:

# Redis 存储
sa-token-plugin-axum = { version = "0.1.11", features = ["redis"] }

# 多个存储后端
sa-token-plugin-axum = { version = "0.1.11", features = ["memory", "redis"] }

# 所有存储后端
sa-token-plugin-axum = { version = "0.1.11", features = ["full"] }

可用的 features:

  • memory(默认):内存存储
  • redis:Redis 存储
  • database:数据库存储
  • full:所有存储后端

可用的插件:

  • sa-token-plugin-axum - Axum 框架
  • sa-token-plugin-actix-web - Actix-web 框架
  • sa-token-plugin-poem - Poem 框架
  • sa-token-plugin-rocket - Rocket 框架
  • sa-token-plugin-warp - Warp 框架

📦 传统使用方式(高级)

如果你喜欢细粒度控制,仍然可以分别导入各个包:

[dependencies]
sa-token-core = "0.1.11"
sa-token-storage-memory = "0.1.11"
sa-token-plugin-axum = "0.1.11"
tokio = { version = "1", features = ["full"] }
axum = "0.8"

2. 初始化 sa-token

方式 A: 使用内存存储(开发环境)

使用简化导入:

use sa_token_plugin_axum::*;  // ✨ 一行导入
use std::sync::Arc;

#[tokio::main]
async fn main() {
    // 创建状态(StpUtil 会自动初始化)
    let state = SaTokenState::builder()
        .storage(Arc::new(MemoryStorage::new()))  // 已重新导出!
        .token_name("Authorization")
        .timeout(86400)  // 24 小时
        .build();
    
    // StpUtil 已就绪,可以直接使用!
    // 你的应用代码...
}

方式 B: 使用 Redis 存储(生产环境)

添加 Redis feature 到依赖:

[dependencies]
sa-token-plugin-axum = { version = "0.1.11", features = ["redis"] }

使用简化导入:

use sa_token_plugin_axum::*;  // ✨ RedisStorage 已包含!
use std::sync::Arc;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 连接 Redis(带密码)
    let storage = RedisStorage::new(
        "redis://:Aq23-hjPwFB3mBDNFp3W1@localhost:6379/0",
        "sa-token:"
    ).await?;
    
    let state = SaTokenState::builder()
        .storage(Arc::new(storage))
        .timeout(86400)
        .build();
    
    Ok(())
}

方法 2: RedisConfig 结构体(推荐配置文件读取)

use sa_token_storage_redis::{RedisStorage, RedisConfig};
use sa_token_plugin_axum::SaTokenState;
use std::sync::Arc;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = RedisConfig {
        host: "localhost".to_string(),
        port: 6379,
        password: Some("Aq23-hjPwFB3mBDNFp3W1".to_string()),
        database: 0,
        pool_size: 10,
    };
    
    let storage = RedisStorage::from_config(config, "sa-token:").await?;
    
    let state = SaTokenState::builder()
        .storage(Arc::new(storage))
        .timeout(86400)
        .build();
    
    Ok(())
}

方法 3: Builder 构建器(最灵活)

use sa_token_storage_redis::RedisStorage;
use sa_token_plugin_axum::SaTokenState;
use std::sync::Arc;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let storage = RedisStorage::builder()
        .host("localhost")
        .port(6379)
        .password("Aq23-hjPwFB3mBDNFp3W1")
        .database(0)
        .key_prefix("sa-token:")
        .build()
        .await?;
    
    let state = SaTokenState::builder()
        .storage(Arc::new(storage))
        .timeout(86400)
        .build();
    
    Ok(())
}

3. 用户登录

use sa_token_core::StpUtil;

// 用户登录
let token = StpUtil::login("user_id_10001").await?;
println!("Token: {}", token.value());

// 设置权限和角色
StpUtil::set_permissions(
    "user_id_10001",
    vec!["user:list".to_string(), "user:add".to_string()]
).await?;

StpUtil::set_roles(
    "user_id_10001",
    vec!["admin".to_string()]
).await?;

4. 检查认证(Axum 示例)

use axum::{Router, routing::get};
use sa_token_plugin_axum::{SaTokenMiddleware, LoginIdExtractor};

async fn user_info(LoginIdExtractor(login_id): LoginIdExtractor) -> String {
    format!("当前用户: {}", login_id)
}

async fn admin_panel(login_id: LoginIdExtractor) -> String {
    // 检查权限
    if !StpUtil::has_permission(&login_id.0, "admin:panel").await {
        return "无权限".to_string();
    }
    format!("欢迎管理员: {}", login_id.0)
}

let app = Router::new()
    .route("/user/info", get(user_info))
    .route("/admin/panel", get(admin_panel))
    .layer(SaTokenMiddleware::new(state));

5. 使用过程宏

use sa_token_macro::*;

#[sa_check_login]
async fn protected_route() -> &'static str {
    "此路由需要登录"
}

#[sa_check_permission("user:delete")]
async fn delete_user(user_id: String) -> &'static str {
    "用户已删除"
}

#[sa_check_role("admin")]
async fn admin_only() -> &'static str {
    "仅管理员可见内容"
}

6. 事件监听

监听登录、登出、踢出下线等认证事件:

use async_trait::async_trait;
use sa_token_core::SaTokenListener;
use std::sync::Arc;

// 创建自定义监听器
struct MyListener;

#[async_trait]
impl SaTokenListener for MyListener {
    async fn on_login(&self, login_id: &str, token: &str, login_type: &str) {
        println!("用户 {} 登录了", login_id);
        // 在这里添加你的业务逻辑:
        // - 记录到数据库
        // - 发送通知
        // - 更新统计数据
    }

    async fn on_logout(&self, login_id: &str, token: &str, login_type: &str) {
        println!("用户 {} 登出了", login_id);
    }

    async fn on_kick_out(&self, login_id: &str, token: &str, login_type: &str) {
        println!("用户 {} 被踢出下线", login_id);
    }
}

// 注册监听器
StpUtil::register_listener(Arc::new(MyListener)).await;

// 或使用内置的日志监听器
use sa_token_core::LoggingListener;
StpUtil::register_listener(Arc::new(LoggingListener)).await;

// 事件会自动触发
let token = StpUtil::login("user_123").await?; // 触发登录事件
StpUtil::logout(&token).await?;                 // 触发登出事件
StpUtil::kick_out("user_123").await?;          // 触发踢出下线事件

7. Token 风格

sa-token-rust 支持多种 Token 生成风格,满足不同场景需求:

use sa_token_core::SaTokenConfig;
use sa_token_core::config::TokenStyle;

let config = SaTokenConfig::builder()
    .token_style(TokenStyle::Tik)  // 选择你喜欢的风格
    .build_config();

可用的 Token 风格

风格 长度 示例 使用场景
Uuid 36 字符 550e8400-e29b-41d4-a716-446655440000 标准 UUID 格式,通用性强
SimpleUuid 32 字符 550e8400e29b41d4a716446655440000 无横杠的 UUID,更紧凑
Random32 32 字符 a3f5c9d8e2b7f4a6c1e8d3b9f2a7c5e1 随机十六进制字符串,安全性好
Random64 64 字符 a3f5c9d8... 更长的随机字符串,安全性更高
Random128 128 字符 a3f5c9d8... 最长随机字符串,超高安全性
Jwt 可变长度 eyJhbGc... 自包含令牌,带有声明信息 (JWT指南)
Hash 64 字符 472c7dce... SHA256 哈希,包含用户信息,可追溯
Timestamp ~30 字符 1760404107094_a8f4f17d88fcddb8 包含时间戳,易于追踪
Tik 8 字符 GIxYHHD5 短小精悍,适合分享

⭐ = 本版本新增

Token 风格示例

// Uuid 风格(默认)
.token_style(TokenStyle::Uuid)
// 输出: 550e8400-e29b-41d4-a716-446655440000

// Hash 风格 - 哈希中包含用户信息
.token_style(TokenStyle::Hash)
// 输出: 472c7dceee2b3079a1ae70746f43ba99b91636292ba7811b3bc8985a1148836f

// Timestamp 风格 - 包含毫秒级时间戳
.token_style(TokenStyle::Timestamp)
// 输出: 1760404107094_a8f4f17d88fcddb8

// Tik 风格 - 短小的8位字符 token
.token_style(TokenStyle::Tik)
// 输出: GIxYHHD5

// JWT 风格 - 自包含令牌
.token_style(TokenStyle::Jwt)
.jwt_secret_key("your-secret-key")
// 输出: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

如何选择 Token 风格

  • Uuid/SimpleUuid: 标准选择,兼容性广
  • Random32/64/128: 需要特定长度的随机 token 时
  • JWT: 需要自包含令牌,内嵌信息时
  • Hash: 需要可追溯到用户信息的 token 时
  • Timestamp: 需要知道 token 创建时间时
  • Tik: 需要短小 token 用于分享(URL、二维码等)时

运行示例查看所有 Token 风格效果:

cargo run --example token_styles_example

8. 安全特性

Nonce 防重放攻击

use sa_token_core::NonceManager;

let nonce_manager = NonceManager::new(storage, 300); // 5 分钟有效期

// 生成 nonce
let nonce = nonce_manager.generate();

// 验证并消费(单次使用)
nonce_manager.validate_and_consume(&nonce, "user_123").await?;

// 第二次使用将失败(检测到重放攻击)
match nonce_manager.validate_and_consume(&nonce, "user_123").await {
    Err(_) => println!("重放攻击已阻止!"),
    _ => {}
}

Refresh Token 刷新机制

use sa_token_core::RefreshTokenManager;

let refresh_manager = RefreshTokenManager::new(storage, config);

// 生成 refresh token
let refresh_token = refresh_manager.generate("user_123");
refresh_manager.store(&refresh_token, &access_token, "user_123").await?;

// 访问令牌过期时刷新
let (new_access_token, user_id) = refresh_manager
    .refresh_access_token(&refresh_token)
    .await?;

运行安全特性示例:

cargo run --example security_features_example

9. OAuth2 授权

完整的 OAuth2 授权码模式实现:

use sa_token_core::{OAuth2Manager, OAuth2Client};

let oauth2 = OAuth2Manager::new(storage);

// 注册 OAuth2 客户端
let client = OAuth2Client {
    client_id: "web_app_001".to_string(),
    client_secret: "secret_abc123xyz".to_string(),
    redirect_uris: vec!["http://localhost:3000/callback".to_string()],
    grant_types: vec!["authorization_code".to_string()],
    scope: vec!["read".to_string(), "write".to_string()],
};

oauth2.register_client(&client).await?;

// 生成授权码
let auth_code = oauth2.generate_authorization_code(
    "web_app_001".to_string(),
    "user_123".to_string(),
    "http://localhost:3000/callback".to_string(),
    vec!["read".to_string()],
);

oauth2.store_authorization_code(&auth_code).await?;

// 授权码换取令牌
let token = oauth2.exchange_code_for_token(
    &auth_code.code,
    "web_app_001",
    "secret_abc123xyz",
    "http://localhost:3000/callback",
).await?;

// 验证访问令牌
let token_info = oauth2.verify_access_token(&token.access_token).await?;

// 刷新令牌
let new_token = oauth2.refresh_access_token(
    token.refresh_token.as_ref().unwrap(),
    "web_app_001",
    "secret_abc123xyz",
).await?;

10. SSO 单点登录

完整的 SSO 实现,支持票据认证:

use sa_token_core::{SsoServer, SsoClient, SsoConfig};

// 创建 SSO Server
let sso_server = SsoServer::new(manager.clone())
    .with_ticket_timeout(300);  // 5 分钟

// 创建 SSO Client
let client = SsoClient::new(
    manager.clone(),
    "http://sso.example.com/auth".to_string(),
    "http://app1.example.com".to_string(),
);

// 配置跨域支持的 SSO
let config = SsoConfig::builder()
    .server_url("http://sso.example.com/auth")
    .ticket_timeout(300)
    .allow_cross_domain(true)
    .add_allowed_origin("http://app1.example.com".to_string())
    .build();

// 用户登录流程
let ticket = sso_server.login(
    "user_123".to_string(),
    "http://app1.example.com".to_string(),
).await?;

// 验证票据
let login_id = sso_server.validate_ticket(
    &ticket.ticket_id,
    "http://app1.example.com",
).await?;

// 创建本地会话
let token = client.login_by_ticket(login_id).await?;

// 统一登出(所有应用)
let clients = sso_server.logout("user_123").await?;
for client_url in clients {
    // 通知各客户端登出
}

📚 框架集成示例

Axum

use axum::{Router, routing::{get, post}};
use sa_token_plugin_axum::{SaTokenState, SaTokenMiddleware, LoginIdExtractor};

let state = SaTokenState::builder()
    .storage(Arc::new(MemoryStorage::new()))
    .build();

let app = Router::new()
    .route("/user/info", get(user_info))
    .layer(SaTokenMiddleware::new(state));

Actix-web

use actix_web::{App, HttpServer, web};
use sa_token_plugin_actix_web::{SaTokenState, SaTokenMiddleware, LoginIdExtractor};

// 初始化 Sa-Token
let sa_token_manager = conf::init_sa_token(None)
    .await
    .expect("Sa-Token 初始化失败");

// 创建 Sa-Token 状态
let sa_token_state = SaTokenState {
    manager: sa_token_manager.clone(),
};

// 创建应用状态数据
let sa_token_data = web::Data::new(sa_token_state.clone());

HttpServer::new(move || {
    App::new()
        // 注册中间件
        .wrap(Logger::default())
        .app_data(sa_token_data.clone()) // 注入 Sa-Token 到应用状态
        .wrap(SaTokenMiddleware::new(sa_token_state.clone()))
        
        // 路由
        .route("/api/login", web::post().to(login))
        .route("/api/user/info", web::get().to(user_info))
})
.bind("0.0.0.0:3000")?
.run()
.await

// 完整示例请参考 examples/actix-web-example/

Poem

use poem::{Route, Server};
use sa_token_plugin_poem::{SaTokenState, SaTokenMiddleware, LoginIdExtractor};

let state = SaTokenState::builder()
    .storage(Arc::new(MemoryStorage::new()))
    .build();

let app = Route::new()
    .at("/user/info", poem::get(user_info))
    .with(SaTokenMiddleware::new(state));

Server::new(TcpListener::bind("127.0.0.1:8080"))
    .run(app)
    .await

Rocket

use rocket::{launch, get, routes};
use sa_token_plugin_rocket::{SaTokenState, SaTokenFairing, LoginIdGuard};

#[get("/user/info")]
fn user_info(login_id: LoginIdGuard) -> String {
    format!("用户: {}", login_id.0)
}

#[launch]
fn rocket() -> _ {
    let state = SaTokenState::builder()
        .storage(Arc::new(MemoryStorage::new()))
        .build();
    
    rocket::build()
        .attach(SaTokenFairing::new(state))
        .mount("/", routes![user_info])
}

Warp

use warp::Filter;
use sa_token_plugin_warp::{SaTokenState, sa_token_filter};

let state = SaTokenState::builder()
    .storage(Arc::new(MemoryStorage::new()))
    .build();

let routes = warp::path("user")
    .and(warp::path("info"))
    .and(sa_token_filter(state))
    .map(|token_data| {
        format!("用户信息")
    });

warp::serve(routes)
    .run(([127, 0, 0, 1], 8080))
    .await;

项目地址:https://github.com/llc-993/sa-token-rust


Ext Link: https://github.com/llc-993/sa-token-rust

评论区

写评论

还没有评论

1 共 0 条评论, 1 页