当然!我们来攻克安全领域的第二道大门,也是更精细的一道门:授权 (Authorization)。如果你已经理解了认证(Authentication),那么授权就是建立在认证基础之上的“权限管理中心”。
为你的应用划分“权限区域”:NestJS 授权 (Authorization) 完全指南
再次回到我们那个高度机密的科研基地。
通过认证 (Authentication),我们已经确认了门口这位访客的身份——他就是“张三研究员”(request.user
对象里现在有了他的信息)。
现在,授权 (Authorization) 要回答一个更复杂的问题:“好的,既然你是张三研究员,那么你具体能做什么?”
- 他能进入普通的 B 级实验室吗?(权限检查)
- 他有权删除重要的实验数据吗?(操作权限检查)
- 他是否有权查看S 级核心区的机密文件?(角色或资源权限检查)
这位“张三研究员”,就像我们应用中一个已登录的用户。授权,就是根据这个用户的身份、角色或属性,来决定他是否有权访问某个特定的资源或执行某个特定的操作。
在 NestJS 中,实现授权的核心武器就是守卫 (Guards)。我们之前在认证中已经用 AuthGuard
部署了一位“门禁保安”,现在我们要为基地的每个重要房间门口,都配备一位更智能的“区域保安”,他们手里拿着一本详细的“权限清单”。
1. 基础授权:基于角色的访问控制 (RBAC)
这是最经典、最常见的授权模型:Role-Based Access Control (RBAC)。
思路:
- 为每个用户赋予一个或多个角色 (Role)(如
admin
,member
,guest
)。通常这个角色信息会在用户登录认证后,被包含在request.user
对象中。 - 为每个需要保护的路由,声明它需要什么角色才能访问。
- 创建一个
RolesGuard
(区域保安),他的工作就是比较用户拥有的角色和路由要求的角色,然后决定是否放行。
第一步:创建“权限标签” (@Roles
装饰器)
我们需要一种方式来给路由“贴标签”。这就需要用到我们之前学过的自定义装饰器和元数据 (Metadata)。
src/auth/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const ROLES_KEY = 'roles';
// Roles 是一个函数,它返回一个由 SetMetadata 创建的装饰器
// (...roles: string[]) 允许我们传递一个或多个角色字符串
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
现在,我们有了一个 @Roles('admin')
装饰器,可以用来给任何路由处理函数附加 roles: ['admin']
这样的元数据。
第二步:实现“区域保安” (RolesGuard
)
这位保安需要一把能读取“权限标签”的“扫描枪”——Reflector
。
src/auth/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from './roles.decorator';
@Injectable()
export class RolesGuard implements CanActivate {
// 注入 Reflector
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
// 1. 使用 Reflector 读取附加在路由上的元数据 (需要的角色)
// getAllAndOverride 会同时查找方法和类上的元数据,并以方法上的为准
const requiredRoles = this.reflector.getAllAndOverride<string[]>(
ROLES_KEY,
[
context.getHandler(), // 当前的方法
context.getClass(), // 当前的控制器类
]
);
// 2. 如果这个路由没有 @Roles() 标签,说明它不需要特定角色,直接放行
if (!requiredRoles) {
return true;
}
// 3. 获取当前登录的用户信息 (由之前的 AuthGuard 附加)
const { user } = context.switchToHttp().getRequest();
// 4. 核心逻辑:判断用户的角色数组中,是否至少有一个角色是路由所需要的
return requiredRoles.some((role) => user.roles?.includes(role));
}
}
第三步:在控制器中部署保安和张贴标签
现在,我们可以将 AuthGuard
(门禁保安)和 RolesGuard
(区域保安)一起部署。
src/cats/cats.controller.ts
import { Controller, Get, Post, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { RolesGuard } from '../auth/roles.guard';
import { Roles } from '../auth/roles.decorator';
@Controller('cats')
// 在整个控制器上部署保安,确保所有接口都至少需要登录
@UseGuards(AuthGuard('jwt'), RolesGuard)
export class CatsController {
@Get()
findAll() {
// 这个接口没有 @Roles 标签,所以任何登录的用户都可以访问
return 'This action returns all cats for any logged-in user.';
}
@Post()
@Roles('admin') // 贴上“仅限管理员”的标签
create() {
// 只有角色为 'admin' 的用户才能访问这个接口
return 'This action adds a new cat, only for admins.';
}
}
工作流程:
- 一个请求到达
POST /cats
。 AuthGuard
先启动,验证 JWT,确认用户身份并将user
对象(假设user = { id: 1, roles: ['member'] }
)附加到req
上。RolesGuard
接着启动。- 它通过
Reflector
发现create()
方法需要['admin']
角色。 - 它检查
req.user.roles
(['member']
)中是否包含'admin'
。 - 检查结果为
false
。RolesGuard
返回false
,NestJS 自动抛出一个403 Forbidden
错误。
2. 进阶授权:声明式的声明周期 (Claims-Based Authorization)
RBAC 很简单,但有时不够灵活。比如:
- “只有创建了这只猫的用户,才能编辑它。”
- “只有年龄大于 18 岁的用户,才能访问这个内容。”
这些规则与“角色”无关,而与用户的具体声明/属性 (Claim) 或与资源本身的关系有关。
我们可以扩展 RolesGuard
来实现更复杂的逻辑。
src/auth/policies.guard.ts
(一个更通用的守卫)
// policies.handler.ts - 定义策略处理器
export interface PolicyHandler {
handle(ability: CaslAbility, ...args: any[]): boolean;
}
// policies.guard.ts
@Injectable()
export class PoliciesGuard implements CanActivate {
constructor(
private reflector: Reflector,
private caslAbilityFactory: CaslAbilityFactory // 假设我们有一个 CASL 的能力工厂
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const policyHandlers = this.reflector.get<PolicyHandler[]>(/* ... */) || [];
const { user } = context.switchToHttp().getRequest();
// 使用 CASL 来创建一个“能力”对象
const ability = this.caslAbilityFactory.createForUser(user);
return policyHandlers.every((handler) => handler.handle(ability));
}
}
这个例子引入了 CASL (Casl is an authorization library) 这个强大的库,它允许你定义非常精细的权限规则,比如 can('update', 'Article', { authorId: user.id })
(可以更新文章,当文章的作者 ID 是当前用户 ID 时)。
3. 中国企业级的复杂权限系统方案
在大型的、多租户的 SaaS 应用或复杂的后台管理系统(如飞书、钉钉的管理后台)中,权限系统远比简单的 RBAC 要复杂。
挑战:
- 数据权限:同一个角色的用户(如“销售经理”),华东区的经理只能看到华东区的销售数据,华北区的经理只能看到华北区的。
- 动态权限:权限不是静态赋予的,而是通过复杂的规则和组织架构计算得出的。
- 权限组合与继承:一个用户可能同时属于多个用户组,拥有多个角色,其最终权限是这些身份权限的并集。
- 权限可视化管理:需要一个 UI 界面,让非技术人员(如系统管理员)可以方便地配置用户的角色和权限。
企业级方案 Demo:ABAC (Attribute-Based Access Control) + 权限中台
这是一个更现代、更灵活的模型。
- 核心思想: 授权决策不仅仅基于用户的角色 (Role),而是基于一系列属性 (Attributes) 的组合。这些属性可以来自:
- 用户属性 (Subject): 年龄、部门、职位、地理位置...
- 资源属性 (Object): 文件的密级、订单的金额、客户的归属地...
- 操作属性 (Action): 读取、写入、删除、审批...
- 环境属性 (Environment): 当前时间、访问 IP 地址、设备类型...
- 权限中台:
- 统一的权限模型定义:企业会建立一个独立的“权限中心”服务。在这个服务中,管理员可以通过界面来定义资源 (Resource)、操作 (Action),并创建策略 (Policy)。
- 策略定义: 一个典型的策略可能看起来像这样:
- 允许 (Effect: Allow)
- 主体 (Principal):所有职位为“部门经理”的用户
- 操作 (Action):
read
,update
- 资源 (Resource):所有属于“本部门”的“报销单”
- 条件 (Condition):当报销单金额小于 5000 元时
- 决策点 (PDP - Policy Decision Point):权限中心提供一个 API,比如
can(principal, action, resource, environment)
。
- 与 NestJS 的集成:
- 你的 NestJS 应用中会有一个非常通用的
AuthorizationGuard
。 - 这个守卫不再自己实现复杂的 if-else 逻辑。它的唯一职责是:
- 从请求中收集用户、资源、操作、环境这四个维度的属性。
- 调用权限中台的
can()
API,将这些属性发送过去。 - 根据权限中台返回的
true
或false
,来决定是否放行。
- 你的 NestJS 应用中会有一个非常通用的
AuthorizationGuard
伪代码:
@Injectable()
export class CentralAuthGuard implements CanActivate {
constructor(private readonly permissionCenterClient: PermissionCenterClient) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const { user } = request;
// 从请求和元数据中提取所需属性
const action = this.reflector.get('action', context.getHandler());
const resourceType = this.reflector.get('resource', context.getClass());
const resourceId = request.params.id;
// 调用权限中台进行决策
const isAllowed = await this.permissionCenterClient.can({
subject: { id: user.id, department: user.department, title: user.title },
action: action,
object: { type: resourceType, id: resourceId },
environment: { ip: request.ip }
});
return isAllowed;
}
}```
这种模式将**业务逻辑**(在 NestJS 应用中)和**权限决策逻辑**(在权限中台中)完全分离,使得权限系统可以独立演进、统一管理,极大地提高了大型系统的可维护性和安全性。这是构建复杂企业级后台的“黄金标准”。
### 总结
授权是认证的下一步,它决定了“你能做什么”。
* **基础方案 (RBAC)**: 基于**角色**的访问控制。通过**自定义装饰器 (`@Roles`)** 和**守卫 (`RolesGuard`)** 配合 **`Reflector`** 来实现。简单、直观、能满足大部分应用的需求。
* **进阶方案**: 使用 **CASL** 等库实现更精细的、基于**声明 (Claims)** 的授权。
* **企业级方案 (ABAC + 权限中台)**: 将权限决策逻辑外包给一个**独立的权限中心**服务。NestJS 的守卫只负责收集属性并发起决策请求。这是构建大型、复杂、可动态配置权限系统的最佳实践。
从简单的 RBAC 开始,到理解 ABAC 的思想,掌握授权技术,你才能构建出真正安全、可靠、能应对复杂业务规则的应用程序。