添加jwt验证,添加交叉评查首页加载对接接口,评查任务文档列表对接接口,意见列表对接接口
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* 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;
|
||||
Reference in New Issue
Block a user