Refresh Token授权流程是什么?Express项目JWT鉴权实操咨询
Express 项目 JWT 双Token(Access + Refresh)授权全流程
双Token机制的核心逻辑是拆分令牌职责:短期的Access Token负责接口访问鉴权,泄露后风险窗口极短;长期的Refresh Token仅负责续签新的Access Token,支持服务端吊销,兼顾使用体验和安全。以下是可直接落地的完整执行步骤:
1. 登录环节:双Token生成与存储
- 前端提交账号密码后,后端首先查询数据库匹配用户记录,用
bcrypt比对提交密码和库中存储的密码哈希,校验不通过直接返回401状态码。 - 账号密码校验通过后,分别生成两个独立签名的JWT:
- Access Token:有效期设为15~30分钟,payload仅存放必要的非敏感字段(用户ID、角色权限),使用独立的
ACCESS_TOKEN_SECRET密钥签名:const accessToken = jwt.sign( { userId: user._id, role: user.role }, process.env.ACCESS_TOKEN_SECRET, { expiresIn: '15m' } ); - Refresh Token:有效期设为7~30天,payload仅保留用户ID即可,使用和Access Token完全独立的
REFRESH_TOKEN_SECRET密钥签名:const refreshToken = jwt.sign( { userId: user._id }, process.env.REFRESH_TOKEN_SECRET, { expiresIn: '7d' } );
- Access Token:有效期设为15~30分钟,payload仅存放必要的非敏感字段(用户ID、角色权限),使用独立的
- 绝对不要把Refresh Token明文存入数据库:和密码存储逻辑一致,将生成的Refresh Token用
bcrypt计算哈希后,关联用户ID、过期时间、设备标识(可选,用于多设备管理)存入用户表的refreshTokens数组字段,避免数据库拖库导致所有令牌泄露。 - 令牌返回规则:Access Token直接放在响应体返回给前端,前端存在运行时内存中(比如Vue/React的全局状态,不要存localStorage降低XSS被盗风险);Refresh Token通过
httpOnly、secure、sameSite: 'Strict'属性的Cookie下发,前端JS无法读取,自动随同域请求携带,大幅降低泄露概率。
2. 普通受保护接口鉴权逻辑
- 前端每次调用受保护接口,将Access Token放在请求头
Authorization: Bearer <accessToken>字段中携带。 - 后端编写统一鉴权中间件,校验请求携带的Access Token:
const authRequired = (req, res, next) => { const authHeader = req.headers.authorization; if (!authHeader?.startsWith('Bearer ')) return res.sendStatus(401); const accessToken = authHeader.split(' ')[1]; jwt.verify(accessToken, process.env.ACCESS_TOKEN_SECRET, (err, payload) => { if (err) return res.sendStatus(403); // Token无效、过期直接返回权限错误 req.user = { userId: payload.userId, role: payload.role }; next(); }); }; - 校验通过后将用户信息挂载到
req对象上放行接口,校验失败直接返回403状态码。
3. Access Token过期后的静默续签流程
这是双Token配合的核心环节,全程用户无感知:
- 前端封装统一的请求响应拦截器,只要收到接口返回的403状态码,不直接跳转登录页,先静默调用
/api/auth/refresh续签接口。注意这个接口不要加上面的Access Token鉴权中间件。 - 后端续签接口执行以下校验逻辑:
- 从请求Cookie中读取Refresh Token原文,先用
REFRESH_TOKEN_SECRET校验签名合法性,签名不合法直接返回401要求重新登录。 - 签名校验通过后,从payload中取出用户ID,查询数据库中该用户关联的所有Refresh Token哈希记录。
- 用
bcrypt将请求携带的Refresh Token原文和库中存储的哈希逐一比对,找不到匹配记录、或匹配记录已过有效期,直接返回401(这种情况大概率是令牌被盗用,可额外加异常日志告警)。 - 所有校验通过后,生成新的15分钟有效期Access Token返回给前端。
- 可选安全增强:每次续签时同步生成新的Refresh Token,删除库中旧的Refresh Token哈希记录,新Token重新通过httpOnly Cookie下发,实现滚动续期,进一步压缩令牌泄露后的可用窗口。
- 从请求Cookie中读取Refresh Token原文,先用
- 前端拿到新的Access Token后,更新内存中存储的令牌值,自动重发之前失败的请求,用户完全感知不到续签过程。
- 如果续签接口返回401,说明Refresh Token已过期或无效,此时再清除本地登录状态,跳转至登录页要求用户重新登录。
4. 特殊场景处理
- 用户主动登出:后端接收到登出请求后,从Cookie中读取Refresh Token,校验通过后将对应的哈希记录从用户的
refreshTokens数组中删除,同时清除前端的Refresh Cookie,令牌即刻失效。 - 强制下线/修改密码:直接清空该用户在数据库中存储的所有Refresh Token哈希记录,所有设备的登录状态会在下次Access Token过期后触发续签时失效,要求重新登录。
- 基础安全红线:
- 两个JWT签名密钥必须用足够长度的随机字符串,存放在环境变量中,绝对不要硬编码提交到代码仓库
- 绝对不要在JWT payload中存放密码、手机号等敏感信息:JWT payload仅做base64编码,没有加密,任何人拿到令牌都可以直接解码读取内容
- 生产环境必须启用HTTPS,Refresh Token对应的Cookie必须开启
secure属性,防止传输过程被截获
内容的提问来源于stack exchange,提问作者Ben Böckmann
相关产品推荐
相关产品推荐

