守卫

作者: hellomyshadow | 来源:发表于2019-10-25 16:52 被阅读0次
    1. 守卫是一个 @Injectable() 装饰的类,守卫应该实现 CanActivate 接口;

    2. 守卫是一个单独的责任,用于权限验证;虽然访问限制逻辑通常在中间件内,但中间件是非常笨的,它不知道调用 next() 后会执行哪个处理程序;而守卫可以访问 ExecutionContext 对象,确切知道将要执行什么;所以在Nextjs中,可以用守卫做权限判断,也可以用中间件;

    3. 创建守卫

      nest g guard guard/auth
      
      1. 生成 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 则拒绝访问
              }
          }
      
      1. 守卫可以全局配置,也可以在控制器中使用,还可以在对应的模块中配置。
    4. 控制器中使用守卫

      1. 配置在控制器上,访问其中的所有控制器方法都会经过守卫校验;
          import { UseGuards } from '@nestjs/common';
          import { AuthGuard } from '../../../guard/auth.guard';
      
          @Controller('user')
          @UseGuards(AuthGuard)
          export class UserController {}
      
      1. 配置在控制器方法上,只有访问此路由时才会经过守卫校验;
          @Controller('user')
          export class UserController {
              @Get('add')
              UseGuards(AuthGuard)
              add() {
                  return '----add----'
              }
          }
      
    5. 全局配置守卫:main.ts,所有路由都经过守卫校验

      1. main.ts
      import { AuthGuard } from './guard/auth.guard';
      
      app.useGlobalGuards(new AuthGuard());
      
      1. 这种方式的注册不依赖于任何模块,所以在依赖注入方面,这种注册全局守卫的方式不能插入依赖项, 因为它们不属于任何模块。为此,还可以直接在任何模块中设置一个守卫,它同样是一个全局守卫:
      import { APP_GUARD } from '@nestjs/core';
      import { AuthGuard } from './guard/auth.guard';
      
      @Module({
          providers: [{ provide: APP_GUARD,  useClass: AuthGuard }],
          ......
      })
      export class XxxModule { }
      
    6. 在守卫中获取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;
      }
      

    反射器

    1. 守卫最重要的特征在于:执行上下文
    2. 守卫还不知道角色,每个处理程序允许哪些角色访问,也就是将路由和角色匹配起来,这就是自定义元数据所发挥的作用;
    3. @SetMetadata() 装饰器将定制的元数据附加到路由处理程序上,这些元数据提供了角色数据;守卫通过这些数据做出决策;
      @Post()
      @SetMetadata('roles', ['admin'])
      create() { }
      
      1. roles 为键、['admin'] 为特定值的元数据,被附加到 create() 方法上;
      2. 通过自定义装饰器@Roles(),封装 @SetMetadata()
          roles.decorator.ts
      
          import { SetMetadata } from '@nestjs/common';
          export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
      
          @Post()
          @Roles('admin')
          create() { }
      
    4. ExecutionContext:执行上下文对象,继承自ArgumentsHost
      1. ArgumentsHost是传递给原始处理程序的参数的包装器,ExecutionContext在此基础上扩展了getClass()、getHandler()
      2. getClass():获取处理程序所在的控制器类,是Controller类型,而不是实例;
      3. getHandler():获取将要调用的处理程序,也就是控制器方法。
    5. 在守卫中,通过反射器 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();
          }
      }
      
      1. 如果没有权限,即守卫返回falseNest默认响应:
          {
              "statusCode": 403,
              "message": "Forbidden resource"
          }
      
      1. 而实际上,返回false时,守卫会抛出一个 HttpException 异常;如果希望响应不同的错误内容,则手动抛出异常;
          throw new UnauthorizedException();
      
      1. 由守卫引发的任何异常都将由异常层(全局异常过滤器和应用于当前上下文的任何异常过滤器)处理。

    相关文章

      网友评论

          本文标题:守卫

          本文链接:https://www.haomeiwen.com/subject/gyfevctx.html