Files
leaudit-platform-frontend/app/utils/jwt.ts
T

191 lines
5.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* JWT工具类
* 用于生成和验证前端专用的JWT Token
*
* 主要功能:
* - 生成包含用户信息的JWT
* - 验证JWT的有效性
* - 解析JWT获取用户信息
*/
import jwt from 'jsonwebtoken';
const { sign, verify, decode } = jwt;
// JWT密钥 - 在生产环境中应该从环境变量读取
const JWT_SECRET = 'gdyc-super-secrets-jjwtt-key-change-this-in-production-20250721-from-login-callback';
// 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 payload = verify(token, JWT_SECRET, {
algorithms: [JWT_CONFIG.algorithm],
issuer: JWT_CONFIG.issuer,
audience: JWT_CONFIG.audience
}) as JWTPayload;
return { valid: true, payload };
} 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;