守卫
2019-10-25 本文已影响0人
hellomyshadow
-
守卫是一个
@Injectable()
装饰的类,守卫应该实现CanActivate
接口; -
守卫是一个单独的责任,用于权限验证;虽然访问限制逻辑通常在中间件内,但中间件是非常笨的,它不知道调用
next()
后会执行哪个处理程序;而守卫可以访问ExecutionContext
对象,确切知道将要执行什么;所以在Nextjs
中,可以用守卫做权限判断,也可以用中间件; -
创建守卫
nest g guard guard/auth
- 生成
guard/auth.guard.ts
import { Observable } from 'rxjs'; @Injectable() export class AuthGuard implements CanActivate { canActivate(context: ExecutionContext):boolean | Promise<boolean> | Observable<boolean> { console.log('守卫执行...'); return true; // 返回 true 表示当前具有访问权限,返回 false 则拒绝访问 } }
- 守卫可以全局配置,也可以在控制器中使用,还可以在对应的模块中配置。
- 生成
-
控制器中使用守卫
- 配置在控制器上,访问其中的所有控制器方法都会经过守卫校验;
import { UseGuards } from '@nestjs/common'; import { AuthGuard } from '../../../guard/auth.guard'; @Controller('user') @UseGuards(AuthGuard) export class UserController {}
- 配置在控制器方法上,只有访问此路由时才会经过守卫校验;
@Controller('user') export class UserController { @Get('add') UseGuards(AuthGuard) add() { return '----add----' } }
-
全局配置守卫:
main.ts
,所有路由都经过守卫校验main.ts
import { AuthGuard } from './guard/auth.guard'; app.useGlobalGuards(new AuthGuard());
- 这种方式的注册不依赖于任何模块,所以在依赖注入方面,这种注册全局守卫的方式不能插入依赖项, 因为它们不属于任何模块。为此,还可以直接在任何模块中设置一个守卫,它同样是一个全局守卫:
import { APP_GUARD } from '@nestjs/core'; import { AuthGuard } from './guard/auth.guard'; @Module({ providers: [{ provide: APP_GUARD, useClass: AuthGuard }], ...... }) export class XxxModule { }
-
在守卫中获取Cookie和Session
canActivate(context: ExecutionContext):boolean | Promise<boolean> | Observable<boolean> { // 获取到Request对象,从而获取到Cookie和Session let req = context.switchToHttp().getRequest(); // 获取Session let uname = req.session.uname; return true; }
反射器
- 守卫最重要的特征在于:执行上下文
- 守卫还不知道角色,每个处理程序允许哪些角色访问,也就是将路由和角色匹配起来,这就是自定义元数据所发挥的作用;
-
@SetMetadata()
装饰器将定制的元数据附加到路由处理程序上,这些元数据提供了角色数据;守卫通过这些数据做出决策;@Post() @SetMetadata('roles', ['admin']) create() { }
- 以
roles
为键、['admin']
为特定值的元数据,被附加到create()
方法上; - 通过自定义装饰器
@Roles()
,封装@SetMetadata()
roles.decorator.ts import { SetMetadata } from '@nestjs/common'; export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
@Post() @Roles('admin') create() { }
- 以
-
ExecutionContext
:执行上下文对象,继承自ArgumentsHost
-
ArgumentsHost
是传递给原始处理程序的参数的包装器,ExecutionContext
在此基础上扩展了getClass()、getHandler()
-
getClass()
:获取处理程序所在的控制器类,是Controller类型,而不是实例; -
getHandler()
:获取将要调用的处理程序,也就是控制器方法。
-
- 在守卫中,通过反射器
Reflector
访问自定义的元数据;import { Reflector } from '@nestjs/core'; @Injectable() export class RolesGuard implements CanActivate { // 依赖注入反射器对象 constructor(private readonly reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { // 根据元数据的键,反射获取控制器方法上的值:roles -- ['admin'] 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(); } }
- 如果没有权限,即守卫返回
false
,Nest
默认响应:
{ "statusCode": 403, "message": "Forbidden resource" }
- 而实际上,返回
false
时,守卫会抛出一个HttpException
异常;如果希望响应不同的错误内容,则手动抛出异常;
throw new UnauthorizedException();
- 由守卫引发的任何异常都将由异常层(全局异常过滤器和应用于当前上下文的任何异常过滤器)处理。
- 如果没有权限,即守卫返回