公司动态
用户权限校验模块设计:Cookie/Session、Redis 与 JWT
目录一、Cookie/Session 方案服务端有状态工作流程核心特点二、Redis 在权限模块中的角色1. 集中式 Session 存储2. Token 黑名单 / 吊销列表3. 权限缓存三、JWT 方案无状态令牌3.1 JWT 是什么3.2 三部分结构详解3.3 完整认证流程3.4 有效期是怎么做到的3.5 JWT 的存储位置方案 AHttpOnly Cookie方案 BJS 内存变量实际选择3.6 签名算法对比3.7 安全注意事项四、方案对比五、校验失败时后端如何处理中间件 / 拦截器的典型实现总结用户权限校验的核心目标是两件事认证Authentication——识别用户是谁以及授权Authorization——判断用户能做什么。本文梳理三种主流方案的设计思路。一、Cookie/Session 方案服务端有状态工作流程浏览器 服务器 │ │ │ POST /login{username, password}│ │ ──────────────────────────────────────│ 验证密码生成 session_id │ │ 存入服务端内存或 Redis │ Set-Cookie:session_idabc123 │ │────────────────────────────────────── │ │ │ │ GET /api/user(Cookie:session_idabc)│ │ ──────────────────────────────────────│ 查 session 找到用户信息 │ │ 校验权限返回数据 │────────────────────────────────────── │核心特点状态在服务端session 数据存在服务器内存或 Redis 中Cookie 里只放一个无意义的 session_id。设置HttpOnly、Secure、SameSite标记防止 JS 读取和跨站攻击。二、Redis 在权限模块中的角色Redis 不是独立的认证方案而是Session 方案的存储层或JWT 方案的补充层。1. 集中式 Session 存储多实例部署时所有服务器共享同一个 Redis 中的 session 数据。用户被负载均衡路由到任意一台机器都能正常读取登录态不会掉线。Nginx 负载均衡 │ ├── 服务器 A ──┐ ├── 服务器 B ──┼── 都去 Redis 查 session └── 服务器 C ──┘2. Token 黑名单 / 吊销列表JWT 签发后本身无法主动失效。当用户退出登录或账号被管理员封禁时将 token 的唯一标识jti存入 Redis 黑名单并设置与 token 剩余有效期一致的 TTL。每次校验 JWT 签名的同时额外查一次 Redis 黑名单即可。jti是jwt token 用户负载信息的一个字段用于唯一标识一个token。jti的作用就是区分同一用户的不同 token。用户有三台设备都登录了现在他在设备 A 上点了退出登录你只想让设备 A 的 token 失效设备 B、C 继续正常使用。这时把设备 A 那枚 token 的 jti 扔进 Redis 黑名单{userId:10086,role:admin,jti:a1b2c3d4,exp:1693152000}3. 权限缓存将用户角色对应的权限列表缓存到 Redis避免每次请求都查数据库Redis Key: user:permissions:10086 Value: [read:article, write:article, delete:comment] TTL: 5 分钟三、JWT 方案无状态令牌3.1 JWT 是什么JWTJSON Web Token是一个自包含的、经过签名的 JSON 字符串格式为Header.Payload.Signature实际看起来像这样三个 Base64URL 编码的字符串用.连接eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjEwMDg2LCJyb2xlIjoiYWRtaW4ifQ.4xLGW1c_P3fB0LgrYQ7qZ6AZ9XQ8kR2dWvF5mN3jK1Y3.2 三部分结构详解Header头部——描述签名算法和 token 类型{alg:HS256,typ:JWT}Payload载荷——存放声明Claims分为三类{iss:my-app,// 签发者sub:10086,// 主题通常是用户 IDexp:1693152000,// 过期时间Unix 时间戳iat:1693065600,// 签发时间userId:10086,// 自定义声明role:admin}关键原则Payload 只是 Base64 编码不是加密。任何人拿到 token 都能解码看到里面的内容绝不能存放密码、手机号等敏感信息。Signature签名——防篡改的核心签名 HMAC-SHA256( base64(header) . base64(payload), secret )服务端验证时用相同的算法和密钥重新计算签名与 token 中的签名比对。如果对不上说明 token 被篡改过比如有人把role从user改成admin直接拒绝。3.3 完整认证流程客户端 服务端 │ │ │ POST /login { username, password } │ │ ────────────────────────────────────── │ 验证密码生成 JWT │ │ 含 userId, role, exp │ { token: eyJhbG... } │ │ ────────────────────────────────────── │ │ │ │ GET /api/orders │ │ Authorization: Bearer eyJhbG... │ │ ────────────────────────────────────── │ │ │ 1. 验证签名是否被篡改 │ │ 2. 检查 exp 是否过期 │ │ 3. 从 payload 取出 userId, role │ │ 4. 判断 role 是否有权限 │ │ 5. 返回数据 │ ────────────────────────────────────── │3.4 有效期是怎么做到的关键在于 Payload 里的expexpiration字段它是一个 Unix 时间戳表示这个 token 在此之前有效。服务端验证时做的事情收到 token用 secret 或公钥验证签名→签名不对则拒绝被篡改过取出 payload 中的exp与当前时间比较→当前时间大于exp则拒绝已过期全部通过则放行过期不靠服务端记录什么时候签发的而是靠 token 自身携带的过期时间戳每次请求时做一次时间比较。即使攻击者把exp改大签名也会对不上第一步就会被拦截。这就是 JWT 无状态的核心所有判断信息都在 token 自身里服务端拿到就能验证无需查任何存储。3.5 JWT 的存储位置方案 AHttpOnly CookieSet-Cookie: tokeneyJhbG...; HttpOnly; Secure; SameSiteStrict优点JS 无法读取XSS 攻击偷不到 token缺点需额外防护 CSRF移动端/原生 App 不友好方案 BJS 内存变量letaccessTokennull;functionlogin(){accessTokenresponse.data.token;// 只存在内存不持久化}axios.defaults.headers.common[Authorization]Bearer${accessToken};优点天然防 CSRF页面关闭 token 即销毁缺点刷新页面 token 丢失需要配合 refresh token存 HttpOnly Cookie来无感恢复实际选择场景推荐存储方式传统 Web 应用服务端渲染HttpOnly CookieSPA 单页应用内存变量 refresh token 补充移动端 / App安全存储Keychain / Keystore3.6 签名算法对比算法类型密钥适用场景HS256对称同一个 secret单服务简单场景RS256非对称私钥签发 公钥验签微服务架构ES256非对称ECDSA同 RS256比 RS256 更短更快移动端友好微服务场景下推荐 RS256 或 ES256认证服务持有私钥签发 JWT其他服务订单、支付等只需持有公钥即可独立验证不需要共享同一个 secret安全性更好。3.7 安全注意事项不要往 Payload 存敏感数据——Payload 只是 Base64 编码任何人都能解码exp 必须设置——没有过期时间的 token 是安全隐患强制 HTTPS——明文传输 token 等于拱手送人secret 要足够强——HS256 密钥至少 256 位定期轮换控制 token 体积——不要往 Payload 塞太多东西每次 HTTP 请求都会带上它签名算法推荐用非对称的——微服务场景下各服务只需公钥降低密钥泄露面四、方案对比维度Cookie/SessionSession RedisJWT状态有状态服务端有状态Redis无状态扩展性差需 sticky session好共享 Redis好天然无状态认证性能查内存查 Redis本地验签最快主动失效天然支持支持需额外机制黑名单移动端不友好一般友好复杂度低中中高三种方案不是互斥的。实际项目中常见的组合是JWT无状态认证 Redis黑名单 / 权限缓存各取所长。五、校验失败时后端如何处理场景状态码含义响应体示例没带 token未登录401Unauthorized“不知道你是谁”{ code: 401, msg: 请先登录 }token 过期401Unauthorized“凭证已失效”{ code: 401, msg: 登录已过期请重新登录 }token 签名无效401Unauthorized“token 非法”{ code: 401, msg: token 无效 }已登录但权限不够403Forbidden“知道你是谁但不能做”{ code: 403, msg: 权限不足 }401 vs 403 的核心区分401你是谁我不知道先去登录403我知道你是谁但你没有权限做这件事中间件 / 拦截器的典型实现defauth_middleware(request):tokenrequest.headers.get(Authorization)ifnottoken:return401,请先登录try:payloadjwt.decode(token,PUBLIC_KEY,algorithms[RS256])exceptExpiredSignatureError:return401,登录已过期exceptInvalidTokenError:return401,token 无效request.user_idpayload[userId]request.rolepayload[role]defpermission_middleware(request,required_permission):ifrequired_permissionnotinget_role_permissions(request.role):return403,权限不足前端收到 401 时通常跳转到登录页或尝试刷新 token收到 403 时提示无权操作但不跳转登录页。总结Cookie/Session适合传统 Web 应用简单可控但扩展性差JWT适合分布式系统和移动端无状态、高性能但要处理好过期和吊销问题Redis无论哪种方案都可以作为补充解决 session 共享、token 黑名单、权限缓存等问题401 vs 403是认证和授权的分界线不要混用