第四十八变 授权
知其为谁,方能定其可为。认证辨明身份,授权划定边界。
授权之道,在于回答“你能做什么“——从简单的访问控制列表到复杂的属性策略引擎,从单一系统的权限管理到分布式服务的统一授权,授权机制决定了资源的安全边界。
48.1 授权的基本概念
48.1.1 认证与授权的区别
认证和授权是安全体系中紧密相连却截然不同的两个环节:
| 维度 | 认证(Authentication) | 授权(Authorization) |
|---|---|---|
| 核心问题 | 你是谁? | 你能做什么? |
| 发生时机 | 访问前,确认身份 | 认证后,决定权限 |
| 依赖关系 | 不依赖授权 | 依赖认证结果 |
| 类比 | 机场安检核实身份证 | 登机牌决定你能进入哪个舱位 |
| 失败表现 | “无法确认你的身份” | “你没有权限执行此操作” |
一个经典的安全反模式是:系统仅完成认证,却将授权决策交给前端控制。攻击者可以轻易绕过前端限制,直接调用后端API。因此,授权检查必须在服务端强制执行。
48.1.2 授权的核心要素
授权决策通常涉及三个核心要素:
- 主体(Subject):请求访问的实体,通常是已认证的用户或服务
- 资源(Resource):被访问的对象,如文件、数据库记录、API端点
- 操作(Action):主体希望对资源执行的动作,如读、写、删除、执行
授权系统回答的问题是:“主体是否可以对资源执行操作?”
48.2 访问控制模型
访问控制模型定义了如何管理和执行授权决策。从简单到复杂,主要有四种经典模型。
48.2.1 DAC:自主访问控制
自主访问控制(Discretionary Access Control, DAC) 允许资源的所有者自主决定谁可以访问其资源。
核心特征:
- 资源拥有者具有完全控制权
- 可以授予或撤销其他主体的访问权限
- 权限可以传递(如Linux文件系统中的ACL)
典型实现:
- Unix/Linux文件权限(rwx/UGO模型)
- Windows文件共享权限
- 云存储的分享链接
#![allow(unused)]
fn main() {
// 简化的DAC权限检查示例
struct File {
owner: String,
permissions: u16, // rwxrwxrwx
}
impl File {
fn can_access(&self, user: &User, action: Action) -> bool {
if user.id == self.owner {
return self.check_owner_perm(action);
}
if user.groups.iter().any(|g| self.group_has_access(g)) {
return self.check_group_perm(action);
}
self.check_other_perm(action)
}
}
}
优点:灵活,符合直觉。 缺点:权限分散,难以审计;权限传递可能导致意外泄露(特洛伊木马问题)。
48.2.2 MAC:强制访问控制
强制访问控制(Mandatory Access Control, MAC) 由系统管理员统一制定访问策略,资源所有者无权修改。
核心特征:
- 系统全局安全策略强制执行
- 每个主体和资源都有安全标签
- 访问决策基于标签比较,而非用户意愿
典型实现:
- SELinux:为Linux进程和文件添加安全上下文
- AppArmor:基于路径的强制访问控制
- 军事分级系统:绝密 > 机密 > 秘密 > 公开
Bell-LaPadula模型(保密性):
- 不上读(No Read Up):主体不能读取更高安全级别的资源
- 不下写(No Write Down):主体不能向更低安全级别写入
Biba模型(完整性):
- 不下读(No Read Down):防止低完整性数据污染高完整性主体
- 不上写(No Write Up):防止高完整性主体被低完整性数据影响
#![allow(unused)]
fn main() {
// 简化的MAC标签检查
#[derive(PartialEq, Eq, PartialOrd, Ord)]
enum SecurityLevel {
Public,
Internal,
Confidential,
Secret,
}
struct MacSubject {
clearance: SecurityLevel,
}
struct MacResource {
classification: SecurityLevel,
}
fn can_read(subject: &MacSubject, resource: &MacResource) -> bool {
subject.clearance >= resource.classification // 不上读
}
fn can_write(subject: &MacSubject, resource: &MacResource) -> bool {
subject.clearance <= resource.classification // 不下写
}
}
优点:安全性高,适合高安全需求环境。 缺点:灵活性差,配置复杂,可能影响正常业务。
48.2.3 RBAC:基于角色的访问控制
基于角色的访问控制(Role-Based Access Control, RBAC) 是目前企业应用中最广泛使用的模型。
核心概念:
- 用户(User):系统的使用者
- 角色(Role):一组权限的集合,代表组织中的职位或职责
- 权限(Permission):对资源执行操作的能力
- 会话(Session):用户激活的一组角色
RBAC层级:
| 层级 | 名称 | 新增特性 |
|---|---|---|
| RBAC0 | 核心RBAC | 用户-角色-权限基本关联 |
| RBAC1 | 层级RBAC | 角色继承(Senior Role继承Junior Role的权限) |
| RBAC2 | 约束RBAC | 职责分离约束(互斥角色、基数约束) |
| RBAC3 | 统一RBAC | RBAC1 + RBAC2的完整组合 |
职责分离(Separation of Duties, SoD):
- 静态SoD:用户不能同时被分配互斥角色(如会计和审计)
- 动态SoD:用户可同时拥有互斥角色,但不能在同一会话中同时激活
#![allow(unused)]
fn main() {
// RBAC核心数据结构
use std::collections::{HashMap, HashSet};
struct RbacSystem {
user_roles: HashMap<String, HashSet<String>>, // user -> roles
role_permissions: HashMap<String, HashSet<String>>, // role -> permissions
role_hierarchy: HashMap<String, HashSet<String>>, // role -> parent roles
}
impl RbacSystem {
fn user_permissions(&self, user: &str) -> HashSet<String> {
let mut perms = HashSet::new();
if let Some(roles) = self.user_roles.get(user) {
for role in roles {
self.collect_role_permissions(role, &mut perms);
}
}
perms
}
fn collect_role_permissions(&self, role: &str, perms: &mut HashSet<String>) {
if let Some(direct) = self.role_permissions.get(role) {
perms.extend(direct.iter().cloned());
}
if let Some(parents) = self.role_hierarchy.get(role) {
for parent in parents {
self.collect_role_permissions(parent, perms);
}
}
}
fn check_permission(&self, user: &str, permission: &str) -> bool {
self.user_permissions(user).contains(permission)
}
}
}
优点:简化权限管理,角色与组织结构对齐,易于审计。 缺点:角色爆炸(大量细粒度角色);难以表达基于资源属性的复杂策略。
48.2.4 ABAC:基于属性的访问控制
基于属性的访问控制(Attribute-Based Access Control, ABAC) 是最灵活、最强大的访问控制模型。
核心思想:访问决策基于主体、资源、操作和环境的属性,而非预定义的角色或标签。
四类属性:
- 主体属性:用户部门、职级、认证方式、信任等级
- 资源属性:文件分类、所有者、创建时间、敏感等级
- 操作属性:读、写、删除、分享
- 环境属性:当前时间、访问位置、网络环境、设备类型
策略表达式示例:
允许 如果:
主体.部门 == "财务部"
且 资源.类型 == "财务报表"
且 操作 == "读取"
且 环境.时间在 "09:00" 到 "18:00" 之间
且 环境.位置 == "公司内网"
XACML(eXtensible Access Control Markup Language) 是ABAC的标准实现,使用XML定义策略。
#![allow(unused)]
fn main() {
// 简化的ABAC策略评估
struct AbacContext {
subject: HashMap<String, String>,
resource: HashMap<String, String>,
action: String,
environment: HashMap<String, String>,
}
struct AbacPolicy {
rules: Vec<AbacRule>,
}
struct AbacRule {
conditions: Vec<Box<dyn Fn(&AbacContext) -> bool>>,
effect: Effect,
}
enum Effect {
Permit,
Deny,
}
impl AbacPolicy {
fn evaluate(&self, ctx: &AbacContext) -> Effect {
for rule in &self.rules {
if rule.conditions.iter().all(|c| c(ctx)) {
return rule.effect;
}
}
Effect::Deny // 默认拒绝
}
}
}
优点:极度灵活,可表达复杂业务规则;细粒度控制。 缺点:策略复杂,性能开销大,难以直观理解和审计。
48.2.5 模型对比
| 特性 | DAC | MAC | RBAC | ABAC |
|---|---|---|---|---|
| 灵活性 | 高 | 低 | 中 | 极高 |
| 安全性 | 低 | 极高 | 高 | 高 |
| 管理复杂度 | 低 | 高 | 中 | 高 |
| 审计难度 | 高 | 低 | 低 | 高 |
| 适用场景 | 个人系统 | 军事/政府 | 企业应用 | 云原生/微服务 |
| 性能 | 高 | 高 | 高 | 中 |
48.3 权限设计原则
48.3.1 最小权限原则
最小权限原则(Principle of Least Privilege, PoLP) 要求每个主体仅拥有完成其工作所必需的最小权限集合。
实践要点:
- 默认拒绝所有访问,显式授予所需权限
- 定期审查和回收不再需要的权限
- 使用临时权限提升机制(如sudo),而非长期高权限
- 服务账户按功能拆分,避免“万能账户“
#![allow(unused)]
fn main() {
// 最小权限原则示例:数据库连接按功能分离
struct ReadOnlyDbPool;
struct ReadWriteDbPool;
struct AdminDbPool;
impl ReadOnlyDbPool {
async fn query<T>(&self, sql: &str) -> Result<Vec<T>, Error> {
// 只读操作
todo!()
}
}
impl ReadWriteDbPool {
async fn execute(&self, sql: &str) -> Result<u64, Error> {
// 读写操作
todo!()
}
}
// 普通服务只能访问只读池
struct UserService {
db: ReadOnlyDbPool,
}
// 订单服务需要读写
struct OrderService {
db: ReadWriteDbPool,
}
}
48.3.2 职责分离
职责分离(Separation of Duties, SoD) 要求关键操作由多个主体协作完成,防止单点滥用权限。
经典场景:
- 采购审批:申请人、审批人、验收人不能为同一人
- 金融交易:录入员和复核员分离
- 代码发布:开发提交、测试验证、运维部署分离
#![allow(unused)]
fn main() {
// 职责分离检查
struct SoDConstraint {
mutually_exclusive_roles: Vec<(String, String)>,
}
impl SoDConstraint {
fn check_assignment(&self, user_roles: &HashSet<String>) -> Result<(), String> {
for (r1, r2) in &self.mutually_exclusive_roles {
if user_roles.contains(r1) && user_roles.contains(r2) {
return Err(format!("违反职责分离: {} 与 {} 不能同时拥有", r1, r2));
}
}
Ok(())
}
}
}
48.3.3 权限设计模式
ACL(访问控制列表):直接在资源上维护允许访问的主体列表。
能力列表(Capability List):主体持有可访问资源的令牌(如文件描述符、API密钥)。
策略即代码(Policy as Code):将授权策略以代码形式版本化管理,支持代码审查和自动化测试。
48.4 OAuth 2.0 授权框架
48.4.1 OAuth 2.0 概述
OAuth 2.0 是业界标准的授权协议,允许第三方应用代表用户访问资源,而无需获取用户密码。
核心解决的问题:
- 用户不希望将密码交给第三方应用
- 用户希望细粒度控制第三方应用的访问范围
- 用户希望随时撤销第三方应用的访问权限
48.4.2 四种授权模式
授权码模式(Authorization Code)
最安全、最常用的模式,适用于服务器端应用:
+----------+
| 资源所有者 |
| (用户) |
+----------+
|
| 1. 浏览器重定向到授权服务器
v
+----------+ +---------------+
| |--(2) 用户登录并授权--------------->| |
| 用户 | | 授权服务器 |
| 代理 |<-(3) 返回授权码(重定向到客户端)--| |
| (浏览器) | +---------------+
+----------+ |
| |
| 4. 授权码通过浏览器传递给客户端 |
v |
+----------+ +---------------+
| |--(5) 用授权码换取访问令牌------------>| |
| 客户端 | (直接后端通信,保密) | 授权服务器 |
| (服务器) |<-(6) 返回访问令牌和刷新令牌------------| |
+----------+ +---------------+
|
| 7. 用访问令牌访问资源
v
+---------------+
| 资源服务器 |
+---------------+
PKCE扩展(Proof Key for Code Exchange, RFC 7636): 为授权码模式增加保护层,防止授权码拦截攻击。公共客户端(如移动应用、单页应用)必须使用PKCE。
#![allow(unused)]
fn main() {
// PKCE参数生成
use rand::{distributions::Alphanumeric, Rng};
use sha2::{Sha256, Digest};
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
fn generate_pkce() -> (String, String) {
// 生成随机code_verifier
let verifier: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(128)
.map(char::from)
.collect();
// 计算code_challenge = BASE64URL(SHA256(code_verifier))
let mut hasher = Sha256::new();
hasher.update(&verifier);
let challenge = URL_SAFE_NO_PAD.encode(hasher.finalize());
(verifier, challenge)
}
}
简化模式(Implicit)
直接从授权端点获取访问令牌,不经过授权码交换。因安全性问题,已被OAuth 2.1废弃。
密码凭证模式(Resource Owner Password Credentials)
用户直接向客户端提供用户名和密码,客户端用其换取令牌。仅适用于受信任的第一方应用。
客户端凭证模式(Client Credentials)
客户端以自己的身份(而非用户身份)请求访问资源。适用于服务间通信、后台任务。
#![allow(unused)]
fn main() {
// 客户端凭证模式示例
async fn client_credentials_flow(
token_endpoint: &str,
client_id: &str,
client_secret: &str,
scope: &str,
) -> Result<TokenResponse, reqwest::Error> {
let client = reqwest::Client::new();
let params = [
("grant_type", "client_credentials"),
("client_id", client_id),
("client_secret", client_secret),
("scope", scope),
];
let response = client
.post(token_endpoint)
.form(¶ms)
.send()
.await?
.json::<TokenResponse>()
.await?;
Ok(response)
}
#[derive(Debug, serde::Deserialize)]
struct TokenResponse {
access_token: String,
token_type: String,
expires_in: u64,
scope: Option<String>,
}
}
48.4.3 令牌类型
- 访问令牌(Access Token):用于访问受保护资源,通常短期有效(分钟到小时级)
- 刷新令牌(Refresh Token):用于获取新的访问令牌,长期有效但可撤销
- 授权码(Authorization Code):一次性凭证,用于交换访问令牌
48.4.4 范围(Scope)
Scope定义了访问令牌的权限边界:
scope = "read:users write:orders admin:settings"
客户端请求时声明所需scope,用户授权时可以看到并选择性同意。资源服务器验证令牌时检查scope是否包含所需权限。
48.5 JWT 令牌
48.5.1 JWT 结构
JSON Web Token(JWT, RFC 7519) 是一种紧凑、自包含的方式,用于在各方之间安全地传输信息。
JWT由三部分组成,用点号分隔:
xxxxx.yyyyy.zzzzz
| | |
Header Payload Signature
Header(头部):
{
"alg": "HS256",
"typ": "JWT"
}
Payload(载荷):声明(claims)的集合:
{
"sub": "user_12345",
"iss": "auth.example.com",
"aud": "api.example.com",
"exp": 1700000000,
"iat": 1699996400,
"scope": "read:users write:orders",
"role": "admin"
}
Signature(签名):
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
48.5.2 JWT 签名算法
| 算法 | 类型 | 说明 |
|---|---|---|
| HS256/384/512 | 对称 | HMAC with SHA,密钥共享 |
| RS256/384/512 | 非对称 | RSA with SHA,私钥签名、公钥验证 |
| ES256/384/512 | 非对称 | ECDSA with SHA,更短的密钥 |
| EdDSA | 非对称 | Ed25519,现代推荐算法 |
对称 vs 非对称:
- 对称(HMAC):签发和验证使用同一密钥,适合单一服务
- 非对称(RSA/ECDSA):签发用私钥,验证用公钥,适合分布式系统(授权服务器签发,各资源服务器验证)
48.5.3 JWT 验证要点
验证JWT时,必须检查以下声明:
- exp(过期时间):当前时间必须小于过期时间
- nbf(生效时间):当前时间必须大于生效时间
- iat(签发时间):用于检测令牌重放
- iss(签发者):必须是信任的签发者
- aud(受众):必须包含当前服务标识
- 签名:必须使用正确的算法和密钥验证
安全警告:
- 永远不要信任客户端提供的JWT头部算法(alg: none攻击)
- 密钥强度要足够(HS256至少256位密钥)
- 敏感信息不要放入JWT payload(仅Base64编码,未加密)
- 使用短过期时间,配合刷新令牌机制
48.5.4 Rust实现:JWT签发与验证
use jsonwebtoken::{
decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation,
};
use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
sub: String, // 用户ID
iss: String, // 签发者
aud: String, // 受众
exp: usize, // 过期时间
iat: usize, // 签发时间
scope: String, // 权限范围
#[serde(skip_serializing_if = "Option::is_none")]
role: Option<String>, // 角色
}
fn now() -> usize {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as usize
}
// 使用非对称密钥(RS256)签发JWT
fn create_jwt_rs256(
user_id: &str,
private_key_pem: &str,
) -> Result<String, jsonwebtoken::errors::Error> {
let now_ts = now();
let claims = Claims {
sub: user_id.to_string(),
iss: "auth-server".to_string(),
aud: "resource-api".to_string(),
iat: now_ts,
exp: now_ts + 3600,
scope: "read:users read:orders".to_string(),
role: Some("user".to_string()),
};
let header = Header::new(Algorithm::RS256);
let encoding_key = EncodingKey::from_rsa_pem(private_key_pem.as_bytes())?;
encode(&header, &claims, &encoding_key)
}
// 使用公钥验证JWT
fn verify_jwt_rs256(
token: &str,
public_key_pem: &str,
) -> Result<Claims, jsonwebtoken::errors::Error> {
let mut validation = Validation::new(Algorithm::RS256);
validation.set_issuer(&["auth-server"]);
validation.set_audience(&["resource-api"]);
validation.set_required_spec_claims(&["exp", "iss", "aud"]);
let decoding_key = DecodingKey::from_rsa_pem(public_key_pem.as_bytes())?;
let token_data = decode::<Claims>(token, &decoding_key, &validation)?;
Ok(token_data.claims)
}
// 检查特定权限
fn has_scope(claims: &Claims, required: &str) -> bool {
claims.scope.split_whitespace().any(|s| s == required)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 注意:实际项目中应从安全存储读取密钥
let private_key = include_str!("private_key.pem");
let public_key = include_str!("public_key.pem");
// 签发
let token = create_jwt_rs256("user_123", private_key)?;
println!("JWT: {}", token);
// 验证
let claims = verify_jwt_rs256(&token, public_key)?;
println!("验证通过: {:?}", claims);
// 权限检查
println!("可读取用户: {}", has_scope(&claims, "read:users"));
println!("可写入订单: {}", has_scope(&claims, "write:orders"));
Ok(())
}
48.6 Rust授权生态
48.6.1 Casbin:通用授权库
Casbin 是一个强大的、开源的访问控制库,支持多种访问控制模型(ACL、RBAC、ABAC)。
核心概念:
- Model:定义访问控制模型的配置文件
- Policy:具体的权限策略数据
- Adapter:策略数据的持久化适配器(内存、文件、数据库)
- Enforcer:执行授权决策的核心引擎
RBAC模型配置(model.conf):
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
策略数据(policy.csv):
p, admin, /users, GET
p, admin, /users, POST
p, user, /profile, GET
g, alice, admin
g, bob, user
48.6.2 Rust实现:Casbin RBAC
use casbin::{CoreApi, DefaultModel, Enforcer, FileAdapter, Result};
#[tokio::main]
async fn main() -> Result<()> {
// 加载模型和策略
let model = DefaultModel::from_file("model.conf").await?;
let adapter = FileAdapter::new("policy.csv");
let enforcer = Enforcer::new(model, adapter).await?;
// 检查权限
let checks = vec![
("alice", "/users", "GET"), // admin角色,允许
("alice", "/users", "POST"), // admin角色,允许
("bob", "/profile", "GET"), // user角色,允许
("bob", "/users", "GET"), // user角色,拒绝
("charlie", "/users", "GET"), // 无角色,拒绝
];
for (sub, obj, act) in &checks {
let allowed = enforcer.enforce((sub.to_string(), obj.to_string(), act.to_string()))?;
println!("{} 能否 {} {}? {}", sub, act, obj, allowed);
}
// 动态添加策略
enforcer.add_policy(vec!["user".to_string(), "/orders".to_string(), "GET".to_string()]).await?;
// 动态添加角色
enforcer.add_grouping_policy(vec!["charlie".to_string(), "user".to_string()]).await?;
let allowed = enforcer.enforce(("charlie", "/orders", "GET"))?;
println!("添加角色后,charlie 能否 GET /orders? {}", allowed);
Ok(())
}
Cargo.toml 依赖:
[dependencies]
casbin = "2"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
48.6.3 OAuth2-rs:OAuth 2.0客户端
#![allow(unused)]
fn main() {
use oauth2::{
AuthUrl, ClientId, ClientSecret, CsrfToken, PkceCodeChallenge, RedirectUrl,
Scope, TokenUrl, AuthorizationCode, TokenResponse,
};
use oauth2::basic::BasicClient;
use oauth2::reqwest::async_http_client;
fn create_oauth_client() -> BasicClient {
let client_id = ClientId::new("your-client-id".to_string());
let client_secret = ClientSecret::new("your-client-secret".to_string());
let auth_url = AuthUrl::new("https://auth.example.com/authorize".to_string()).unwrap();
let token_url = TokenUrl::new("https://auth.example.com/token".to_string()).unwrap();
BasicClient::new(client_id, Some(client_secret), auth_url, Some(token_url))
.set_redirect_uri(RedirectUrl::new("http://localhost:8080/callback".to_string()).unwrap())
}
async fn authorization_code_flow() -> Result<(), Box<dyn std::error::Error>> {
let client = create_oauth_client();
// 生成PKCE参数
let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
// 生成授权URL
let (auth_url, csrf_token) = client
.authorize_url(CsrfToken::new_random)
.add_scope(Scope::new("read:users".to_string()))
.add_scope(Scope::new("write:orders".to_string()))
.set_pkce_challenge(pkce_challenge)
.url();
println!("请访问以下URL授权: {}", auth_url);
println!("CSRF Token: {}", csrf_token.secret());
// 用户授权后,从回调URL获取授权码
let authorization_code = AuthorizationCode::new("code-from-callback".to_string());
// 用授权码换取令牌
let token_response = client
.exchange_code(authorization_code)
.set_pkce_verifier(pkce_verifier)
.request_async(async_http_client)
.await?;
println!("访问令牌: {}", token_response.access_token().secret());
Ok(())
}
}
Cargo.toml 依赖:
[dependencies]
oauth2 = "4"
reqwest = "0.11"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
48.6.4 Actix-web中的授权中间件
#![allow(unused)]
fn main() {
use actix_web::{dev::ServiceRequest, error::ErrorUnauthorized, web, App, Error, HttpServer};
use actix_web_httpauth::extractors::bearer::{BearerAuth, Config};
use actix_web_httpauth::extractors::AuthenticationError;
use jsonwebtoken::{decode, DecodingKey, Validation, Algorithm};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
struct Claims {
sub: String,
role: String,
scope: String,
}
async fn validator(
req: ServiceRequest,
credentials: BearerAuth,
) -> Result<ServiceRequest, (Error, ServiceRequest)> {
let token = credentials.token();
let secret = req.app_data::<web::Data<String>>()
.map(|d| d.get_ref().clone())
.unwrap_or_default();
let validation = Validation::new(Algorithm::HS256);
match decode::<Claims>(token, &DecodingKey::from_secret(secret.as_bytes()), &validation) {
Ok(token_data) => {
req.extensions_mut().insert(token_data.claims);
Ok(req)
}
Err(_) => {
let config = req.app_data::<Config>()
.map(|data| data.clone())
.unwrap_or_default();
Err((AuthenticationError::from(config).into(), req))
}
}
}
// 角色检查守卫
fn require_role(req: &actix_web::HttpRequest, role: &str) -> Result<Claims, Error> {
let claims = req.extensions()
.get::<Claims>()
.cloned()
.ok_or_else(|| ErrorUnauthorized("未认证"))?;
if claims.role != role {
return Err(ErrorUnauthorized("权限不足"));
}
Ok(claims)
}
async fn admin_endpoint(req: actix_web::HttpRequest) -> Result<String, Error> {
require_role(&req, "admin")?;
Ok("管理员数据".to_string())
}
async fn user_endpoint(req: actix_web::HttpRequest) -> Result<String, Error> {
let _claims = require_role(&req, "user")?;
Ok("用户数据".to_string())
}
}
48.7 授权架构模式
48.7.1 集中式授权
所有授权决策由专门的授权服务处理:
- 优点:策略统一管理,易于变更和审计
- 缺点:单点故障风险,网络延迟
- 适用:策略复杂、合规要求高的场景
48.7.2 分布式授权
各服务本地执行授权决策:
- 优点:低延迟,高可用
- 缺点:策略同步困难,一致性挑战
- 适用:微服务架构,性能敏感场景
48.7.3 混合模式
- 策略管理集中:统一的服务管理策略定义
- 策略执行分布:策略下发到各服务本地执行(如通过JWT携带权限声明)
#![allow(unused)]
fn main() {
// 混合模式:JWT携带权限声明,本地验证
#[derive(Debug, Clone)]
struct UserContext {
user_id: String,
roles: Vec<String>,
permissions: Vec<String>,
}
impl UserContext {
fn can(&self, permission: &str) -> bool {
self.permissions.contains(&permission.to_string())
}
fn has_role(&self, role: &str) -> bool {
self.roles.contains(&role.to_string())
}
}
// 中间件解析JWT并注入UserContext
// 处理器本地进行权限检查,无需远程调用
async fn create_order(ctx: UserContext) -> Result<String, Error> {
if !ctx.can("order:create") {
return Err(ErrorUnauthorized("无创建订单权限"));
}
// 执行业务逻辑
Ok("订单创建成功".to_string())
}
}
48.8 本章总结
| 主题 | 核心要点 |
|---|---|
| 授权基础 | 认证回答“你是谁“,授权回答“你能做什么“;授权检查必须在服务端强制执行 |
| DAC | 资源所有者自主控制权限,灵活但难以审计 |
| MAC | 系统强制策略,安全性最高,适合高安全环境 |
| RBAC | 通过角色管理权限,企业应用首选;支持角色继承和职责分离 |
| ABAC | 基于属性决策,最灵活但复杂;适合云原生和动态环境 |
| OAuth 2.0 | 授权码模式最安全,PKCE保护公共客户端;scope定义权限边界 |
| JWT | 自包含令牌,支持对称/非对称签名;必须验证exp/iss/aud等声明 |
| Rust生态 | casbin通用授权、oauth2客户端、jsonwebtokenJWT处理 |
48.9 练习建议
-
RBAC系统实现:使用Casbin实现一个完整的RBAC系统,包含用户管理、角色管理、权限分配和层级继承功能。
-
OAuth 2.0服务端:基于
oxide-auth或自建实现一个简化版OAuth 2.0授权服务器,支持授权码模式和PKCE。 -
ABAC策略引擎:设计一个基于资源属性的访问控制系统,实现时间、位置、设备类型等环境属性的策略评估。
-
JWT安全中间件:为Axum框架编写JWT认证和授权中间件,支持角色检查和scope验证,并实现自动令牌刷新。
-
权限审计系统:实现一个权限变更审计日志系统,记录所有策略变更、角色分配和访问拒绝事件,支持合规报告生成。
授权如划界,界内可自由驰骋,界外则寸步难行。好的授权系统,既不因过度限制而束缚业务创新,也不因放任自流而埋下安全隐患。在Rust的类型系统和所有权模型之上,我们可以构建出既高效又可靠的授权机制,让每一行代码都在明确的权限边界内安全运行。