/** * JWT工具类 * 用于生成和验证前端专用的JWT Token * * 主要功能: * - 生成包含用户信息的JWT * - 验证JWT的有效性 * - 解析JWT获取用户信息 */ import jwt from 'jsonwebtoken'; const { sign, verify, decode } = jwt; // JWT密钥 - 从环境变量读取,如果未设置则抛出错误 const JWT_SECRET: string = (() => { const secret = process.env.JWT_SECRET; if (!secret) { throw new Error('JWT_SECRET environment variable is not set. Please add it to your .env file.'); } return secret; })(); // JWT配置 const JWT_CONFIG = { algorithm: 'HS256' as const, issuer: 'docreview-system', audience: 'docreview-frontend' }; // JWT载荷接口 export interface JWTPayload { // 标准字段 sub: string; // 用户唯一标识(来自IDaaS) iss: string; // 发行者 aud: string; // 受众 iat: number; // 签发时间 exp: number; // 过期时间 // 自定义用户信息字段 user_id: string; // 数据库中的用户ID username: string; // 用户名 nick_name: string; // 用户昵称 email?: string; // 邮箱 phone_number?: string; // 手机号 ou_id: string; // 组织单位ID ou_name: string; // 组织单位名称 is_leader: boolean; // 是否为负责人 user_role: string; // 用户角色 } // 用户信息接口(用于生成JWT) export interface UserInfoForJWT { sub: string; user_id: string; username: string; nick_name: string; email?: string; phone_number?: string; ou_id: string; ou_name: string; is_leader: boolean; user_role: string; } /** * JWT工具类 */ export class JWTUtils { /** * 生成JWT * @param userInfo 用户信息 * @param expiresIn 过期时间(秒),默认为OAuth token过期时间的90% * @returns JWT字符串 */ static generateJWT(userInfo: UserInfoForJWT, expiresIn: number): string { const now = Math.floor(Date.now() / 1000); // 将过期时间设置为OAuth token过期时间的90%,确保JWT在OAuth token之前过期 const jwtExpiresIn = Math.floor(expiresIn); const payload: JWTPayload = { // 标准字段 sub: userInfo.sub, iss: JWT_CONFIG.issuer, aud: JWT_CONFIG.audience, iat: now, exp: now + jwtExpiresIn, // 用户信息字段 user_id: userInfo.user_id, username: userInfo.username, nick_name: userInfo.nick_name, email: userInfo.email, phone_number: userInfo.phone_number, ou_id: userInfo.ou_id, ou_name: userInfo.ou_name, is_leader: userInfo.is_leader, user_role: userInfo.user_role }; return sign(payload, JWT_SECRET, { algorithm: JWT_CONFIG.algorithm }); } /** * 验证JWT * @param token JWT字符串 * @returns 验证结果和载荷 */ static verifyJWT(token: string): { valid: boolean; payload?: JWTPayload; error?: string } { try { const decoded = verify(token, JWT_SECRET, { algorithms: [JWT_CONFIG.algorithm], issuer: JWT_CONFIG.issuer, audience: JWT_CONFIG.audience }); // 验证返回的payload是否包含必需字段 if (typeof decoded === 'object' && decoded !== null && 'sub' in decoded) { const payload = decoded as JWTPayload; return { valid: true, payload }; } return { valid: false, error: 'JWT载荷格式不正确' }; } catch (error) { if (error instanceof Error) { return { valid: false, error: error.message }; } return { valid: false, error: 'JWT验证失败' }; } } /** * 解析JWT(不验证签名) * @param token JWT字符串 * @returns 解析结果 */ static decodeJWT(token: string): { payload?: JWTPayload; error?: string } { try { const payload = decode(token) as JWTPayload; return { payload }; } catch (error) { return { error: 'JWT解析失败' }; } } /** * 检查JWT是否即将过期 * @param token JWT字符串 * @param bufferMinutes 缓冲时间(分钟),默认5分钟 * @returns 是否即将过期 */ static isJWTExpiringSoon(token: string, bufferMinutes: number = 5): boolean { const decoded = this.decodeJWT(token); if (!decoded.payload) { return true; // 解析失败视为过期 } const now = Math.floor(Date.now() / 1000); const bufferSeconds = bufferMinutes * 60; return decoded.payload.exp <= (now + bufferSeconds); } /** * 获取JWT过期时间 * @param token JWT字符串 * @returns 过期时间戳 */ static getJWTExpiration(token: string): number | null { const decoded = this.decodeJWT(token); return decoded.payload?.exp || null; } /** * 从JWT中提取用户信息 * @param token JWT字符串 * @returns 用户信息 */ static extractUserInfo(token: string): UserInfoForJWT | null { const verification = this.verifyJWT(token); if (!verification.valid || !verification.payload) { return null; } const payload = verification.payload; return { sub: payload.sub, user_id: payload.user_id, username: payload.username, nick_name: payload.nick_name, email: payload.email, phone_number: payload.phone_number, ou_id: payload.ou_id, ou_name: payload.ou_name, is_leader: payload.is_leader, user_role: payload.user_role }; } } export default JWTUtils;