Note
守卫
nestjs的守卫介绍
守卫
用于判断当前请求是否有资格进入这个路由
位置和流程
守卫在所有中间件之后、拦截器和管道之前执行。因此,守卫拒绝请求后,后面的 Pipe 、Controller 和 Service 都不会执行
客户端请求
↓
Middleware 中间件
↓
Guard 守卫
↓
Interceptor 拦截器(进入阶段)
↓
Pipe 管道
↓
Controller
↓
Service
↓
Interceptor 拦截器(返回阶段)
↓
客户端响应
常见作用
- 认证:判断用户是否登录、验证 JWT 是否有效、判断用户角色
- 鉴权:判断用户是否具有某项权限
定义守卫
使用 @Injectable() 修饰并且实现 CanActivate 接口,这个类就是守卫。守卫内部必须实现 canActivate 方法。守卫通常命名为 xxx.guard.ts
import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common";
@Injectable()
// 守卫类AuthGuard
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
return true;
}
}
ExecutionContext,在Argumenthost讨论过,它可以获取请求对象、当前Controller、当前路由方法
canActivate()返回值
返回值包含:boolean、Promise<boolean>、Observable<boolean>(不常用),可以看出守卫既可以同步检查,也可以异步检查。并且 canActivate 返回结果决定着是否继续处理请求,返回 true 继续,返回 false NestJS会拒绝请求,并在底层抛出 ForbiddenException ,通常返回:
{
"statusCode": 403,
"message": "Forbidden resource",
"error": "Forbidden"
}
如果需要返回其他错误,例如未登录时的 401,不要只返回 false,应主动抛出异常:
throw new UnauthorizedException("请先登录");
守卫可以对请求数据并进行修改,后续的Pipe、Controller接收到的数据是修改后的。不过建议只做它职责之内的处理,如只添加认证、授权相关的上下文信息,不要修改 body、query 或 params,这样按单一职责便于后期维护。
作用域
跟管道一样,存在方法级、Controller级、全局的守卫
方法级守卫
只作用于绑定的路由
@Controller("users")
export class UsersController {
@Get("profile")
// 作用于getProfile路由
@UseGuards(AuthGuard)
getProfile() {
return "个人资料";
}
}
Controller级守卫
只作用于该控制器的路由
@Controller("users")
// 作用UsersController的所有路由
@UseGuards(AuthGuard)
export class UsersController {
@Get()
findAll() {}
@Get(":id")
findOne() {}
@Post()
create() {}
}
全局守卫
作用于所有控制器和路由。也可通过在任意模块中使用 APP_GUARD + useClass 进行全局注册。
main.ts
const app = await NestFactory.create(AppModule);
// 注册全局守卫
app.useGlobalGuards(new AuthGuard());
UseGuards也可以传入实例@UseGuards(new AuthGuard()),但是推荐用类。app.useGlobalGuards只接受实例
存在多个守卫时,会依次执行,只要其中一个出现:返回false/抛出异常/promise被拒绝/observable抛出错误,那么后续的守卫和Controller就不会执行
常见用例
角色权限守卫
通过守卫设置角色权限
- 定义角色
export enum Role {
User = "user",
Admin = "admin",
}
- 创建角色元数据装饰器@Roles()
import { Reflector } from "@nestjs/core";
import { Role } from "./role.enum";
export const Roles = Reflector.createDecorator<Role[]>();
- 创建RolesGuard
import {
CanActivate,
ExecutionContext,
ForbiddenException,
Injectable,
} from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { Role } from "./role.enum";
interface CurrentUser {
id: number;
username: string;
roles: Role[];
}
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(Roles, [
context.getHandler(),
context.getClass(),
]);
// 角色列表为空,说明当前路由没有声明角色要求,默认允许
if (!requiredRoles?.length) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user as CurrentUser | undefined;
// 没有用户信息
if (!user) {
return false;
}
const hasRole = requiredRoles.some((requiredRole) =>
user.roles.includes(requiredRole),
);
// 用户信息不符合角色权限
if (!hasRole) {
throw new ForbiddenException("当前账号没有访问权限");
}
return true;
}
}
- 应用守卫
@Controller("users")
@UseGuards(JwtAuthGuard, RolesGuard)
export class UsersController {
@Get()
@Roles(Role.User, Role.Admin)
findAll() {
return "用户列表";
}
@Post()
@Roles(Role.Admin)
create() {
return "创建用户";
}
@Delete(":id")
@Roles(Role.Admin)
remove() {
return "删除用户";
}
}