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

发表评论 取消回复