一、数据结构与家族管理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 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部