如何在无Devise的Rails API中实现带Refresh Token的JWT认证?
背景
我正在现有Rails API应用里实现Refresh Token路由,用的是jwt gem做令牌编解码,不想用Devise,已经自定义了登录、登出路由,核心操作就是JWT.encode和JWT.decode。
我对JWT的理解(如有错误请指正)
通过JWT.encode生成的令牌会发给前端(ReactJS),由客户端存储,每次请求以Bearer Token的形式发送。令牌的过期时间可以作为参数放进JWT.encode的payload里,示例代码如下:
def encode(payload, key, algorithm = 'HS256', header_fields = {}) Encode.new(payload: payload, key: key, algorithm: algorithm, headers: header_fields).segments end
JWT整体由三部分组成:
- Header:包含令牌的相关信息,比如签名算法
- Payload:存储令牌携带的数据(一般是用户uuid和角色)
- Signature:通过
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret_key)生成
这意味着编码后的header和payload字符串会和secret_key结合生成HASH,只有令牌数据没被篡改且密钥正确时,哈希值才会一致。如果payload被篡改或者没有正确密钥,签名就会失效,验证失败。当然,要是令牌被盗,别人就能冒充用户,这个问题没法避免。
我的疑问与思路
如果上面的理解是对的,想请教怎么实现Refresh Token路由?流程是:当API请求验证失败(返回403状态码),前端的回调函数会调用Refresh Token路由获取新令牌,再重新发起之前失败的请求。
问题1:后端逻辑如何组织?
Refresh Token需要在生成Access Token后一起返回给前端,所以得在数据库里存储相关数据,用来验证Refresh Token的过期时间、关联的Access Token等。
我之前用Laravel,它的Passport包有个refresh_tokens表,字段包括:
- id
- access_token_id:但
jwtgem不存储数据,也没有Access Token表,这里是不是应该存Access Token本身? - revoked(布尔型):标记令牌是否作废
- expiry_at:Refresh Token的过期时间,到期后用户得重新登录
- refresh_token:我觉得要加这个字段来关联Access Token(Laravel Passport里没有这个字段)
- timestamps
请问在当前Rails的数据库里创建这个结构合理且安全吗?
问题2:Refresh Token怎么生成?
它的有效期要比Access Token长,但不能拥有和Access Token一样的权限。
我自己想了个思路:用JWT.encode生成,payload里加额外的标识数据:
access_token = JWT.encode({user_uuid: user.uuid, role: user.role, type: 'access', exp: 1.hour.to_i}) refresh_token = JWT.encode({user_uuid: user.uuid, role: user.role, type: 'refresh', exp: 3.days.to_i}) # 然后在API中间件验证时,还要检查用户发送的是access还是refresh token
请问这个思路可行吗?有没有其他建议?
内容的提问来源于stack exchange,提问作者spooky

