39 lines
1.2 KiB
TypeScript
39 lines
1.2 KiB
TypeScript
import { Injectable } from "@nestjs/common";
|
||
import { getRedis } from "../../config/redis.js";
|
||
|
||
/**
|
||
* Token 黑名单服务(I7 裁决:JWT 黑名单)。
|
||
*
|
||
* 用于 logout / refresh 轮换场景,将未过期的 refresh_token 的 jti 加入黑名单,
|
||
* 阻止其再次被用于刷新 access_token。
|
||
*
|
||
* 缓存策略:
|
||
* - Key: `iam:bl:{jti}` → "1"
|
||
* - TTL: 与 refresh_token 剩余有效期对齐(避免永久驻留)
|
||
*
|
||
* access_token 不走黑名单(短生命周期 15min,自然过期)。
|
||
*/
|
||
@Injectable()
|
||
export class TokenBlacklistService {
|
||
private static buildKey(jti: string): string {
|
||
return `iam:bl:${jti}`;
|
||
}
|
||
|
||
async isBlacklisted(jti: string): Promise<boolean> {
|
||
const redis = getRedis();
|
||
const exists = await redis.exists(TokenBlacklistService.buildKey(jti));
|
||
return exists === 1;
|
||
}
|
||
|
||
/**
|
||
* 将 jti 加入黑名单。
|
||
* @param jti JWT ID
|
||
* @param ttlSeconds 剩余有效期(秒),到期后自动清理
|
||
*/
|
||
async blacklist(jti: string, ttlSeconds: number): Promise<void> {
|
||
if (ttlSeconds <= 0) return; // 已过期,无需加入
|
||
const redis = getRedis();
|
||
await redis.set(TokenBlacklistService.buildKey(jti), "1", "EX", ttlSeconds);
|
||
}
|
||
}
|