卫士
卫士
警卫是一个用@Injectable()
装饰者注释的类。警卫应该实现CanActivate
界面。
卫兵负有单一责任
。它们确定请求是否应由路由处理程序处理。到目前为止,访问限制逻辑主要位于中间件内部。它仍然很好,因为诸如令牌验证或附加属性的request
事物与特定路线没有很强的联系。
但是,从本质上讲,中间件是愚蠢的。它不知道调用该next()
函数后将执行哪个处理程序。另一方面,Guards
可以访问ExecutionContext
实例,因此确切知道下一步将要执行什么。它们的设计非常类似于异常过滤器,管道和拦截器,使您可以在请求/响应周期中的正确位置插入处理逻辑,并以声明的方式进行。这有助于使代码保持DRY和声明性。
提示保护在
每个中间件之后
执行,但在
任何管道之前
执行。
授权守卫
如前所述,授权
是Guards的一个很好的用例,因为只有当调用者(通常是经过身份验证的特定用户)具有足够的权限时,特定的路由才可用。现在AuthGuard
,我们将构建的假设已通过身份验证的用户(因此,令牌附加到了请求标头中)。它将提取并验证令牌,并使用提取的信息来确定请求是否可以继续进行。
auth.guard.ts
JS
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
}
}
validateRequest()
函数内部的逻辑可以根据需要简单或复杂。该示例的重点是显示防护如何适应请求/响应周期。
- 如果它返回
true
,将处理用户调用。
执行上下文
export interface ArgumentsHost {
getArgs<T extends Array<any> = any[]>(): T;
getArgByIndex<T = any>(index: number): T;
switchToRpc(): RpcArgumentsHost;
switchToHttp(): HttpArgumentsHost;
switchToWs(): WsArgumentsHost;
}
在ArgumentsHost
一组有用的方法,有助于从底层阵列挑选正确的参数为我们提供。换句话说,ArgumentsHost
只不过是一个参数数组
。例如,当在HTTP应用程序上下文中使用guard时,ArgumentsHost
将包含[request, response]
数组。但是,当前上下文是Web套接字应用程序时,此数组将等于[client, data]
。此设计决策使您可以访问最终将传递给相应处理程序的任何参数。
在ExecutionContext
提供多一点点。它扩展了ArgumentsHost
,但也提供了有关当前执行过程的更多详细信息。
export interface ExecutionContext extends ArgumentsHost {
getClass<T = any>(): Type<T>;
getHandler(): Function;
}
所述getHandler()
返回一个参考当前处理的处理程序,而getClass()
返回的类型
的Controller
此特定处理程序属于类别。使用换句话说,如果用户指向create()
方法被定义和内注册CatsController
时,getHandler()
将返回一个参考create()
方法和getClass()
在这种情况下,将简单地返回一个CatsController
类型
(未实例)。
基于角色的身份验证
一个更详细的例子是一个RolesGuard
。此保护仅允许具有特定角色的用户访问。我们将从一个基本的防护模板开始,目前,它允许所有请求继续进行:
roles.guard.ts
JS
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class RolesGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
return true;
}
}
结合守卫
防护可以是控制器作用域
,方法作用域和全局作用域。为了设置守卫,我们必须使用@UseGuards()
装饰器。这个装饰器可能需要无数个参数,这意味着你可以传递几个守卫并用逗号分隔它们。
cats.controller.ts
JS
@Controller('cats')
@UseGuards(RolesGuard)
export class CatsController {}
提示
该@UseGuards()
装饰器从导入的@nestjs/common
包。
上面,我们传递了RolesGuard
类型(而不是实例),将实例化责任留给了框架并启用了依赖注入。与管道和异常过滤器一样,我们还可以传递就地实例:
cats.controller.ts
JS
@Controller('cats')
@UseGuards(new RolesGuard())
export class CatsController {}
上面的构造将警卫附加到该控制器声明的每个处理程序。如果我们决定只限制其中一个,我们只需要在方法级别
设置防护。为了绑定全局守护,我们使用useGlobalGuards()
Nest应用程序实例的方法:
const app = await NestFactory.create(ApplicationModule
app.useGlobalGuards(new RolesGuard()
注意
该useGlobalGuards()
方法不为网关和微服务设置保护。
全局防护用于整个应用程序,用于每个控制器和每个路由处理程序。在依赖注入方面,从任何模块外部注册的全局防护(如上例中所示)不能注入依赖关系,因为它们不属于任何模块。为了解决这个问题,您可以使用以下构造直接从任何模块
设置防护:
app.module.ts
JS
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
@Module{
providers: [
{
provide: APP_GUARD,
useClass: RolesGuard,
},
],
})
export class ApplicationModule {}
提示替代选项是使用执行上下文功能。此外,useClass这不是处理自定义提供程序注册的唯一方法。在这里了解更多。
反光
守卫现在正在工作,但我们仍然没有利用最重要的防守功能,即执行环境
。
RolesGuard
到目前为止,这是不可重用的。我们如何知道处理程序需要处理哪些角色?在CatsController
可能有很多。有些可能只适用于管理员,有些可供所有人使用,但是,它们没有任何权限。
这就是为什么除了守卫之外,Nest还提供了通过装饰器附加自定义元数据
的能力@ReflectMetadata()
。
cats.controller.ts
JS
@Post()
@ReflectMetadata('roles', ['admin'])
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto
}
提示
该@ReflectMetadata()
装饰器从导入的@nestjs/common
包。
通过上面的构造,我们为方法附加了roles
元数据(roles
是一个键,同时['admin']
是一个特定的值)create()
。@ReflectMetadata()
直接使用不是一个好习惯。相反,您应该始终创建自己的装饰器:
roles.decorator.ts
JS
import { ReflectMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => ReflectMetadata('roles', roles
这种方法更清晰,更易读。既然我们@Roles()
现在有了装饰器,我们就可以将它与create()
方法一起使用。
cats.controller.ts
JS
@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto
}
好的。让我们RolesGuard
再回过头来。它只是true
立即返回,允许请求继续进行到目前为止。为了反映元数据,我们将使用Reflector
框架提供的并从@nestjs/core
包中公开的辅助类。
roles.guard.ts
JS
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<string[]>('roles', context.getHandler()
if (!roles) {
return true;
}
const request = context.switchToHttp().getRequest(
const user = request.user;
const hasRole = () => user.roles.some((role) => roles.includes(role)
return user && user.roles && hasRole(
}
}
提示
在node.js世界中,将授权用户附加到request
对象是一种常见做法。这就是我们假设request.user
包含用户实例的原因。
该Reflector
使我们能够轻松地反映了指定的元数据的关键
。在上面的示例中,我们使用getHandler()
以反映元数据,因为它是对路由处理函数的引用
。如果我们添加控制器反射部分,我们可以使这个防护更加通用
。要提取控制器元数据
,我们应该使用context.getClass()
而不是getHandler()
函数:
JS
const roles = this.reflector.get<string[]>('roles', context.getClass()
当用户尝试在/cats
没有足够权限的情况下调用POST端点时,Nest将自动返回以下响应:
{
"statusCode": 403,
"message": "Forbidden resource"
}
事实上,回归false
投掷的后卫HttpException
。如果要向最终用户返回不同的错误响应,则应抛出异常。之后,异常过滤器可以捕获此异常。