前言
在 Nest 中,除了创建一个模块必备的 Modules、Controllers、Providers 概念外,还有很多重要的概念,比如中间件(Middleware)、异常过滤器(Exception filters)、管道(Pipes)、守卫(Guards)、拦截器(Interceptors)、装饰器(Decorators)等,这些内容都是学习 Nest 时必须掌握的知识点。
其中,我们可以借助守卫(guard)去实现路由权限的控制,用于用户授权、用户鉴权等步骤。接下来,开始探索 Nest 守卫的相关内容。
守卫(guard)是什么
根据官方文档,守卫(guard)是带有 @Injectable() 装饰器、实现了 CanActivate 接口的一个类。
A guard is a class annotated with the
@Injectable()decorator, which implements theCanActivateinterface.
在 Nest 中,我们通常不清楚中间件的 next() 函数之后是执行什么,而 guard 可以访问到ExecutionContext实例,因此我们可以清楚接下来执行的是什么代码。需要注意的是,guard 在所有中间件之后执行,且在 拦截器(interceptor)或 管道(pipe)之前执行。
授权守卫
举个例子,我们想实现一个授权守卫(Authorization guard)。代码如下:
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
return validateRequest(request);
}
}
function validateRequest(request): boolean {
return true
}
通过 context.switchToHttp().getRequest()获取 request 对象,并传给 validateRequest 函数进行授权校验,比如判断 headers 中的 token 等等。需要注意的是,如果 validateRequest 函数的返回值是 true,则响应会继续下去,返回Hello World!;如果是 false,则响应拒绝,得到状态码 403。
{
"message": "Forbidden resource",
"error": "Forbidden",
"statusCode": 403
}
绑定守卫
像管道(pipes)和异常过滤器(exception filters),守卫可以是控制器范围的、函数范围的或者全局范围的。要创建一个控制器范围的守卫,需要使用 @nestjs/common 包的@UseGuards()装饰器:
@Controller('cats')
@UseGuards(RolesGuard)
export class CatsController {}
对于单一函数范围的守卫,也可以应用@UseGuards()装饰器。
对于全局范围的守卫,使用 Nest 应用实例的useGlobalGuards()函数:
const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new RolesGuard());
路由权限的设置
我们可以借助执行上下文实现角色的路由权限的设置,比如某些路由只对 admin 用户开放,而某些路由对所有用户都开放。这需要我们使用Reflector#createDecorator静态方法创建的装饰器,或者 Nest 内置的@SetMetadata() 装饰器,将自定义的 metadata 添加到路由函数上。
举个例子,使用Reflector#createDecorator静态方法创建了一个@Roles() 装饰器,该装饰器只有一个string[]类型的参数。
// roles.decorator.ts
import { Reflector } from '@nestjs/core';
export const Roles = Reflector.createDecorator<string[]>();
使用这个装饰器:
// cats.controller.ts
@Post()
@Roles(['admin'])
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
另一种方法是使用 Nest 内置的@SetMetadata() 装饰器:
// cats.controller.ts
@Post()
@SetMetadata('roles', ['admin'])
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
当我们设置好路由及其权限后,当访问用户的权限不足时,会得到响应如下:
{
"statusCode": 403,
"message": "Forbidden resource",
"error": "Forbidden"
}
当守卫返回 false 时,Nest 会返回 403 Forbidden。如果我们想自定义错误响应信息,可以主动抛出异常:
throw new UnauthorizedException();