likes
comments
collection
share

Nest框架中的守卫

作者站长头像
站长
· 阅读数 14

根据运行时出现的某些条件(例如权限,角色,访问控制列表等)来确定给定的请求,是否由路由处理程序处理

Express中,用的是中间件处理授权和认证。但是,中间件调用next()函数后,不明确那个执行处理程序,守卫比较明确。

执行时机:守卫在每个中间件之后执行,但在任何拦截器或管道之前执行

创建守卫

在终端执行 $ nest g guard userGuard

$ nest g guard userGuard

授权守卫

只有当调用者(通常是经过身份验证的特定用户)具有足够的权限时,特定的路由才可用。假设用户是经过身份验证的(因此,请求头附加了一个token)。守卫将提取和验证token,并使用提取的信息来确定请求是否可以继续。

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
// 定义了一个名为UserGuardGuard的守卫,该守卫实现了CanActivate接口
export class UserGuardGuard implements CanActivate {
  // 实现了CanActivate接口中的canActivate方法,该方法接收一个执行上下文(ExecutionContext)参数,返回一个布尔值、Promise对象或Observable对象
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    // 执行上下文中的switchToHttp方法获取到HttpServer对象,并调用其getRequest方法获取到当前的请求对象
    const request = context.switchToHttp().getRequest();
    // 对请求进行校验,并返回校验结果
    return validateRequest(request);
  }
}

注入器(Injectable)、守卫接口(CanActivate)和执行上下文(ExecutionContext)

UserGuardGuard实现了CanActivate接口,用于在请求到达控制器之前进行身份验证和授权检查。在canActivate方法中,使用ExecutionContext获取当前请求的对象,并将其传递给validateRequest函数进行验证。

validateRequest函数返回一个boolean值,表示该请求是否具有访问权限。如果返回true,则请求将被允许进入控制器;如果返回false,则请求将被拒绝,并返回相应的错误响应

绑定守卫

和绑定过滤器拦截器一样

第一种情况:类型绑定

传递了 RolesGuard 类型而不是实例, 框架内部会进行实例化,并启用了依赖注入

@Controller('user')
@UseGuards(RolesGuard)
export class CatsController {}

第二种情况:实例绑定

@Controller('user')
@UseGuards(new RolesGuard())
export class CatsController {}

第三种情况:全局绑定(全局守卫)

const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new RolesGuard());

全局绑定问题:全局守卫,不能插入依赖项

解决方案:

import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_GUARD,
      useClass: RolesGuard,
    },
  ],
})
export class AppModule {}

控制用户角色权限

@SetMetadata() 装饰器将定制元数据附加到路由处理程序的能力。这些元数据提供了我们所缺少的角色数据,而守卫需要这些数据来做出决策。

@Post()
// 该方法需要 `admin` 角色的用户才能访问
@SetMetadata('roles', ['admin'])
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

在方法上使用了@SetMetadata装饰器,将一个key为'roles',value为['admin']的元数据附加到该方法上。

在实际开发中,一般用自定义的装饰器,更加灵活方便

例如:

// roles.decorator.ts
import { SetMetadata } from '@nestjs/common';

export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
// cats.controller.ts
@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

综合运用

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());
    if (!roles) {
      return true;
    }
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    return matchRoles(roles, user.roles);
  }
}

详解:

AuthGuard实现了CanActivate接口,用于在请求到达控制器之前进行身份验证和授权检查。在RolesGuard中,使用Reflector注入依赖,以便在canActivate方法中获取控制器方法上的元数据。

canActivate方法中,使用this.reflector.get()方法获取context.getHandler()对应的控制器方法上的roles元数据。如果该元数据不存在,则直接返回true,表示该请求具有访问权限。否则,获取当前请求的用户信息,并使用matchRoles函数进行角色匹配,如果匹配成功,则返回true,否则返回false。