ソースを参照

♻️ refactor(auth): 重构JWT认证逻辑,提取为工具类

- 创建JWTUtil工具类,集中管理JWT生成、验证等功能
- 从auth.service.ts和mini-auth.service.ts中移除重复的JWT逻辑
- 使用JWTUtil替代直接调用jsonwebtoken库
- 添加token解码和剩余有效期计算功能
- 统一处理JWT配置,支持环境变量配置密钥和过期时间

🔧 chore(config): 改进JWT配置管理

- 将JWT_SECRET和JWT_EXPIRES_IN移至JWTUtil统一管理
- 添加环境变量支持,增强生产环境安全性
- 移除硬编码的密钥,使用环境变量或默认值作为备选方案
yourname 4 ヶ月 前
コミット
f92fdb333d

+ 3 - 15
src/server/modules/auth/auth.service.ts

@@ -1,7 +1,7 @@
-import jwt from 'jsonwebtoken';
 import { UserService } from '../users/user.service';
 import { UserEntity as User } from '../users/user.entity';
 import { DisabledStatus } from '@/share/types';
+import { JWTUtil } from '@/server/utils/jwt.util';
 import debug from 'debug';
 
 const logger = {
@@ -9,8 +9,6 @@ const logger = {
   error: debug('backend:auth:error')
 }
 
-const JWT_SECRET = 'your-secret-key'; // 生产环境应使用环境变量
-const JWT_EXPIRES_IN = '7d'; // 7天有效期
 const ADMIN_USERNAME = 'admin';
 const ADMIN_PASSWORD = 'admin123';
 
@@ -67,21 +65,11 @@ export class AuthService {
   }
 
   generateToken(user: User): string {
-    const payload = {
-      id: user.id,
-      username: user.username,
-      roles: user.roles?.map(role => role.name) || []
-    };
-    return jwt.sign(payload, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
+    return JWTUtil.generateToken(user);
   }
 
   verifyToken(token: string): any {
-    try {
-      return jwt.verify(token, JWT_SECRET);
-    } catch (error) {
-      console.error('Token verification failed:', error);
-      throw new Error('Invalid token');
-    }
+    return JWTUtil.verifyToken(token);
   }
 
   async logout(token: string): Promise<void> {

+ 2 - 12
src/server/modules/auth/mini-auth.service.ts

@@ -1,7 +1,7 @@
 import { DataSource, Repository } from 'typeorm';
 import { UserEntity } from '@/server/modules/users/user.entity';
 import { FileService } from '@/server/modules/files/file.service';
-import jwt from 'jsonwebtoken';
+import { JWTUtil } from '@/server/utils/jwt.util';
 import axios from 'axios';
 import process from 'node:process'
 
@@ -128,16 +128,6 @@ export class MiniAuthService {
   }
 
   private generateToken(user: UserEntity): string {
-    const payload = {
-      id: user.id,
-      username: user.username,
-      openid: user.openid
-    };
-    
-    if (!process.env.JWT_SECRET) {
-      throw new Error('JWT配置缺失');
-    }
-    
-    return jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: '7d' });
+    return JWTUtil.generateToken(user);
   }
 }

+ 90 - 0
src/server/utils/jwt.util.ts

@@ -0,0 +1,90 @@
+import jwt, { SignOptions } from 'jsonwebtoken';
+import { UserEntity } from '@/server/modules/users/user.entity';
+import debug from 'debug';
+
+const logger = {
+  info: debug('backend:jwt:info'),
+  error: debug('backend:jwt:error')
+};
+
+const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
+const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '7d';
+
+export interface JWTPayload {
+  id: number;
+  username: string;
+  roles?: string[];
+  openid?: string;
+}
+
+export class JWTUtil {
+  /**
+   * 生成 JWT token
+   * @param user 用户实体
+   * @param additionalPayload 额外的 payload 数据
+   * @returns JWT token
+   */
+  static generateToken(user: UserEntity, additionalPayload: Partial<JWTPayload> = {}): string {
+    if (!user.id || !user.username) {
+      throw new Error('用户ID和用户名不能为空');
+    }
+
+    const payload: JWTPayload = {
+      id: user.id,
+      username: user.username,
+      roles: user.roles?.map(role => role.name) || [],
+      openid: user.openid || undefined,
+      ...additionalPayload
+    };
+
+    try {
+      return jwt.sign(payload, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN as SignOptions['expiresIn']});
+    } catch (error) {
+      logger.error('生成JWT token失败:', error);
+      throw new Error('生成token失败');
+    }
+  }
+
+  /**
+   * 验证 JWT token
+   * @param token JWT token
+   * @returns 验证后的 payload
+   */
+  static verifyToken(token: string): JWTPayload {
+    try {
+      return jwt.verify(token, JWT_SECRET) as JWTPayload;
+    } catch (error) {
+      logger.error('验证JWT token失败:', error);
+      throw new Error('无效的token');
+    }
+  }
+
+  /**
+   * 解码 JWT token(不验证签名)
+   * @param token JWT token
+   * @returns 解码后的 payload
+   */
+  static decodeToken(token: string): JWTPayload | null {
+    try {
+      return jwt.decode(token) as JWTPayload;
+    } catch (error) {
+      logger.error('解码JWT token失败:', error);
+      return null;
+    }
+  }
+
+  /**
+   * 获取 token 的剩余有效期(秒)
+   * @param token JWT token
+   * @returns 剩余有效期(秒),如果 token 无效则返回 0
+   */
+  static getTokenRemainingTime(token: string): number {
+    try {
+      const decoded = jwt.verify(token, JWT_SECRET) as JWTPayload & { exp: number };
+      const currentTime = Math.floor(Date.now() / 1000);
+      return Math.max(0, decoded.exp - currentTime);
+    } catch (error) {
+      return 0;
+    }
+  }
+}