Bläddra i källkod

实现单点登录功能,根据最后一次登录时间,之前的提示失效

wilhelm wong 2 månader sedan
förälder
incheckning
a9a8a5a27e

+ 25 - 1
src/controllers/user.controller.ts

@@ -1,5 +1,6 @@
 import { FastifyRequest, FastifyReply, FastifyInstance } from 'fastify'
 import { UserService } from '../services/user.service'
+import { MemberTokenManagerService } from '../services/member-token-manager.service'
 import {
   ListUserQuery,
   LoginBody,
@@ -12,9 +13,11 @@ import { UserRole } from '../entities/user.entity'
 
 export class UserController {
   private userService: UserService
+  private tokenManagerService: MemberTokenManagerService
 
   constructor(app: FastifyInstance) {
     this.userService = new UserService(app)
+    this.tokenManagerService = new MemberTokenManagerService(app)
   }
 
   async register(request: FastifyRequest<{ Body: RegisterBody }>, reply: FastifyReply) {
@@ -61,6 +64,9 @@ export class UserController {
         return reply.code(401).send({ message: '用户名或密码错误' })
       }
 
+      // 单点登录:使该用户的所有其他token失效
+      await this.tokenManagerService.invalidateUserTokens(user.id)
+
       const token = await reply.jwtSign({ id: user.id, name: user.name, role: user.role })
 
       return reply.send({
@@ -68,7 +74,8 @@ export class UserController {
           id: user.id,
           name: user.name
         },
-        token
+        token,
+        message: '登录成功,其他设备已自动下线'
       })
     } catch (error) {
       return reply.code(500).send({ message: '登录失败' })
@@ -199,4 +206,21 @@ export class UserController {
       return reply.code(500).send({ message: '获取子用户失败' })
     }
   }
+
+  /**
+   * 用户登出
+   * 使当前token失效
+   */
+  async logout(request: FastifyRequest, reply: FastifyReply) {
+    try {
+      // 使当前用户的所有token失效(包括当前token)
+      await this.tokenManagerService.invalidateUserTokens(request.user.id)
+      
+      return reply.send({ 
+        message: '登出成功,所有设备已下线' 
+      })
+    } catch (error) {
+      return reply.code(500).send({ message: '登出失败' })
+    }
+  }
 }

+ 16 - 1
src/middlewares/auth.middleware.ts

@@ -1,9 +1,24 @@
 import { FastifyRequest, FastifyReply } from 'fastify'
 import { UserRole } from '../entities/user.entity'
+import { MemberTokenManagerService } from '../services/member-token-manager.service'
 
 export async function authenticate(request: FastifyRequest, reply: FastifyReply) {
   try {
     await request.jwtVerify()
+    
+    // 单点登录验证:检查token是否仍然有效
+    const tokenManager = new MemberTokenManagerService(request.server)
+    const tokenIssuedAt = (request.user as any).iat // JWT的签发时间
+    
+    if (tokenIssuedAt) {
+      const isValid = await tokenManager.isTokenValid(request.user.id, tokenIssuedAt)
+      
+      if (!isValid) {
+        return reply.code(401).send({
+          message: 'Token已失效,请重新登录'
+        })
+      }
+    }
   } catch (err) {
     reply.code(401).send({
       message: 'Unauthorized'
@@ -35,7 +50,7 @@ export function hasAnyRole(...roles: UserRole[]): (request: FastifyRequest, repl
     try {
       await request.jwtVerify()
 
-      if (!roles.includes(request.user.role)) {
+      if (!roles.includes(request.user.role as UserRole)) {
         return reply.code(403).send({
           message: 'Access denied. Insufficient Permissions.'
         })

+ 1 - 0
src/routes/user.routes.ts

@@ -8,6 +8,7 @@ export default async function userRoutes(fastify: FastifyInstance) {
 
   fastify.post('/register', userController.register.bind(userController))
   fastify.post('/login', userController.login.bind(userController))
+  fastify.post('/logout', { onRequest: [authenticate] }, userController.logout.bind(userController))
   fastify.get('/profile', { onRequest: [authenticate] }, userController.profile.bind(userController))
   fastify.post<{ Body: ResetPasswordBody }>(
     '/reset-password',

+ 6 - 0
src/types/fastify.d.ts

@@ -40,5 +40,11 @@ declare module 'fastify' {
       mimetype: string
       toBuffer(): Promise<Buffer>
     } | null>
+    user?: {
+      id: number
+      name: string
+      role: string
+      iat?: number
+    }
   }
 }

+ 2 - 1
src/types/jwt.d.ts

@@ -1,8 +1,9 @@
 import 'fastify'
+import { UserRole } from '../entities/user.entity'
 
 declare module '@fastify/jwt' {
   interface FastifyJWT {
     payload: { id: number; name: string; role: string | UserRole }
-    user: { id: number; name: string; role: string | UserRole }
+    user: { id: number; name: string; role: string | UserRole; iat?: number }
   }
 }