一、数据结构与家族管理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`与原因,便于追踪与告警。

发表评论 取消回复