Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第四十八变 授权

知其为谁,方能定其可为。认证辨明身份,授权划定边界。

授权之道,在于回答“你能做什么“——从简单的访问控制列表到复杂的属性策略引擎,从单一系统的权限管理到分布式服务的统一授权,授权机制决定了资源的安全边界。

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统一RBACRBAC1 + 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 模型对比

特性DACMACRBACABAC
灵活性极高
安全性极高
管理复杂度
审计难度
适用场景个人系统军事/政府企业应用云原生/微服务
性能

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(&params)
        .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 练习建议

  1. RBAC系统实现:使用Casbin实现一个完整的RBAC系统,包含用户管理、角色管理、权限分配和层级继承功能。

  2. OAuth 2.0服务端:基于oxide-auth或自建实现一个简化版OAuth 2.0授权服务器,支持授权码模式和PKCE。

  3. ABAC策略引擎:设计一个基于资源属性的访问控制系统,实现时间、位置、设备类型等环境属性的策略评估。

  4. JWT安全中间件:为Axum框架编写JWT认证和授权中间件,支持角色检查和scope验证,并实现自动令牌刷新。

  5. 权限审计系统:实现一个权限变更审计日志系统,记录所有策略变更、角色分配和访问拒绝事件,支持合规报告生成。


授权如划界,界内可自由驰骋,界外则寸步难行。好的授权系统,既不因过度限制而束缚业务创新,也不因放任自流而埋下安全隐患。在Rust的类型系统和所有权模型之上,我们可以构建出既高效又可靠的授权机制,让每一行代码都在明确的权限边界内安全运行。