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

这篇笔记记录一下我对 JWT 认证方式的理解。
一开始我知道 JWT 经常用来做登录,但脑子里其实是糊的:
用户登录以后,后端返回一个 token。
前端以后每次请求带上 token。这句话当然没错,但它没有解释清楚几个问题:
- token 里面到底有什么?
- 后端为什么相信这个 token?
- token 被改了怎么办?
- 不同权限怎么管理?
- 退出登录和过期又怎么处理?
后来我发现,JWT 的核心不是“把用户信息随便塞到前端”,而是:
后端签发一份可以校验真伪的身份声明。JWT 是什么
JWT 全称是 JSON Web Token。
它通常长这样:
xxxxx.yyyyy.zzzzz中间用两个点分成三段:
Header.Payload.Signature第一段 Header 说明签名算法和 token 类型。
第二段 Payload 放声明信息,比如用户 ID、角色、过期时间。
第三段 Signature 是签名,用来证明前两段没有被篡改。
一个典型登录流程
可以把 JWT 登录流程拆成几步:
用户提交账号密码
-> 后端校验账号密码
-> 校验通过后生成 JWT
-> 前端保存 JWT
-> 后续请求带上 JWT
-> 后端验证 JWT
-> 验证通过后处理请求用接口形式看,大概是这样:
POST /login
Content-Type: application/json
{
"username": "xiaoming",
"password": "123456"
}后端校验成功后返回:
{
"accessToken": "xxxxx.yyyyy.zzzzz"
}前端之后请求用户信息:
GET /profile
Authorization: Bearer xxxxx.yyyyy.zzzzz后端从 Authorization 里取出 token,验证它是否合法。
后端为什么相信 JWT
关键在签名。
假设 Payload 是:
{
"sub": "10001",
"role": "admin",
"exp": 1781000000
}如果用户自己把 role 改成 admin,Payload 内容确实可能被改。
但改完以后,原来的签名就对不上了。
后端验证时会用自己的密钥重新计算签名:
拿 Header 和 Payload
-> 用服务端密钥重新计算签名
-> 和 JWT 第三段对比只要密钥没有泄漏,攻击者就不能伪造一个合法签名。
所以 JWT 不是靠“前端不会改”来保证安全,而是靠签名校验。
Payload 里应该放什么
JWT 的 Payload 适合放少量身份声明。
常见字段有:
| 字段 | 含义 |
|---|---|
sub | 用户 ID |
iat | 签发时间 |
exp | 过期时间 |
iss | 签发方 |
aud | 接收方 |
role | 用户角色 |
permissions | 权限列表 |
不要把敏感信息放进去。
JWT 的 Payload 只是 Base64URL 编码,不是加密。
也就是说,别人拿到 token 后,虽然不能随便改,但可以解码看到里面的内容。
所以不要放:
- 密码。
- 身份证号。
- 手机验证码。
- 私密配置。
- 不希望前端或中间人看到的信息。
前端怎么保存 JWT
常见有几种方式。
放在内存里
比如 React 状态或普通变量。
优点是刷新页面后就没了,暴露时间短。
缺点是用户一刷新就要重新登录,或者需要配合刷新 token。
放在 localStorage
localStorage.setItem("accessToken", token);优点是实现简单,刷新后还在。
缺点是 JavaScript 可以读取。如果出现 XSS,token 容易被偷。
放在 HttpOnly Cookie
由后端设置:
Set-Cookie: access_token=...; HttpOnly; Secure; SameSite=Lax优点是前端脚本读不到。
缺点是要额外考虑 CSRF、防跨域 Cookie、接口调用方式等问题。
我现在不会简单说哪种永远最好,而是看系统安全要求、前后端部署方式和团队能力。
权限怎么管理
JWT 只能说明“这个用户是谁”以及“签发时声明了什么”。
真正的权限管理还需要业务规则。
常见做法有三层。
第一层:认证
认证解决的是:
你是谁?后端验证 JWT,拿到用户 ID。
如果 token 无效或过期,直接返回:
401 Unauthorized第二层:角色
角色解决的是粗粒度身份:
你是普通用户、管理员,还是超级管理员?例如:
{
"sub": "10001",
"role": "admin"
}后端在访问管理接口时检查:
只有 role = admin 才能访问不满足时返回:
403 Forbidden第三层:权限点
权限点更细。
例如:
{
"permissions": [
"user:read",
"user:update",
"order:refund"
]
}接口可以要求:
访问退款接口必须拥有 order:refund这种方式比单纯角色更灵活。
权限信息要不要直接放进 JWT
可以放,但要想清楚更新问题。
如果用户登录时签发了一个 token:
{
"role": "admin"
}后来管理员把他的权限降级了,但旧 token 还没过期。
如果后端只相信 JWT 里的角色,那么在 token 过期前,他可能仍然拥有旧权限。
所以常见策略有几种:
- JWT 只放用户 ID,权限每次从数据库或缓存查。
- JWT 放角色和权限,但 access token 有效期设置很短。
- 维护 token 版本号,用户权限变化后让旧 token 失效。
- 高风险操作实时查权限,普通操作使用 token 中的声明。
没有绝对答案。权限变化频繁、风险高的系统,就不能只靠长期 JWT 里的旧信息。
Access Token 和 Refresh Token
很多系统会拆成两种 token。
Access Token 用来访问接口,有效期短。
Refresh Token 用来换新的 Access Token,有效期长。
流程大概是:
登录成功
-> 返回 access token 和 refresh token
-> 前端用 access token 请求接口
-> access token 过期
-> 用 refresh token 换新的 access token这样可以减少长期暴露 access token 的风险。
但 refresh token 权限更敏感,保存和失效策略要更严格。
退出登录怎么处理
JWT 的一个特点是自包含。
后端只要能验证签名和过期时间,就可以认为它有效。
这带来一个问题:
用户退出登录以后,已经签发的 JWT 怎么立刻失效?如果不做额外处理,只删除前端 token,服务器并不知道这件事。
常见方案有:
- access token 设置短有效期。
- 服务端保存黑名单,把已退出的 token 加进去。
- 使用 token version,用户退出或改密码后递增版本号。
- refresh token 存服务端,退出时删除 refresh token。
如果系统要求“退出后立即完全失效”,就不能只依赖无状态 JWT。
我最后想通的地方
JWT 认证的核心流程其实很清楚:
登录时签发身份声明
请求时带上身份声明
后端验证签名、过期时间和业务权限它的优势是服务端不一定要为每个 access token 保存会话数据,适合分布式接口、移动端、前后端分离场景。
但它不是万能登录方案。
真正设计认证系统时,还要继续考虑:
- token 放在哪里。
- token 有效期多长。
- 权限变化后怎么生效。
- 退出登录怎么处理。
- XSS 和 CSRF 风险怎么控制。
- 高风险操作是否需要重新校验。
所以 JWT 不是“登录的全部”,它只是认证链路里用来表达身份声明的一种格式和机制。