The #[sa_check_permission] macro in sa-token-rust provides a flexible permission checking system that supports both exact matching and wildcard patterns. This document explains how the permission matching algorithm works.
Permissions follow the format: module:action
Examples:
user:list- View user listuser:create- Create useruser:update- Update useruser:delete- Delete userorder:refund- Refund order
The system first attempts an exact string match.
// User has permission
["user:delete"]
// Required permission
"user:delete"
// Result: ✅ Match (exact)Match Table:
| User Permission | Required Permission | Result |
|---|---|---|
user:delete |
user:delete |
✅ Match |
user:create |
user:delete |
❌ No match |
order:list |
user:delete |
❌ No match |
If exact match fails, the system checks for wildcard patterns.
Module Wildcard: module:*
- Matches all actions in the specified module
- Format:
{prefix}:* - Example:
user:*matchesuser:list,user:create,user:delete, etc.
// User has permission
["user:*"]
// Required permissions (all match)
"user:list" // ✅
"user:create" // ✅
"user:update" // ✅
"user:delete" // ✅
// No match
"order:list" // ❌ (different module)Match Table:
| User Permission | Required Permission | Result |
|---|---|---|
user:* |
user:delete |
✅ Wildcard match |
user:* |
user:list |
✅ Wildcard match |
user:* |
user:create |
✅ Wildcard match |
admin:* |
user:delete |
❌ No match (different prefix) |
order:* |
user:list |
❌ No match (different prefix) |
A single * grants all permissions.
// User has permission
["*"]
// All permissions match
"user:delete" // ✅
"order:create" // ✅
"admin:config" // ✅Match Table:
| User Permission | Required Permission | Result |
|---|---|---|
* |
user:delete |
✅ Global wildcard |
* |
order:list |
✅ Global wildcard |
* |
admin:config |
✅ Global wildcard |
graph TD
A[Start: Check Permission] --> B[Get user's permission list]
B --> C{Step 1: Exact Match<br/>Does list contain<br/>exact permission?}
C -->|Yes| D[✅ PASS]
C -->|No| E{Step 2: Wildcard Match<br/>Check each permission<br/>Ends with ':*'?<br/>Required starts with prefix?}
E -->|Yes| F[✅ PASS]
E -->|No| G[❌ DENY]
style D fill:#90EE90,stroke:#333,stroke-width:2px
style F fill:#90EE90,stroke:#333,stroke-width:2px
style G fill:#FFB6C6,stroke:#333,stroke-width:2px
style C fill:#87CEEB,stroke:#333,stroke-width:2px
style E fill:#87CEEB,stroke:#333,stroke-width:2px
The matching logic is implemented in sa-token-core/src/util.rs:
pub async fn has_permission(login_id: impl LoginId, permission: &str) -> bool {
let manager = Self::get_manager();
let map = manager.user_permissions.read().await;
if let Some(permissions) = map.get(&login_id.to_login_id()) {
// 1. Exact match
if permissions.contains(&permission.to_string()) {
return true;
}
// 2. Wildcard match
for perm in permissions {
if perm.ends_with(":*") {
let prefix = &perm[..perm.len() - 2];
if permission.starts_with(prefix) {
return true;
}
}
}
}
false
}use sa_token_core::StpUtil;
use sa_token_macro::sa_check_permission;
// Initialize permissions
StpUtil::set_permissions("user_123", vec![
"user:list".to_string(),
"user:create".to_string(),
]).await?;
// Check exact permission
#[sa_check_permission("user:list")]
async fn list_users() -> &'static str {
let login_id = StpUtil::get_login_id_as_string()?;
// Manual check (recommended)
if !StpUtil::has_permission(&login_id, "user:list").await {
return "Permission denied";
}
"User list"
}// Admin has all user module permissions
StpUtil::set_permissions("admin_001", vec![
"user:*".to_string(), // All user operations
"order:*".to_string(), // All order operations
]).await?;
// These all pass for admin_001
#[sa_check_permission("user:list")]
async fn list_users() { /* ... */ }
#[sa_check_permission("user:create")]
async fn create_user() { /* ... */ }
#[sa_check_permission("user:delete")]
async fn delete_user() { /* ... */ }// Check multiple permissions (AND logic)
if StpUtil::has_permissions_and(&login_id, &["user:read", "user:write"]).await {
println!("Has both read and write permissions");
}
// Check multiple permissions (OR logic)
if StpUtil::has_permissions_or(&login_id, &["admin:*", "user:*"]).await {
println!("Has admin or user module permissions");
}#[sa_check_permission("order:refund")]
async fn refund_order(order_id: u64, amount: f64) -> Result<String, StatusCode> {
let login_id = StpUtil::get_login_id_as_string()?;
// Dynamic permission based on business logic
let required_permission = if amount > 1000.0 {
"order:refund:advanced" // High-value refunds need advanced permission
} else {
"order:refund"
};
if !StpUtil::has_permission(&login_id, required_permission).await {
return Err(StatusCode::FORBIDDEN);
}
Ok(format!("Refunded ${}", amount))
}Follow the module:action format:
✅ Good:
- user:list
- user:create
- user:update
- user:delete
- order:create
- order:refund
- admin:config
❌ Bad:
- userList (no separator)
- user_create (wrong separator)
- deleteUser (action first)
Use wildcards sparingly for administrative roles:
// Regular user - specific permissions
StpUtil::set_permissions("user_123", vec![
"user:list".to_string(),
"user:view".to_string(),
]).await?;
// Admin - module wildcard
StpUtil::set_permissions("admin_001", vec![
"user:*".to_string(),
"order:*".to_string(),
]).await?;
// Super admin - global wildcard (use with caution)
StpUtil::set_permissions("superadmin_001", vec![
"*".to_string(),
]).await?;Organize permissions hierarchically:
// Level 1: Module
"user:*" // All user operations
// Level 2: Action
"user:list"
"user:create"
"user:update"
"user:delete"
// Level 3: Resource-specific (custom implementation)
"user:update:self" // Only update own profile
"user:update:any" // Update any user- Exact Match First: The system checks exact matches before wildcards, optimizing for the most common case.
- In-Memory Storage: Permissions are stored in memory (
HashMap) for fast access. - Async Operations: All permission checks are async to support Redis or database backends.
- Manual Checks Required: The
#[sa_check_permission]macro only adds metadata. You must manually callStpUtil::has_permission()in your function. - Validate Before Use: Always check permissions before performing sensitive operations.
- Limit Wildcards: Use global wildcards (
*) only for super admin accounts. - Audit Trails: Consider logging permission checks for security auditing.
sa-token-rust 中的 #[sa_check_permission] 宏提供了一个灵活的权限检查系统,支持精确匹配和通配符模式。本文档详细说明了权限匹配算法的工作原理。
权限遵循格式:模块:操作
示例:
user:list- 查看用户列表user:create- 创建用户user:update- 更新用户user:delete- 删除用户order:refund- 退款
系统首先尝试精确字符串匹配。
// 用户拥有的权限
["user:delete"]
// 需要的权限
"user:delete"
// 结果:✅ 匹配(精确)匹配表:
| 用户权限 | 需要的权限 | 结果 |
|---|---|---|
user:delete |
user:delete |
✅ 匹配 |
user:create |
user:delete |
❌ 不匹配 |
order:list |
user:delete |
❌ 不匹配 |
如果精确匹配失败,系统会检查通配符模式。
模块通配符: 模块:*
- 匹配指定模块中的所有操作
- 格式:
{前缀}:* - 示例:
user:*匹配user:list、user:create、user:delete等
// 用户拥有的权限
["user:*"]
// 需要的权限(全部匹配)
"user:list" // ✅
"user:create" // ✅
"user:update" // ✅
"user:delete" // ✅
// 不匹配
"order:list" // ❌(不同模块)匹配表:
| 用户权限 | 需要的权限 | 结果 |
|---|---|---|
user:* |
user:delete |
✅ 通配符匹配 |
user:* |
user:list |
✅ 通配符匹配 |
user:* |
user:create |
✅ 通配符匹配 |
admin:* |
user:delete |
❌ 不匹配(前缀不同) |
order:* |
user:list |
❌ 不匹配(前缀不同) |
单个 * 授予所有权限。
// 用户拥有的权限
["*"]
// 所有权限都匹配
"user:delete" // ✅
"order:create" // ✅
"admin:config" // ✅匹配表:
| 用户权限 | 需要的权限 | 结果 |
|---|---|---|
* |
user:delete |
✅ 全局通配符 |
* |
order:list |
✅ 全局通配符 |
* |
admin:config |
✅ 全局通配符 |
graph TD
A[开始:检查权限] --> B[获取用户的权限列表]
B --> C{步骤 1:精确匹配<br/>列表中是否包含<br/>精确权限?}
C -->|是| D[✅ 通过]
C -->|否| E{步骤 2:通配符匹配<br/>检查每个权限<br/>是否以 ':*' 结尾?<br/>需要的权限是否以前缀开头?}
E -->|是| F[✅ 通过]
E -->|否| G[❌ 拒绝]
style D fill:#90EE90,stroke:#333,stroke-width:2px
style F fill:#90EE90,stroke:#333,stroke-width:2px
style G fill:#FFB6C6,stroke:#333,stroke-width:2px
style C fill:#87CEEB,stroke:#333,stroke-width:2px
style E fill:#87CEEB,stroke:#333,stroke-width:2px
匹配逻辑在 sa-token-core/src/util.rs 中实现:
pub async fn has_permission(login_id: impl LoginId, permission: &str) -> bool {
let manager = Self::get_manager();
let map = manager.user_permissions.read().await;
if let Some(permissions) = map.get(&login_id.to_login_id()) {
// 1. 精确匹配
if permissions.contains(&permission.to_string()) {
return true;
}
// 2. 通配符匹配
for perm in permissions {
if perm.ends_with(":*") {
let prefix = &perm[..perm.len() - 2];
if permission.starts_with(prefix) {
return true;
}
}
}
}
false
}use sa_token_core::StpUtil;
use sa_token_macro::sa_check_permission;
// 初始化权限
StpUtil::set_permissions("user_123", vec![
"user:list".to_string(),
"user:create".to_string(),
]).await?;
// 检查精确权限
#[sa_check_permission("user:list")]
async fn list_users() -> &'static str {
let login_id = StpUtil::get_login_id_as_string()?;
// 手动检查(推荐)
if !StpUtil::has_permission(&login_id, "user:list").await {
return "权限不足";
}
"用户列表"
}// 管理员拥有所有用户模块权限
StpUtil::set_permissions("admin_001", vec![
"user:*".to_string(), // 所有用户操作
"order:*".to_string(), // 所有订单操作
]).await?;
// admin_001 可以访问以下所有接口
#[sa_check_permission("user:list")]
async fn list_users() { /* ... */ }
#[sa_check_permission("user:create")]
async fn create_user() { /* ... */ }
#[sa_check_permission("user:delete")]
async fn delete_user() { /* ... */ }// 检查多个权限(AND 逻辑)
if StpUtil::has_permissions_and(&login_id, &["user:read", "user:write"]).await {
println!("同时拥有读写权限");
}
// 检查多个权限(OR 逻辑)
if StpUtil::has_permissions_or(&login_id, &["admin:*", "user:*"]).await {
println!("拥有管理员或用户模块权限");
}#[sa_check_permission("order:refund")]
async fn refund_order(order_id: u64, amount: f64) -> Result<String, StatusCode> {
let login_id = StpUtil::get_login_id_as_string()?;
// 根据业务逻辑动态决定权限
let required_permission = if amount > 1000.0 {
"order:refund:advanced" // 大额退款需要高级权限
} else {
"order:refund"
};
if !StpUtil::has_permission(&login_id, required_permission).await {
return Err(StatusCode::FORBIDDEN);
}
Ok(format!("已退款 ¥{}", amount))
}遵循 模块:操作 格式:
✅ 正确:
- user:list
- user:create
- user:update
- user:delete
- order:create
- order:refund
- admin:config
❌ 错误:
- userList(没有分隔符)
- user_create(错误的分隔符)
- deleteUser(操作在前)
谨慎使用通配符,仅用于管理员角色:
// 普通用户 - 具体权限
StpUtil::set_permissions("user_123", vec![
"user:list".to_string(),
"user:view".to_string(),
]).await?;
// 管理员 - 模块通配符
StpUtil::set_permissions("admin_001", vec![
"user:*".to_string(),
"order:*".to_string(),
]).await?;
// 超级管理员 - 全局通配符(谨慎使用)
StpUtil::set_permissions("superadmin_001", vec![
"*".to_string(),
]).await?;按层次组织权限:
// 层级 1:模块
"user:*" // 所有用户操作
// 层级 2:操作
"user:list"
"user:create"
"user:update"
"user:delete"
// 层级 3:资源特定(自定义实现)
"user:update:self" // 只能更新自己的资料
"user:update:any" // 可以更新任何用户- 精确匹配优先: 系统先检查精确匹配,优化最常见的情况
- 内存存储: 权限存储在内存(
HashMap)中以实现快速访问 - 异步操作: 所有权限检查都是异步的,支持 Redis 或数据库后端
- 需要手动检查:
#[sa_check_permission]宏只添加元数据,必须在函数中手动调用StpUtil::has_permission() - 使用前验证: 在执行敏感操作前始终检查权限
- 限制通配符: 仅对超级管理员账户使用全局通配符 (
*) - 审计跟踪: 考虑记录权限检查日志以进行安全审计
- StpUtil API - Complete StpUtil API reference
- README - Project overview and quick start
- StpUtil API - 完整的 StpUtil API 参考
- README - 项目概述和快速开始