NestJS服务层基于权限的访问控制优化方案咨询
优化NestJS服务层权限控制的方案
你当前的方案可行,但手动传递userId和权限会导致代码冗余,且不利于维护。以下是几个更优的实现思路:
1. 用请求上下文自动传递用户信息,避免手动传参
NestJS中可以通过AsyncLocalStorage或请求作用域服务在服务层直接获取当前用户的上下文信息,不用每次从控制器手动传参。
示例:用AsyncLocalStorage存储请求上下文
- 先创建上下文存储服务:
// src/common/context/request-context.service.ts import { Injectable, Scope } from '@nestjs/common'; import { AsyncLocalStorage } from 'async_hooks'; export interface RequestContext { userId: string; permissions: string[]; } @Injectable({ scope: Scope.DEFAULT }) export class RequestContextService { private readonly als = new AsyncLocalStorage<RequestContext>(); runWithContext(context: RequestContext, callback: () => void) { return this.als.run(context, callback); } getContext(): RequestContext | undefined { return this.als.getStore(); } }
- 在权限Guard中注入该服务,将用户信息存入上下文:
// src/auth/guards/permission.guard.ts import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { RequestContextService } from '../common/context/request-context.service'; @Injectable() export class PermissionGuard implements CanActivate { constructor(private readonly requestContextService: RequestContextService) {} async canActivate(context: ExecutionContext): Promise<boolean> { const request = context.switchToHttp().getRequest(); // 假设已从request中解析出用户信息(比如JWT验证后) const user = request.user; const hasPermission = // 你的权限校验逻辑 if (hasPermission) { // 将用户信息存入上下文 this.requestContextService.runWithContext({ userId: user.id, permissions: user.permissions }, () => {}); return true; } return false; } }
- 在服务层直接获取上下文并过滤数据:
// src/users/users.service.ts import { Injectable } from '@nestjs/common'; import { RequestContextService } from '../common/context/request-context.service'; import { UserRepository } from './user.repository'; @Injectable() export class UsersService { constructor( private readonly userRepository: UserRepository, private readonly requestContextService: RequestContextService ) {} async findAll() { const context = this.requestContextService.getContext(); if (!context) { throw new Error('请求上下文未初始化'); } const canViewAll = context.permissions.includes('user:read_all'); return canViewAll ? this.userRepository.find() : this.userRepository.find({ where: { id: context.userId } }); } }
2. 将数据过滤逻辑封装为独立服务,解耦业务代码
把权限判断和数据过滤的逻辑抽成单独的服务,让业务服务专注于核心业务,而非权限校验。
示例:用户数据过滤服务
// src/users/services/user-data-filter.service.ts import { Injectable } from '@nestjs/common'; import { RequestContextService } from '../../common/context/request-context.service'; import { UserRepository } from '../user.repository'; @Injectable() export class UserDataFilterService { constructor( private readonly requestContextService: RequestContextService, private readonly userRepository: UserRepository ) {} async findUsers() { const context = this.requestContextService.getContext(); const canViewAll = context.permissions.includes('user:read_all'); return canViewAll ? this.userRepository.find() : this.userRepository.find({ where: { id: context.userId } }); } }
业务服务中直接调用:
// src/users/users.service.ts import { Injectable } from '@nestjs/common'; import { UserDataFilterService } from './services/user-data-filter.service'; @Injectable() export class UsersService { constructor(private readonly userDataFilterService: UserDataFilterService) {} async findAll() { return this.userDataFilterService.findUsers(); } }
3. 在查询构建器层面自动注入过滤条件(TypeORM/Prisma)
如果使用TypeORM或Prisma,可以封装查询扩展,根据用户权限自动添加WHERE条件,彻底避免在服务层写权限判断逻辑。
示例:TypeORM自定义Repository
// src/users/user.repository.ts import { Repository, DataSource } from 'typeorm'; import { Injectable } from '@nestjs/common'; import { User } from './entities/user.entity'; import { RequestContextService } from '../common/context/request-context.service'; @Injectable() export class UserRepository extends Repository<User> { constructor( dataSource: DataSource, private readonly requestContextService: RequestContextService ) { super(User, dataSource.createEntityManager()); } async findWithPermissionFilter() { const context = this.requestContextService.getContext(); const queryBuilder = this.createQueryBuilder('user'); if (!context.permissions.includes('user:read_all')) { queryBuilder.where('user.id = :userId', { userId: context.userId }); } return queryBuilder.getMany(); } }
服务层调用封装好的查询方法:
// src/users/users.service.ts import { Injectable } from '@nestjs/common'; import { UserRepository } from './user.repository'; @Injectable() export class UsersService { constructor(private readonly userRepository: UserRepository) {} async findAll() { return this.userRepository.findWithPermissionFilter(); } }
这些方案的核心是减少重复代码、解耦权限逻辑与业务逻辑,同时让服务层代码更简洁、可维护。
内容的提问来源于stack exchange,提问作者Ilija Vuk
相关产品推荐
相关产品推荐

