认证鉴权

从 JWT 登录流程理解认证和权限管理

这篇笔记记录一下我对 JWT 认证方式的理解。 一开始我知道 JWT 经常用来做登录,但脑子里其实是糊的: 这句话当然没错,但它没有解释清楚几个问题: token 里面到底有什么? 后端为什么相信这个 token? token 被改了怎么办? 不同权限怎么管理? 退出登录和过期又怎么处理? 后来我发现,JWT 的核心不

2026年6月13日7 分钟阅读
从 JWT 登录流程理解认证和权限管理

这篇笔记记录一下我对 JWT 认证方式的理解。

一开始我知道 JWT 经常用来做登录,但脑子里其实是糊的:

text
用户登录以后,后端返回一个 token。
前端以后每次请求带上 token。

这句话当然没错,但它没有解释清楚几个问题:

  • token 里面到底有什么?
  • 后端为什么相信这个 token?
  • token 被改了怎么办?
  • 不同权限怎么管理?
  • 退出登录和过期又怎么处理?

后来我发现,JWT 的核心不是“把用户信息随便塞到前端”,而是:

text
后端签发一份可以校验真伪的身份声明。

JWT 是什么

JWT 全称是 JSON Web Token。

它通常长这样:

text
xxxxx.yyyyy.zzzzz

中间用两个点分成三段:

text
Header.Payload.Signature

第一段 Header 说明签名算法和 token 类型。

第二段 Payload 放声明信息,比如用户 ID、角色、过期时间。

第三段 Signature 是签名,用来证明前两段没有被篡改。

一个典型登录流程

可以把 JWT 登录流程拆成几步:

text
用户提交账号密码
  -> 后端校验账号密码
  -> 校验通过后生成 JWT
  -> 前端保存 JWT
  -> 后续请求带上 JWT
  -> 后端验证 JWT
  -> 验证通过后处理请求

用接口形式看,大概是这样:

http
POST /login
Content-Type: application/json

{
  "username": "xiaoming",
  "password": "123456"
}

后端校验成功后返回:

json
{
  "accessToken": "xxxxx.yyyyy.zzzzz"
}

前端之后请求用户信息:

http
GET /profile
Authorization: Bearer xxxxx.yyyyy.zzzzz

后端从 Authorization 里取出 token,验证它是否合法。

后端为什么相信 JWT

关键在签名。

假设 Payload 是:

json
{
  "sub": "10001",
  "role": "admin",
  "exp": 1781000000
}

如果用户自己把 role 改成 admin,Payload 内容确实可能被改。

但改完以后,原来的签名就对不上了。

后端验证时会用自己的密钥重新计算签名:

text
拿 Header 和 Payload
  -> 用服务端密钥重新计算签名
  -> 和 JWT 第三段对比

只要密钥没有泄漏,攻击者就不能伪造一个合法签名。

所以 JWT 不是靠“前端不会改”来保证安全,而是靠签名校验。

Payload 里应该放什么

JWT 的 Payload 适合放少量身份声明。

常见字段有:

字段含义
sub用户 ID
iat签发时间
exp过期时间
iss签发方
aud接收方
role用户角色
permissions权限列表

不要把敏感信息放进去。

JWT 的 Payload 只是 Base64URL 编码,不是加密。

也就是说,别人拿到 token 后,虽然不能随便改,但可以解码看到里面的内容。

所以不要放:

  • 密码。
  • 身份证号。
  • 手机验证码。
  • 私密配置。
  • 不希望前端或中间人看到的信息。

前端怎么保存 JWT

常见有几种方式。

放在内存里

比如 React 状态或普通变量。

优点是刷新页面后就没了,暴露时间短。

缺点是用户一刷新就要重新登录,或者需要配合刷新 token。

放在 localStorage

ts
localStorage.setItem("accessToken", token);

优点是实现简单,刷新后还在。

缺点是 JavaScript 可以读取。如果出现 XSS,token 容易被偷。

由后端设置:

http
Set-Cookie: access_token=...; HttpOnly; Secure; SameSite=Lax

优点是前端脚本读不到。

缺点是要额外考虑 CSRF、防跨域 Cookie、接口调用方式等问题。

我现在不会简单说哪种永远最好,而是看系统安全要求、前后端部署方式和团队能力。

权限怎么管理

JWT 只能说明“这个用户是谁”以及“签发时声明了什么”。

真正的权限管理还需要业务规则。

常见做法有三层。

第一层:认证

认证解决的是:

text
你是谁?

后端验证 JWT,拿到用户 ID。

如果 token 无效或过期,直接返回:

http
401 Unauthorized

第二层:角色

角色解决的是粗粒度身份:

text
你是普通用户、管理员,还是超级管理员?

例如:

json
{
  "sub": "10001",
  "role": "admin"
}

后端在访问管理接口时检查:

text
只有 role = admin 才能访问

不满足时返回:

http
403 Forbidden

第三层:权限点

权限点更细。

例如:

json
{
  "permissions": [
    "user:read",
    "user:update",
    "order:refund"
  ]
}

接口可以要求:

text
访问退款接口必须拥有 order:refund

这种方式比单纯角色更灵活。

权限信息要不要直接放进 JWT

可以放,但要想清楚更新问题。

如果用户登录时签发了一个 token:

json
{
  "role": "admin"
}

后来管理员把他的权限降级了,但旧 token 还没过期。

如果后端只相信 JWT 里的角色,那么在 token 过期前,他可能仍然拥有旧权限。

所以常见策略有几种:

  1. JWT 只放用户 ID,权限每次从数据库或缓存查。
  2. JWT 放角色和权限,但 access token 有效期设置很短。
  3. 维护 token 版本号,用户权限变化后让旧 token 失效。
  4. 高风险操作实时查权限,普通操作使用 token 中的声明。

没有绝对答案。权限变化频繁、风险高的系统,就不能只靠长期 JWT 里的旧信息。

Access Token 和 Refresh Token

很多系统会拆成两种 token。

Access Token 用来访问接口,有效期短。

Refresh Token 用来换新的 Access Token,有效期长。

流程大概是:

text
登录成功
  -> 返回 access token 和 refresh token
  -> 前端用 access token 请求接口
  -> access token 过期
  -> 用 refresh token 换新的 access token

这样可以减少长期暴露 access token 的风险。

但 refresh token 权限更敏感,保存和失效策略要更严格。

退出登录怎么处理

JWT 的一个特点是自包含。

后端只要能验证签名和过期时间,就可以认为它有效。

这带来一个问题:

text
用户退出登录以后,已经签发的 JWT 怎么立刻失效?

如果不做额外处理,只删除前端 token,服务器并不知道这件事。

常见方案有:

  • access token 设置短有效期。
  • 服务端保存黑名单,把已退出的 token 加进去。
  • 使用 token version,用户退出或改密码后递增版本号。
  • refresh token 存服务端,退出时删除 refresh token。

如果系统要求“退出后立即完全失效”,就不能只依赖无状态 JWT。

我最后想通的地方

JWT 认证的核心流程其实很清楚:

text
登录时签发身份声明
请求时带上身份声明
后端验证签名、过期时间和业务权限

它的优势是服务端不一定要为每个 access token 保存会话数据,适合分布式接口、移动端、前后端分离场景。

但它不是万能登录方案。

真正设计认证系统时,还要继续考虑:

  • token 放在哪里。
  • token 有效期多长。
  • 权限变化后怎么生效。
  • 退出登录怎么处理。
  • XSS 和 CSRF 风险怎么控制。
  • 高风险操作是否需要重新校验。

所以 JWT 不是“登录的全部”,它只是认证链路里用来表达身份声明的一种格式和机制。