0%

2022-11-13 Nest.js 中关于守卫的使用

2022-11-13 Nest.js 中关于守卫的使用

参考:

Nest.js:https://docs.nestjs.cn/

守卫:https://docs.nestjs.cn/9/guards

基本使用

在 Nest.js 中,守卫是一个使用 @Injectable() 装饰器的类,守卫应该实现 CanActivate 接口。

一个简单的例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// auth.guard.ts
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); // 在此处编写自己的校验逻辑,返回true表示可以访问,false为禁止访问
}
}

深入使用

自定义元数据

在守卫的执行过程中,难免会需要根据不同的情况做不同的处理,此时就需要用到自定义元数据。

在 Nest.js 中,自定义元数据是非常方便的,可以使用 @SetMetadata() 装饰器将定制元数据。

1
2
3
4
5
6
7
// cats.controller.ts
@Post()
@SetMetadata('roles', ['admin']) // 在这里就将 元数据 附加了上去,key 为 roles ,value 为 ['admin'],至于怎么获取元数据,后面会说。
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}

很显然的是,直接调用 @SetMetadata() 并不是一个很好的做法,更好的写法是创建自己的装饰器,例如:

1
2
3
4
// roles.decorator.ts
import { SetMetadata } from '@nestjs/common';

export const Roles = (...roles: string[]) => SetMetadata('roles', roles);

之后只需要在要用到的地方调用即可。

1
2
3
4
5
6
// cats.controller.ts
@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}

使用元数据

在自定义了元数据之后,我们还需要拿到元数据的值才能使用,在这里可以使用 @nestjs/core 中提供的 Reflector 帮助类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';

@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}

canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<string[]>('roles', context.getHandler()); // 这一步就能拿到元数据了,需要注意的是,context.getHandler() 只能获取到 方法 上的元数据,无法获取 类 上的元数据。
if (!roles) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user;
return matchRoles(roles, user.roles);
}
}

元数据扩展

更多相关内容可参考:应用上下文

在上一节中,我们通过 context.getHandler() 来获取元数据,但这里获取的元数据只能是 方法 的,无法获取到 的,那么要怎么获取到 的元数据呢?

答案是通过 context.getClass(),即:

1
const roles = this.reflector.get<string[]>('roles', context.getClass()); 

那么接下来问题来了,如果要先从 获取元数据,再从 方法 获取元数据,要怎么办呢?

Nest.js 也提供了相应的方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// cats.controller.ts
@Roles('user')
@Controller('cats')
export class CatsController {
@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
}


// 如果是前者覆盖后者,则使用 getAllAndOverride(),此时将生成包含 ['admin']的 roles
const roles = this.reflector.getAllAndOverride<string[]>('roles', [
context.getHandler(),
context.getClass(),
]);

// 如果是两者合并,则使用 getAllAndMerge(),此时将生成包含 ['user', 'admin']的roles
const roles = this.reflector.getAllAndOverride<string[]>('roles', [
context.getHandler(),
context.getClass(),
]);

总结

通过 @SetMetadata() 设置元数据,再通过 Reflector 获取元数据,我们就可以非常方便的在守卫、拦截器、管道或者其他 Nest.js 组件中使用元数据,极大的简化了装饰器的使用。

本文作者:草梅友仁
本文地址: https://blog.cmyr.ltd/archives/b26331ff.html
版权声明:转载请注明出处!

坚持原创技术分享,您的支持将鼓励我继续创作!