管道
管道是具有 @Injectable() 装饰器的类。管道应实现 PipeTransform 接口。
管道的作用:
转换:管道将输入数据转换为所需的数据输出
验证:对输入数据进行验证,如果验证成功继续传递; 验证失败则抛出异常;
在这个作用下, 管道 参数(arguments) 会由 控制器(controllers)的路由处理程序 进行处理. Nest 会在调用这个方法之前插入一个管道,管道会先拦截方法的调用参数,进行转换或是验证处理,然后用转换好或是验证好的参数调用原方法。
内置管道
Nest 自带六个开箱即用的管道,即
ValidationPipe
ParseIntPipe
ParseBoolPipe
ParseArrayPipe
ParseUUIDPipe
DefaultValuePipe
每个管道必须提供 transform() 方法。 这个方法有两个参数:
value
metadata
import { PipeTransform, Injecttable, ArgumentMetadata } from '@nestjs/common';
@Injecttable()
export class ValidationPipe implements PipeTransform{
transform(value: any, metadata: ArgumentMetadata ) {
return value;
}
}
value 是当前处理的参数,而 metadata 是其元数据。元数据对象包含一些属性,元数据对象包含一些属性:
type 告诉我们该属性是一个 body @Body(),query @Query(),param @Param() 还是自定义参数
metatype 属性的元类型,例如 String。
data 传递给装饰器的字符串,例如 @Body(‘string’)。 如果您将括号留空,则为 undefined。
export interface ArgumentMetadata {
type: 'body' | 'query' | 'param' | 'custom';
metatype?: Type<unknown>;
data?: string;
}
对象结构验证
例如:我们在做添加的时候,需要验证实体类的属性,我们可以在路由方法中做到这一点,但是这回打破职责单一原则(SPR),另一个就是借助中间件进行处理,但是,我们不可能创建一个整个应用通用的中间件,因为中间件不知道执行的环境,以及被调用的函数和他的参数,这种情况下就可以考虑管道
Joi 库是允许您使用一个可读的 API 以非常简单的方式创建 schema(实体类)
安装依赖:
npm install --save @hapi/joi
npm install --save-dev @types/hapi__joi
我们需要在管道类中的构造函数中注入实体类,但前提是你以及创建好实体类
验证管道 要么返回该值,要么抛出一个错误
import {ArgumentMetadata, BadRequestException, Injectable, PipeTransform} from "@nestjs/common";
import {ObjectSchema} from "@hapi/joi";
@Injectable()
export class JoiValidationPipe implements PipeTransform {
constructor(private schema: ObjectSchema) {
}
transform(value: any, metadata: ArgumentMetadata): any {
const {error} = this.schema.validate(value);
if (error) {
throw new BadRequestException('Validation failed');
}
return value;
}
}
绑定管道
绑定管道(可以绑在 controller 或是其方法上)非常简单。我们使用 @UsePipes() 装饰器并创建一个管道实例,并将其传递给 Joi 验证。
@Post()
@UsePipes(new JoiValidationPipe(createCatSchema))
async create(@Body() createCatDto: CreateCatDto){
this.catsService.create(createCatDto);
}
类验证器(JavaScript不可用)
Nest 与 class-validator的兼容性很好,允许基于装饰器的验证,安装依赖:
npm i --save class-validator class-transformer
在实体类中添加装饰器即可
https://github.com/typestack/class-validator#usage查看更多类验证器修饰符的更多信息。
import {IsInt, IsString} from "class-validator";
export class CreateCatDto {
@IsString()
name: string;
@IsInt()
age: number;
@IsString()
breed: string;
}
创建管道类
import {ArgumentMetadata, BadRequestException, Injectable, PipeTransform} from "@nestjs/common";
import {validate} from "class-validator";
import {plainToClass} from "class-transformer";
@Injectable()
export class ValidationPipe implements PipeTransform<any> {
async transform(value: any, {metadata}: ArgumentMetadata) {
if (!metatype || !this.toValidate(metatype)) {
return value;
}
const object = plainToClass(metatype, value);
const errors = await validate(object);
if (errors.length > 0) {
throw new BadRequestException('Validation failed');
}
return value;
}
private toValidate(metatype: Function): boolean {
const types: Function[] = [String, Boolean, Number, Array, Object];
return !types.includes(metatype);
}
}
绑定管道类
管道,与异常过滤器相同,它们可以是方法范围的、控制器范围的和全局范围的。另外,管道可以是参数范围的。我们可以直接将管道实例绑定到路由参数装饰器,例如@Body()。
@Post()
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
当验证逻辑仅涉及一个指定的参数时,参数范围的管道非常有用。要在方法级别设置管道,您需要使用 UsePipes() 装饰器。
当然最高效的方式直接传入类(依赖注入)
@Post()
@UsePipes(new JoiValidationPipe(createCatSchema))
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
全局作用域管道
由于 ValidationPipe 被创建为尽可能通用,所以我们将把它设置为一个全局作用域的管道,用于整个应用程序中的每个路由处理器。
async function bootstrap(){
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap()
全局管道用于整个应用程序、每个控制器和每个路由处理程序。就依赖注入而言,从任何模块外部注册的全局管道(如上例所示)无法注入依赖,因为它们不属于任何模块。为了解决这个问题,可以使用以下构造直接为任何模块设置管道:
@Module({
providers: [
{
provide: APP_PIPE,
useClass: ValidationPipe
}
]
})
export class AppModule {}
转换管道
验证不是管道唯一的用处。管道也可以将输入数据转换为所需的数据,因为从 transform 函数返回的值完全覆盖了参数先前的值,有什么作用呢?
字符串转化为整数
密码加密…
转换管道被插入在客户端请求和请求处理程序之间用来处理客户端请求。
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
@Injectable()
export class ParseIntPipe implements PipeTransform<string, number> {
transform(value: string, metadata: ArgumentMetadata): number {
const val = parseInt(value, 10);
if (isNaN(val)) {
throw new BadRequestException('Validation failed');
}
return val;
}
}
如下所示, 我们可以很简单的配置管道来处理所参数 id:
@Get(':id')
async findOne(@Param('id', new ParseIntPipe()) id) {
return await this.catsService.findOne(id);
}
守卫
卫是一个使用 @Injectable() 装饰器的类。 守卫应该实现 CanActivate 接口。
守卫有一个单独的责任。它们根据运行时出现的某些条件(例如权限,角色,访问控制列表等)来确定给定的请求是否由路由处理程序处理。 这通常称为授权。在传统的 Express 应用程序中,通常由中间件处理授权。中间件是身份验证的良好选择。到目前为止,访问限制逻辑大多在中间件内。这样很好,因为诸如 token 验证或将 request 对象附加属性与特定路由没有强关联。
中间件不知道调用 next() 函数后会执行哪个处理程序。另一方面,警卫可以访问 ExecutionContext 实例,因此确切地知道接下来要执行什么。它们的设计与异常过滤器、管道和拦截器非常相似,目的是让您在请求/响应周期的正确位置插入处理逻辑,并以声明的方式进行插入。这有助于保持代码的简洁和声明性。
守卫在每个中间件之后执行,但在任何拦截器或管道之前执行。
授权守卫-AuthGuard
授权是守卫的一个很好的示例,因为只有当调用者(通常是经过身份验证的特定用户)具有足够的权限时,特定的路由才可用。我们现在要构建的 AuthGuard 假设用户是经过身份验证的(因此,请求头附加了一个token)。它将提取和验证token,并使用提取的信息来确定请求是否可以继续。
认识token
HTTP协议,是一种无状态的协议
比如:用户登陆成功,服务器没办法记录你登陆成功的状态
什么是token:
token可以翻译为令牌;
也就是在验证了用户账号和密码正确的情况,给用户颁发一个令牌;
这个令牌作为后续用户访问一些接口或者资源的凭证;
我们可以根据这个凭证来判断用户是否有权限来访问;
所以token的使用应该分成两个重要的步骤:
生成token:登录的时候,颁发token;
验证token:访问某些资源或者接口时,验证token;
JWT实现Token机制
JWT生成的Token由三部分组成:
header
alg:采用的加密算法,默认是 HMAC SHA256(HS256),采用同一个密钥进行 加密和解密;
typ:JWT,固定值,通常都写成JWT即可;
会通过base64Url算法进行编码;
payload
携带的数据,比如我们可以将用户的id和name放到payload中;
默认也会携带iat(issued at),令牌的签发时间;
我们也可以设置过期时间:exp(expiration time);
会通过base64Url算法进行编码
signature
设置一个secretKey,通过将前两个的结果合并后进行HMACSHA256的算法;
HMACSHA256(base64Url(header)+.+base64Url(payload), secretKey);
但是如果secretKey暴露是一件非常危险的事情,因为之后就可以模拟颁发token, 也可以解密token;