You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 02:32:52