把认证与授权讲透:从 Session、JWT、OAuth2 到 RBAC、ABAC,再到后台管理与开放平台 API 的安全模型

Source

把认证与授权讲透:从 Session、JWT、OAuth2 到 RBAC、ABAC,再到后台管理与开放平台 API 的安全模型

在大多数 Python 后端项目里,功能写出来并不算真正“上线”。真正决定系统能否安全、稳定、可扩展运行的,往往是那些最容易被轻视的基础设施能力:认证授权

很多开发者刚接触这块时,脑子里常常会混在一起:
Session 是不是就是登录?JWT 能不能替代 OAuth2?RBAC 和 ABAC 到底谁更高级?后台管理系统和开放平台 API,安全模型是不是一套方案就能通吃?

这些问题看似零散,本质上都在问一件事:“系统如何确认你是谁,以及在确认之后,允许你做什么。”

这篇文章不讲空泛概念,而是从工程实践出发,把边界讲清楚,把选型逻辑讲清楚,也把 Python 项目里真正落地时的做法讲清楚。


一、先把最核心的概念掰直

1.1 认证与授权,不是一回事

这是第一条,也是最重要的一条。

认证(Authentication):证明“你是谁”。
比如用户名密码登录、短信验证码登录、扫码登录、单点登录。

授权(Authorization):决定“你能做什么”。
比如你能不能访问某个接口、能不能导出报表、能不能删除用户、能不能调用某个 API。

可以把它理解成进写字楼:

  • 认证:门禁系统确认你是不是这栋楼的人
  • 授权:确认你能进几楼、能不能进机房、能不能开财务室的门

很多线上事故,都不是“没登录”,而是“登录了,但权限边界没设计好”。


二、先建立一个清晰的认知地图

很多团队一讨论安全模型,就把 Session、JWT、OAuth2、RBAC、ABAC 摊在一张桌子上比较,结果越比越乱。
原因是:它们根本不在同一个维度上。

2.1 正确的分层理解

可以把它们分成三层:

第一层:身份承载方式

也就是“登录状态放在哪里、怎么传递”。

  • Session
  • JWT
第二层:授权委托协议

也就是“第三方应用如何合法拿到访问资源的权利”。

  • OAuth2
第三层:权限决策模型

也就是“系统依据什么规则判断是否允许操作”。

  • RBAC
  • ABAC

换句话说:

  • Session / JWT 解决的是“身份状态怎么保存和传递”
  • OAuth2 解决的是“权限如何授权给第三方”
  • RBAC / ABAC 解决的是“权限规则怎么表达和计算”

这是理解边界的钥匙。


三、Session:适合传统 Web 后台的“有状态”方案

3.1 它是什么

Session 的典型流程是:

  1. 用户输入用户名密码
  2. 服务端校验成功
  3. 服务端生成一个 session
  4. 把 session_id 写到 Cookie
  5. 浏览器后续请求自动带上 Cookie
  6. 服务端根据 session_id 找到对应登录态

也就是说,状态存在服务端

3.2 优点

  • 实现简单,适合后台管理系统
  • 可以随时失效、踢人下线
  • 权限变更能快速生效
  • 和浏览器 Cookie 配合自然

3.3 缺点

  • 服务端要存状态
  • 集群环境需要共享 session(Redis 很常见)
  • 不太适合跨域、多终端、开放接口场景

3.4 一个简单的 Flask 思路

from flask import Flask, session, request, jsonify

app = Flask(__name__)
app.secret_key = "replace-with-strong-secret"

USERS = {
    
      
    "admin": {
    
      "password": "123456", "role": "admin"},
    "editor": {
    
      "password": "abc123", "role": "editor"},
}

@app.post("/login")
def login():
    data = request.json
    username = data.get("username")
    password = data.get("password")

    user = USERS.get(username)
    if not user or user["password"] != password:
        return jsonify({
    
      "error": "invalid credentials"}), 401

    session["user"] = username
    session["role"] = user["role"]
    return jsonify({
    
      "message": "login success"})

@app.get("/profile")
def profile():
    if "user" not in session:
        return jsonify({
    
      "error": "not logged in"}), 401
    return jsonify({
    
      "user": session["user"], "role": session["role"]})

这类模式非常适合“公司内部后台”“管理控制台”“运营系统”。


四、JWT:适合 API 化系统的“无状态”令牌方案

4.1 它是什么

JWT(JSON Web Token)本质上是一个自包含令牌
服务端签发后,客户端自己保存,之后每次请求通过 Header 带上:

Authorization: Bearer <token>

令牌中通常会包含:

  • 用户 ID
  • 角色
  • 过期时间
  • 签发者
  • 受众
  • 自定义声明

4.2 优点

  • 服务端可以无状态化
  • 天然适合前后端分离、移动端、多服务调用
  • 扩展性好,适合微服务和 API 网关

4.3 缺点

  • 一旦签发,天然不如 Session 那样“好撤销”
  • Token 泄漏风险更大
  • 如果把太多权限信息塞进 JWT,权限更新会变麻烦
  • 容易被误用成“万能安全方案”

4.4 Python 示例:生成和验证 JWT

import jwt
from datetime import datetime, timedelta, timezone

SECRET_KEY = "replace-with-strong-secret"

def create_access_token(user_id: int, role: str):
    payload = {
    
      
        "sub": str(user_id),
        "role": role,
        "exp": datetime.now(timezone.utc) + timedelta(minutes=30),
        "iat": datetime.now(timezone.utc),
        "iss": "my-backend"
    }
    return jwt.encode(payload, SECRET_KEY, algorithm="HS256")

def verify_access_token(token: str):
    return jwt.decode(token, SECRET_KEY, algorithms=["HS256"], issuer="my-backend")

token = create_access_token(user_id=1001, role="admin")
print(token)

decoded = verify_access_token(token)
print(decoded)

4.5 JWT 最容易踩的坑

很多项目会把 JWT 当成“权限数据库”,这是危险的。

比如把部门、菜单、按钮权限、数据范围全写进 token。
这样会导致两个问题:

  1. token 体积膨胀
  2. 一旦权限变更,旧 token 仍可能继续生效

更好的做法是:

  • JWT 里只放稳定、必要、低敏感的信息
  • 权限判断尽量回到服务端完成
  • 重要系统配合短期 access token + refresh token

五、OAuth2:不是登录方式,而是“授权框架”

5.1 这是最常见的误区

很多人说“我项目里用了 OAuth2 登录”。
这句话不算错,但并不精确。

OAuth2 的本质不是认证协议,而是授权框架。
它解决的是:

用户如何允许第三方应用代表自己去访问某个资源,而不暴露自己的密码。

典型例子:

  • 你用 GitHub 账号登录第三方网站
  • 一个应用申请读取你的日历
  • 一个系统允许第三方开发者调用你的 API

5.2 OAuth2 的角色

  • Resource Owner:资源拥有者,通常是用户
  • Client:第三方应用
  • Authorization Server:授权服务器
  • Resource Server:资源服务器

5.3 什么时候用 OAuth2

如果你的系统存在以下场景,就该考虑 OAuth2:

  • 第三方应用接入
  • 开放平台 API
  • 单点登录体系
  • “允许 A 系统代表用户访问 B 系统资源”

5.4 不要把 OAuth2 和 JWT 混为一谈

二者经常一起出现,但不是一个东西。

  • OAuth2:授权流程和规范
  • JWT:一种令牌格式

OAuth2 发的 access token 可以是 JWT,也可以是不透明字符串。
所以“OAuth2 vs JWT”本身就是一个错误比较题。


六、RBAC 与 ABAC:权限模型的边界

6.1 RBAC:基于角色的访问控制

RBAC(Role-Based Access Control)是最常见的企业权限模型。
思路非常直观:

给用户分配角色,给角色分配权限,用户通过角色获得权限。

例如:

  • admin:用户管理、角色管理、导出报表
  • editor:内容编辑、内容发布
  • viewer:只读查看

它的优点是:

  • 易理解
  • 易管理
  • 非常适合组织结构稳定的后台系统

6.2 ABAC:基于属性的访问控制

ABAC(Attribute-Based Access Control)更细粒度。
它不是简单看角色,而是综合考虑多个属性:

  • 用户属性:部门、岗位、地区、等级
  • 资源属性:创建者、分类、密级、所属项目
  • 环境属性:时间、地点、IP、设备
  • 动作属性:查看、编辑、删除、审批

比如:

只有“华东区”的销售主管,才能在工作时间内,查看自己负责客户的合同,并且只能查看未归档版本。

这类规则用 RBAC 会变得很臃肿,而 ABAC 很自然。

6.3 RBAC 与 ABAC 的真实边界

不是“谁更高级”,而是“谁更适合”。

RBAC 更适合:
  • 后台菜单权限
  • 按钮权限
  • 功能模块权限
  • 管理关系清晰的组织型系统
ABAC 更适合:
  • 数据权限
  • 多租户隔离
  • 跨部门协作
  • 复杂审批流程
  • 风控与动态策略

工程上最常见、也最实用的方案其实是:

RBAC 管功能权限,ABAC 管数据权限。

这是很多成熟系统的折中答案。


七、Session、JWT、OAuth2、RBAC、ABAC 的边界,一句话讲明白

如果只能用最简短的话总结:

  • Session:服务端保存登录状态
  • JWT:客户端持有签名令牌
  • OAuth2:第三方授权框架
  • RBAC:按角色授予权限
  • ABAC:按属性动态判定权限

再进一步:

  • Session / JWT 是“你怎么证明自己
  • OAuth2 是“你怎么把访问权交给别人
  • RBAC / ABAC 是“你凭什么能访问这个资源

八、后台管理系统与开放平台 API,安全模型为什么必须区分

这是实践中最容易“一锅炖”的地方。

很多团队把后台管理系统和开放平台 API 用同一套安全设计,最后往往两头都不满意。


九、后台管理系统:重点是人员身份、组织关系与操作审计

后台系统的访问主体通常是:

  • 公司员工
  • 运营人员
  • 财务人员
  • 管理员
  • 审批人员

这个场景的特点是:

  1. 浏览器访问为主
  2. 组织结构清晰
  3. 权限和岗位强相关
  4. 对踢人下线、强制失效、审计留痕要求高

9.1 推荐模型

  • 认证:Session 或短期 JWT + HttpOnly Cookie

  • 授权:RBAC 为主,ABAC 为辅

  • 补充能力

    • 登录风控
    • 操作日志
    • 二次确认
    • 敏感操作审批
    • 多因素认证

9.2 典型权限拆分

  • 菜单权限:是否看得到菜单
  • 页面权限:是否能访问页面
  • 按钮权限:是否能点击导出、删除、审批
  • 数据权限:只能看本部门、本区域、本人创建的数据

你会发现:

  • 菜单、页面、按钮适合 RBAC
  • 数据范围适合 ABAC

9.3 一个简化的权限判断示例

def can_export_report(user, report):
    # RBAC:先看角色是否具备功能权限
    if "report:export" not in user.permissions:
        return False

    # ABAC:再看数据范围
    if user.region != report.region:
        return False

    # 环境属性:只允许公司内网导出
    if not user.is_intranet:
        return False

    return True

十、开放平台 API:重点是应用身份、授权范围与调用隔离

开放平台 API 的访问主体不是“公司员工”,而通常是:

  • 第三方开发者
  • 第三方应用
  • 外部服务
  • 服务器到服务器的调用方

这个场景的特点是:

  1. 接口调用是主形态
  2. 不是以浏览器 Cookie 为中心
  3. 强调 client 身份、scope、配额、签名、防刷
  4. 要考虑第三方滥用、泄漏、越权、重放攻击

10.1 推荐模型

  • 认证/授权框架:OAuth2

  • 令牌形态:Access Token(必要时配合 Refresh Token)

  • 权限表达:scope + 资源级策略

  • 安全补充

    • client_id / client_secret
    • API 签名
    • 频率限制
    • IP 白名单
    • 审计与告警
    • 密钥轮换

10.2 为什么后台系统那套不能直接照搬

后台系统强调“人”和“组织”,
开放平台强调“应用”和“授权范围”。

比如:

  • 后台里是“张三是否能删除订单”
  • 开放平台里是“某个第三方应用是否有 orders.readorders.write 的 scope”

这完全不是一类问题。

10.3 一个开放平台 API 的思路

from fastapi import FastAPI, Header, HTTPException

app = FastAPI()

TOKENS = {
    
      
    "token_readonly": {
    
      "client_id": "app_001", "scopes": ["orders.read"]},
    "token_readwrite": {
    
      "client_id": "app_002", "scopes": ["orders.read", "orders.write"]},
}

def require_scope(token: str, required_scope: str):
    payload = TOKENS.get(token)
    if not payload:
        raise HTTPException(status_code=401, detail="invalid token")
    if required_scope not in payload["scopes"]:
        raise HTTPException(status_code=403, detail="insufficient scope")
    return payload

@app.get("/api/orders")
def list_orders(authorization: str = Header(...)):
    token = authorization.removeprefix("Bearer ").strip()
    payload = require_scope(token, "orders.read")
    return {
    
      "client_id": payload["client_id"], "orders": []}

这里的重点不是“这个人是管理员吗”,而是:

  • 调用方是谁
  • 拿到了什么 scope
  • 能调用哪些资源
  • 调用频率是否超限

十一、一个实用结论:两类系统的安全模型不要混为一谈

后台管理系统更适合:

  • 认证:Session / Cookie 或受控 JWT
  • 授权:RBAC 主体 + ABAC 补充
  • 关键能力:审计、数据权限、二次验证、可撤销

开放平台 API 更适合:

  • 认证/授权:OAuth2
  • 令牌:Access Token / Refresh Token
  • 权限表达:scope、client、资源粒度策略
  • 关键能力:限流、签名、防重放、密钥管理、租户隔离

一句话:

后台是“管人”,开放平台是“管应用”。”


十二、Python 项目落地时的最佳实践

12.1 不要自己发明密码存储方案

密码一定要:

  • 加盐哈希
  • 使用成熟算法
  • 不明文、不可逆

推荐使用 bcryptargon2 相关库,而不是自己 md5(password)

12.2 认证与权限判断分层

常见反模式是把所有判断都写进控制器里。
更好的结构是:

  • 身份验证中间件
  • 权限检查装饰器/依赖项
  • 独立策略层
  • 审计日志层

12.3 敏感操作永远不要只靠前端控制

前端隐藏按钮不是权限控制。
真正的权限判断必须在服务端。

12.4 短 token + 刷新机制优于超长有效期

尤其是对外 API 和移动端应用。
长效 token 一旦泄漏,代价极高。

12.5 权限系统要留“解释能力”

最理想的权限系统,不仅能告诉你“拒绝了”,还要能告诉你“为什么拒绝”。

例如:

  • 缺少 orders.export 权限
  • 非所属部门数据
  • 超出调用配额
  • 当前 IP 不在白名单

这对排障和审计非常关键。


十三、一个常见的工程组合拳

如果让我给大多数 Python 团队一套稳健建议,我会这样搭:

对后台管理系统

  • 登录:用户名密码 + MFA
  • 会话:Session 或短 JWT 放 HttpOnly Cookie
  • 权限:RBAC 控菜单/按钮,ABAC 控数据范围
  • 安全:CSRF 防护、操作审计、异常告警

对开放平台 API

  • 接入:OAuth2
  • 令牌:短期 Access Token + Refresh Token
  • 权限:scope + client 级限制
  • 安全:签名、限流、重放防护、密钥轮换

这套设计不花哨,但非常耐用。


十四、写在最后:安全不是“加一个登录接口”就结束了

很多系统早期都把认证授权看作配套功能,等业务长大后才发现,它其实是系统演进的地基。

一个好的安全模型,带来的不仅是“防攻击”,更是:

  • 更清晰的系统边界
  • 更低的协作成本
  • 更强的可审计性
  • 更可控的扩展能力

从这个角度看,认证与授权不是枯燥的安全术语,而是软件工程成熟度的体现。

当你把 Session、JWT、OAuth2、RBAC、ABAC 的边界想明白之后,很多架构选择会突然变得顺理成章。
你会知道什么时候该“存状态”,什么时候该“发令牌”,什么时候该“看角色”,什么时候必须“看属性”。

这,才是真正的工程判断力。


结语与互动

如果你正在做 Python 后端,或者正在设计一个管理后台、SaaS 平台、开放 API,我很建议你问自己三个问题:

  1. 我现在解决的是“认证问题”还是“授权问题”?
  2. 我的访问主体到底是“人”,还是“应用”?
  3. 我的权限规则到底是“角色驱动”,还是“属性驱动”?

把这三个问题想明白,你的安全设计就已经成功了一大半。

你在项目里更偏向 Session 还是 JWT?
你有没有遇到 RBAC 不够用、最后不得不引入数据权限模型的情况?
欢迎把你的实战经验、踩坑故事和设计取舍分享出来。