守卫是一个使用 @Injectable()
装饰器的类。 守卫应该实现 CanActivate
接口。守卫跟中间件很像,但它有个单一职责,它们根据运行时出现的某些条件(例如权限,角色,访问控制列表等)来确定给定的请求是否由路由处理程序处理。这通常称为授权。在传统的 Express 应用程序中,通常由中间件处理授权。中间件是身份验证的良好选择。到目前为止,访问限制逻辑大多在中间件内。这样很好,因为诸如 token 验证或将 request 对象附加属性与特定路由没有强关联。
中间件不知道调用 next() 函数后会执行哪个处理程序。另一方面,警卫可以访问 ExecutionContext 实例,因此确切地知道接下来要执行什么。它们的设计与异常过滤器、管道和拦截器非常相似,目的是让您在请求/响应周期的正确位置插入处理逻辑,并以声明的方式进行插入。这有助于保持代码的简洁和声明性。注意:守卫在每个中间件之后执行,但在任何拦截器或管道之前执行。
**
1.自定义守卫
自定义守卫必须满足以下2两个条件:
(1).使用@Injectable()装饰器装饰。
(2).自定义守卫需要实现CanActivate接口,并重写CanActivate接口的canActivate()函数,canActivate()函数返回一个布尔值,指示是否允许当前请求,异步地返回响应(通过 Promise 或 Observable)。Nest使用返回值来控制下一个行为:
- 如果返回 true, 将处理用户调用。
- 如果返回 false, 则 Nest 将忽略当前处理的请求。
canActivate()接收单个参数ExecutionContext实例。ExecutionContext 继承自 ArgumentsHost ,ArgumentsHost 是传递给原始处理程序的参数的包装器,ExecutionContext 提供了更多功能,它扩展了 ArgumentsHost,但是也提供了有关当前执行过程的更多详细信息。
//结构 ExecutionContext
export interface ExecutionContext extends ArgumentsHost {
getClass<T = any>(): Type<T>;
getHandler(): Function;
}
接下来我们将实现一个验证注解,此注解的作用是验证请求的请求头是否包含token字段,如果请求头不包含token字段且需要验证则抛出一个异常,否则正常处理请求。
// src/common/guards/auth.guard.ts
/**
* 自定义守卫需要满足以下两个要求:
* (1).使用@Injectable()装饰器装饰
* (2).守卫类需实现CanActivate接口,并重写canActivate()函数,canActivate()返回一个布尔值来表示是否允许请求通过,
* canActivate()函数接收单个参数ExecutionContext实例,ExecutionContext继承自ArgumentsHost,
* ExecutionContext相比较ArgumentsHost提供了更多功能。我们可以通过ExecutionContext获取请求或响应对象。
*
* AuthGuard的作用是验证请求的请求头是否携带头,若请求携带请求头则允许请求到路由方法,否则将抛出错误,
* 这里使用@Auth()装饰器来装饰器需要验证的路由方法,若装饰器了@Auth()则需要验证,否则不需要验证,
* 这里是使用nest提供的反射器工具Reflector 用于判断类或方法上是否有装饰器,这是搭配SetMetadata使用的
*/
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { Reflector } from '@nestjs/core';
import { Request } from 'express';
@Injectable()
export class AuthGuard implements CanActivate {
//通过构造函数将Reflector注入进来
constructor(private readonly reflector: Reflector) {}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const auth = this.reflector.get<string[]>(
'auth',
context.getClass() || context.getHandler(),
);
//如果未使用@Auth() 或 @Auth(false) 则不需要验证正常通过
if (!auth) return true;
//否则验证请求对象请求头是否含有token
const request: Request = context.switchToHttp().getRequest();
const header = request.headers;
//判断请求头是否有token,若token为空则抛出错误
if (!header.token) {
throw new UnauthorizedException('');
}
return true;
}
}
然后自定义一个@Auth()装饰器,使用SetMetadata描述装饰器的元数据。
// src/common/decorators/auth.decorator.ts
/**
* 授权装饰器
*/
import { SetMetadata } from '@nestjs/common';
export const Auth = (isAuth: boolean = true) => SetMetadata('auth', isAuth);
使用守卫也很简单,使用@UseGuards()装饰器局部使用或者在main.ts中注册全局守卫。
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AppService } from './app.service';
import { AuthGuard } from './common/guards/auth.guard';
import { Auth } from './common/decorators/auth.decorator';
@Controller()
@Auth()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get('/hello')
@UseGuards(AuthGuard)
hello(): string {
return 'hello';
}
}
请求头传入token效果图:
请求头未传入token效果图: