后端鉴权体系:认证、授权与访问控制

后端鉴权体系:认证、授权与访问控制

后端鉴权先拆成三件事:

概念英文解决的问题
认证Authentication你是谁?
授权Authorization你能做什么?
访问控制Access Control是否允许这次操作?

很多人把它们都叫「鉴权」,但严格来说:

  • Authentication = 身份确认
  • Authorization = 权限判断
  • Access Control = 在具体 API、页面或资源上执行放行或拒绝

认证和授权回答规则问题,访问控制回答执行问题。用户是谁、拥有什么权限,最终都要落到一次具体请求上:这个 API 能不能调,这个页面能不能进,这个文件能不能下载

这篇文章按真实请求链路展开:先看用户如何完成登录,再看系统如何保持登录态;然后讨论机器调用、私有资源访问、访问控制落点和常见安全风险

从登录到访问资源的流程

auth

这张图表达的是一条完整后端鉴权链路:

用户发起请求

后端识别身份:Session / JWT / API Key

后端判断权限:是否能访问接口或资源

访问控制执行:API / 页面 / 资源是否放行

业务代码执行

返回数据或拒绝请求

章节与流程对应

这些方案不是并列替代关系,而是落在请求链路的不同阶段

章节所在流程主要职责
OAuth 2.0登录入口 / 第三方授权委托从 GitHub、Google 等平台确认第三方身份
Session / Cookie本站登录态保持让浏览器后续请求自动带上本站会话凭证
JWT本站登录态保持 / API 身份凭证让前端、移动端或服务通过 Token 证明身份
API Key机器调用身份识别识别哪个应用、服务或开发者在调用接口
Signed URL私有资源临时访问把一次被允许的资源访问封装成短期 URL
Access ControlAPI、页面、资源放行判断在具体入口决定这次操作是否允许
Middleware / Guard请求链路执行位置统一解析身份,并执行权限与访问控制
CSRF / XSS安全风险与防护防止凭证被滥用、窃取或伪造请求

后端鉴权不是单个技术点,而是一条请求链路:用户先完成登录,后端再把这次身份确认延续到后续请求里

HTTP 本身是无状态的。用户在登录页输入账号密码,或通过 GitHub / Google 授权,只能证明“这一刻登录成功”。下一次请求进来时,后端仍然需要知道它是谁发来的

登录成功后,后端通常会创建自己的登录态:要么保存 Session,让浏览器带上 session_id;要么签发 JWT,让前端在请求 API 时携带它。OAuth 只负责引入第三方身份,不替你的系统维护本站登录态

OAuth 2.0:第三方登录与授权委托

OAuth 2.0 常用于第三方登录和第三方授权。当身份来源不在你的系统里,而是在 GitHub、Google、Notion 这类平台上,就会进入 OAuth 场景

常见例子包括:使用 GitHub 登录网站、使用 Google 登录后台、允许某个应用访问你的 Notion 数据

OAuth 解决的不是“本站账号密码怎么验证”,而是:让一个应用在用户授权后,有限地访问另一个平台的资源

OAuth 2.0 角色

角色说明
Resource Owner资源所有者,通常是用户
Client第三方应用
Authorization Server授权服务器
Resource Server资源服务器

示例:用 GitHub 登录某网站

  • 用户:Resource Owner
  • 某网站:Client,也就是你的前端和后端
  • GitHub 授权页面:Authorization Server
  • GitHub API:Resource Server

你的后端在 OAuth 中做什么

使用 OAuth 并不代表可以省掉自己的后端。GitHub / Google 负责确认第三方账号身份,但你的系统仍然要负责本站用户、权限、订单、文章和私有资源

后端不能直接相信前端传来的用户信息。正确做法是:前端只拿到 GitHub 回跳带回来的 code,再把 code 交给你的后端

你的后端用 codeclient_idclient_secret 去 GitHub 服务器换取 access token。然后再用 access token 请求 GitHub API,获取可信的用户 ID、邮箱或用户名

拿到第三方身份后,后端再查找或创建本地用户。例如把 GitHub 的 id 绑定到你系统里的 user_id,最后再签发你自己域名下的 Session 或 JWT

oauth

OAuth 登录的核心流程可以理解为:

用户点击 GitHub / Google 登录

跳转到第三方授权页面

第三方平台回跳并带回 code

你的后端用 code 换取 access token

你的后端请求第三方用户信息

绑定或创建本站用户

签发本站 Session 或 JWT

关键点: GitHub 的 access token 用来访问 GitHub API;你自己系统的 Session 或 JWT 用来访问你自己的后端 API。两者不是同一个登录态

登录完成之后,系统要解决的是“下一次请求怎么认出这个用户”。这就进入本站登录态保持

无论用户是账号密码登录,还是 OAuth 登录,进入你的系统之后,都需要一种本站可识别的凭证

常见选择有两类:

方案凭证在哪里状态在哪里常见场景
Session / Cookie浏览器 Cookie 中的 session_id后端 Session Store传统 Web 应用
JWTHeader、Storage 或 Cookie 中的 TokenToken 自身携带声明前后端分离、移动端、API

这两种方案解决的是同一个问题:用户不用每次请求都重新登录,后端仍然能识别请求身份

基本原理

Session 鉴权是传统 Web 系统最常见的登录态方案。它的核心是:登录状态保存在你的后端,浏览器只保存一个可以索引这份状态的 ID

这里要先区分两个东西:Session 是后端保存的登录状态,Cookie 是浏览器保存并自动发送的请求凭证

浏览器里通常只存一个 session_id。真正的用户信息、登录时间、过期时间、角色等状态,放在你的后端 Session Store 里,例如 Redis 或数据库

session_cookie

Session / Cookie 的请求流程是:

用户登录成功

后端创建 Session,并保存到 Redis / 数据库

后端通过 Set-Cookie 写入 session_id

浏览器后续请求自动携带 Cookie

后端用 session_id 查 Session

查到后确认当前用户身份

Cookie 是浏览器自动管理的小段数据。Session / Cookie 登录不是把 Session 整个塞进浏览器,而是让 Cookie 携带一个 session_id,后端再用它查到对应的 Session

后端设置:

Set-Cookie: session_id=abc123; HttpOnly; Secure; SameSite=Lax

浏览器携带:

Cookie: session_id=abc123

Session 存储

存储方式说明
内存简单,但服务重启会丢失
Redis最常见,适合分布式后端
数据库可持久化,但查询压力较大

优缺点

维度优点代价
安全性真实用户信息存储在后端,浏览器只拿到 session_idCookie 会自动携带,需要额外防 CSRF
状态控制可以主动删除 Session,让用户立即下线后端必须保存 Session 状态
分布式部署权限变更可以在后端即时生效多台服务器需要共享 Session Store,例如 Redis
使用场景适合普通 Web 应用,浏览器天然支持 Cookie移动端和开放 API 不一定适合 Cookie 流程
Set-Cookie: session_id=abc123;
  HttpOnly;
  Secure;
  SameSite=Lax;
  Path=/;
  Max-Age=7200
属性作用
HttpOnly禁止 JavaScript 读取 Cookie,降低 XSS 窃取风险
Secure只允许 HTTPS 传输
SameSite限制跨站请求携带 Cookie,缓解 CSRF
Max-Age / Expires设置过期时间
Path限制 Cookie 生效路径
Domain限制 Cookie 生效域名

推荐配置:

  • HttpOnly = true
  • Secure = true
  • SameSite = LaxStrict

JWT 鉴权

JWT 全称是 JSON Web Token,常用于前后端分离、移动端和微服务系统

和 Session 相比,JWT 把一部分身份声明放进 Token 里,后端可以通过签名验证它是否可信,而不一定每次都查询 Session Store

JWT 结构

JWT 的格式是 xxxxx.yyyyy.zzzzz

它由三部分组成:Header.Payload.Signature

部分作用
Header声明算法和 Token 类型
Payload存放用户信息和过期时间等声明
Signature签名,防止 Token 被篡改

Payload 示例:

{
  "sub": "user_123",
  "role": "admin",
  "exp": 1710000000
}

注意: JWT 的 Payload 默认只是 Base64URL 编码,不是加密。不能把密码、手机号、身份证号等敏感信息放进去

JWT 鉴权流程

jwt

JWT 的请求流程是:

用户登录成功

后端签发 JWT

前端保存 JWT

请求 API 时携带 Authorization Header

后端验证签名和过期时间

验证通过后解析用户身份

请求示例:

GET /api/user/profile
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

JWT 验证逻辑

后端收到 JWT 后,通常检查:

  1. Token 格式是否正确
  2. 签名是否正确
  3. exp 是否过期
  4. iss 是否可信
  5. aud 是否匹配当前服务
  6. sub 对应的用户是否仍然有效

常见字段:

字段全称作用
subSubject用户 ID
expExpiration Time过期时间
iatIssued At签发时间
nbfNot Before生效时间
issIssuer签发方
audAudience接收方
jtiJWT IDToken 唯一 ID,可用于黑名单

优缺点

维度优点代价
状态管理后端不一定需要存储 Token,验证签名即可识别身份主动失效困难,Token 未过期前仍可能有效
分布式系统多服务只要共享密钥或公钥即可验证权限变更不一定即时生效,旧 Token 可能继续携带旧权限
使用场景适合移动端、前后端分离和开放 APIToken 较长,会增加请求头体积
跨域请求通过 Authorization Header 携带,不依赖 Cookie 自动机制Payload 可被解码查看,不能存敏感数据
长期登录可以配合 Refresh Token 延长登录态需要额外处理轮换、吊销和泄露检测

Access Token + Refresh Token

成熟系统通常不会只用一个长期 JWT,而是拆成:

Token生命周期用途
Access Token5 分钟到 30 分钟访问 API
Refresh Token数天到数周换取新的 Access Token

流程:

refresh_jwt

Access Token 和 Refresh Token 的协作流程是:

前端携带 Access Token 请求 API

Access Token 未过期,后端正常返回数据

Access Token 过期,前端使用 Refresh Token 请求刷新接口

后端验证 Refresh Token

签发新的 Access Token

前端用新的 Access Token 继续访问 API

更安全的设计:Refresh Token Rotation

每次刷新时:

  • 旧 Refresh Token 失效
  • 新 Refresh Token 发放

这样如果旧 Token 被盗后再次使用,后端可以检测异常

API Key 鉴权

OAuth、Session 和 JWT 多数时候处理“用户身份”。API Key 更常处理“哪个应用、服务或开发者在调用接口”

key

API Key 的调用流程是:

服务端应用保存 API Key

调用接口时在 Header 中携带 API Key

API 服务校验 Key 是否存在、是否过期、权限是否匹配

校验通过后识别调用方应用

继续执行接口逻辑

API Key 常用于服务端调用服务端,不适合普通用户登录

示例:

GET /v1/models
Authorization: Bearer sk_xxxxx

或者:

X-API-Key: abc123

API Key 的本质

API Key 不是用户会话,而是调用方身份凭证,通常对应 application_idclient_idservice_id

它适合第三方平台接入、内部服务调用、开放平台和脚本访问,不适合直接代表终端用户

API Key 首先属于 Authentication:它识别调用方是谁。生产环境里,它通常还会绑定 scope、配额、环境、来源限制和资源范围,因此也会参与授权与访问控制

判断点对应概念示例
这个 Key 是否存在、是否有效Authenticationsk_xxx 对应 client_id=app_123
这个 Key 有哪些 scopeAuthorizationmodels:readorders:write
这次请求能不能访问这个接口或资源Access Control只能读当前项目下的数据

所以 API Key 可以这样理解:

API Key 识别调用方是谁

Scope / Role 决定它理论上能做什么

Access Control 判断这次具体请求是否放行

安全风险

风险说明
容易泄露写在前端代码里会被直接看到
权限过大一个 Key 可能能访问大量资源
不适合浏览器前端前端无法安全保存 Secret
轮换成本泄露后需要重新生成和替换

重要原则: 不要把真正的 Secret API Key 放进 React / Vue / 小程序前端代码里。因为前端代码最终会暴露给用户

Signed URL

用户登录态解决的是“谁在访问 API”。但图片、视频、附件下载通常不通过 JSON API 返回,而是经过对象存储或 CDN

Signed URL 就是把一次资源访问授权封装进临时 URL。后端先确认当前用户有权限,再生成带签名和过期时间的链接

常用于:

  • 私有图片访问
  • 私有视频访问
  • 私有文件下载
  • 对象存储访问
  • CDN 防盗链
  • R2 / S3 私有资源访问

Signed URL 的本质

Signed URL 是把权限编码进 URL 的临时访问凭证。它不是登录态,也不表示“当前用户是谁”,更像后端在确认权限后,为某个资源发放的短期通行证

示例:

https://media.example.com/file/abc.jpg?exp=1710000000&sig=xxxx
参数作用
exp过期时间
sig签名
path被保护的资源路径
user_id可选,绑定用户
nonce可选,防重放
ip可选,绑定 IP

Signed URL 要按阶段拆开看:

阶段对应概念说明
生成前Authentication先确认当前请求是谁发来的
生成前Authorization判断这个身份是否有下载、查看或播放权限
生成时Access Control只给允许访问的资源生成短期 URL
使用时Access ControlCDN、Worker 或存储服务校验签名、路径和过期时间

所以 Signed URL 本身更接近 Access Control 的执行结果:它把一次已经被允许的资源访问,封装成短期、可校验的 URL

用户请求查看私有图片

Session / JWT 确认用户身份

Policy 判断用户是否能访问这张图片

后端生成只针对这张图片的 Signed URL

CDN / Worker 校验签名后返回图片

这里的 Session / JWT 负责认证,Policy 负责授权判断,Signed URL 负责把访问控制落到私有资源读取上

适用场景

场景是否适合
私有图片临时访问✓ 适合
私有视频播放✓ 适合
文件下载✓ 适合
大文件 CDN 分发✓ 适合
用户登录状态维护✗ 不适合
API 登录鉴权✗ 不适合

它更像是:资源级临时通行证,不是完整的用户登录系统

与防盗链的区别

Referer 防盗链主要依赖请求来源,Referer 可能缺失或被伪造,只适合轻量防盗刷

Signed URL 依赖服务端签名,可以绑定路径、过期时间、用户或 IP,更适合私有资源访问

Access Control:访问控制落在哪里

Authentication 和 Authorization 之后,还需要 Access Control

认证确认当前请求是谁发来的。授权告诉系统这个身份拥有哪些角色、权限或策略。访问控制则是在具体位置做最终判断:这一次操作到底放不放行

这三层可以这样理解:

层次关注点典型问题
Authentication身份你是谁?
Authorization权限你拥有什么权限?
Access Control执行这次 API、页面或资源访问是否允许?

API 层访问控制

API 层访问控制判断的是:当前身份能不能调用这个接口

例如:

DELETE /api/posts/post_123
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

这时只知道用户已登录还不够。后端还要判断:

  • 这个用户是否有 post:delete 权限
  • 这个用户是否是文章作者
  • 这个接口是否只允许管理员调用
  • 这个操作是否需要二次确认或审计日志

所以 API 层常见的判断不是单纯 isLogin,而是:

can(user, "delete", post)

页面层访问控制

页面层访问控制判断的是:当前用户能不能进入某个页面或看到某块界面

例如后台系统里:

页面访问要求
/admin必须登录
/admin/users必须是管理员
/billing必须属于当前团队
/settings/security可能需要重新验证密码

页面层控制可以放在服务端路由、SSR Loader、前端 Router Guard 或边缘函数里。但敏感页面不能只依赖前端隐藏菜单,因为用户仍然可以直接请求接口

正确做法是:页面层负责用户体验和入口拦截,API 层仍然负责真正的数据保护

资源层访问控制

资源层访问控制判断的是:当前身份能不能访问某一个具体对象

这是最容易被忽略的一层。很多系统只检查“用户已登录”,却没有检查“这个订单、文件、文章或图片是不是属于这个用户”

典型资源包括:

资源判断方式
订单order.user_id == current_user.id
团队文档用户是否属于该团队
私有图片用户是否拥有该图片访问权
文章草稿用户是否是作者或编辑
下载附件用户是否有该文件的下载授权

常见漏洞就是 IDOR:Insecure Direct Object Reference。用户把 URL 里的 order_id=1001 改成 order_id=1002,如果后端只检查登录态、不检查资源归属,就可能泄露别人的数据

资源层访问控制通常要在数据库查询或业务服务层完成:

先查当前用户可访问的资源

再按资源 ID 查询

查不到就返回 404 或 403

例如不要先查任意订单,再判断能不能看:

SELECT * FROM orders WHERE id = :order_id;

更安全的写法是把访问范围放进查询条件:

SELECT * FROM orders
WHERE id = :order_id
  AND user_id = :current_user_id;

如果是团队、组织、项目空间,还要把 team_id、成员关系或角色关系纳入查询

中间件与 Guard:把鉴权落到请求链路

前面的方案最终都要落到请求处理链路里。实际项目中,鉴权通常做成 Middleware / Guard / Interceptor

中间件负责统一解析身份:从 Cookie 里取 session_id,或从 Authorization Header 里取 JWT / API Key。解析成功后,再把用户或调用方信息挂到请求上下文

Guard 或 Interceptor 负责访问控制:当前身份能不能访问这个接口、页面、资源或操作

流程:

Request → Auth Middleware:解析身份

Access Control Guard:检查接口、页面或资源访问

Controller → Service → Database

实际代码里可以拆成两类函数:

authenticate(request) → user / client / anonymous
authorize(context, action, resource) → allow / deny

前者负责识别身份,后者负责把权限规则落到这次接口、页面或资源访问上

CSRF 与 XSS

鉴权设计还必须考虑两个经典风险:CSRFXSS

前面讨论的是凭证如何产生、携带和验证;这里要看凭证会怎样被滥用。CSRF 和 XSS 分别对应 Cookie 自动携带、前端脚本执行这两类风险

CSRF

全称: Cross-Site Request Forgery(跨站请求伪造)

问题来源: 浏览器会自动携带 Cookie

攻击示意:

用户已登录 bank.com

用户访问恶意网站 evil.com

evil.com 诱导浏览器请求 bank.com/transfer

浏览器自动带上 bank.com 的 Cookie

如果后端没有 CSRF 防护,可能执行操作

防护方式:

  • SameSite Cookie
  • CSRF Token
  • Origin / Referer 校验
  • 关键操作二次确认

XSS

全称: Cross-Site Scripting(跨站脚本攻击)

问题来源: 攻击者让恶意 JavaScript 在你的网站页面中执行

风险:

  • 读取 localStorage 中的 Token
  • 发起用户身份下的请求
  • 篡改页面内容
  • 窃取用户信息

防护方式:

  • 输入过滤
  • 输出转义
  • Content Security Policy
  • HttpOnly Cookie
  • 避免 dangerouslySetInnerHTML
  • 依赖库安全更新

如何选择

不同鉴权方案不是互相替代,而是服务于不同位置

场景更常见的选择
第三方登录OAuth 2.0 + 本站 Session / JWT
普通 Web 应用Session / Cookie
前后端分离或移动端JWT
后端服务调用API Key
私有图片、视频、附件Signed URL
API、页面、资源放行判断Access Control Guard / Policy

如果只做普通网站登录,Session / Cookie 往往更直接。如果需要移动端、开放 API 或多服务验证,JWT 会更常见。OAuth 只是登录入口,真正访问你自己后端时,仍然要落到你的 Session 或 JWT

无论选择哪种登录态,最终都不能跳过访问控制。登录态只能说明请求来自谁,权限策略只能说明它理论上能做什么,真正的系统安全来自每一次 API、页面和资源访问时的实际校验

总结

后端认证、授权与访问控制可以按一条链路理解:先确认身份,再保持登录态,然后在每次请求里解析身份、判断权限,并在具体 API、页面或资源层执行放行或拒绝

OAuth 负责引入第三方身份;Session / Cookie 和 JWT 负责维持本站登录态;API Key 负责机器调用;Signed URL 负责私有资源的短期访问;Access Control 负责把权限规则落到一次具体操作上

真正落地时,所有凭证都要经过中间件统一解析,并配合 CSRF、XSS、过期时间、吊销、权限策略和访问控制,才能构成完整的后端鉴权体系

参考文献

  • Auth book:Pilcrow 维护的一本免费、无广告的 Web 应用认证指南,侧重认证与登录系统的实践实现,内容覆盖认证方式、Session、认证会话、邮箱、密码、浏览器端存储、CSRF、Passkeys、WebAuthn 等主题,并提供 Go 语言完整示例项目