---

title: OAuth刷新令牌旋转与撤销检测最佳实践

keywords:

  • OAuth
  • Refresh Token
  • Rotation
  • Revocation
  • jti
  • family
  • grant_type
  • token endpoint
  • scope
  • expires_in

description: 构建刷新令牌旋转与撤销检测方案,支持令牌家族(family)管理、重用检测与全家族吊销、短期访问令牌与精确scope传播,附签发与刷新示例。

categories:

  • 文章资讯
  • 技术教程

---

一、数据结构与家族管理

type Rt = { jti: string; family: string; userId: string; exp: number; revoked?: boolean }

class RtStore {
  byJti = new Map<string, Rt>()
  byFamily = new Map<string, Set<string>>()
  add(rt: Rt) { this.byJti.set(rt.jti, rt); const s = this.byFamily.get(rt.family) || new Set<string>(); s.add(rt.jti); this.byFamily.set(rt.family, s) }
  get(jti: string): Rt | undefined { return this.byJti.get(jti) }
  revoke(jti: string) { const r = this.byJti.get(jti); if (r) r.revoked = true }
  revokeFamily(family: string) { for (const j of this.byFamily.get(family) || []) this.revoke(j) }
}

二、签发与旋转

import crypto from 'crypto'

function nowSec(): number { return Math.floor(Date.now()/1000) }
function rid(): string { return crypto.randomBytes(16).toString('hex') }

type TokenPair = { access_token: string; refresh_token: string; token_type: 'Bearer'; expires_in: number; scope: string[] }

function issuePair(userId: string, scope: string[], store: RtStore): TokenPair {
  const family = rid()
  const jti = rid()
  const rt: Rt = { jti, family, userId, exp: nowSec() + 604800 }
  store.add(rt)
  const access = rid()
  return { access_token: access, refresh_token: `${family}.${jti}`, token_type: 'Bearer', expires_in: 900, scope }
}

function rotateRefresh(old: string, store: RtStore): TokenPair | 'invalid' | 'reused' {
  const [family, jti] = old.split('.')
  const cur = store.get(jti)
  if (!cur || cur.family !== family) return 'invalid'
  if (cur.revoked) { store.revokeFamily(family); return 'reused' }
  store.revoke(jti)
  const nextJti = rid()
  const next: Rt = { jti: nextJti, family, userId: cur.userId, exp: nowSec() + 604800 }
  store.add(next)
  const access = rid()
  return { access_token: access, refresh_token: `${family}.${nextJti}`, token_type: 'Bearer', expires_in: 900, scope: ['basic'] }
}

三、刷新端点与验收

type Req = { body: { grant_type: string; refresh_token?: string } }
type Res = { status: (n: number) => Res; end: (b?: string) => void }

function refreshEndpoint(req: Req, res: Res, store: RtStore) {
  if (req.body.grant_type !== 'refresh_token') return res.status(400).end('unsupported_grant')
  const rt = req.body.refresh_token || ''
  const r = rotateRefresh(rt, store)
  if (r === 'invalid') return res.status(401).end('invalid_refresh')
  if (r === 'reused') return res.status(403).end('refresh_family_revoked')
  return res.end(JSON.stringify(r))
}

四、验收清单

  • 访问令牌expires_in≤900并与scope精确传播;刷新令牌家族与jti管理有效。
  • 刷新后旧令牌立即撤销;检测到重用则吊销整个家族并拒绝。
  • 审计记录包含family/jti/userId与原因,便于追踪与告警。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部