Nest:守卫 guard:请求授权的利器

Nest.js 中的守卫(Guards))可以用于处理请求授权。
守卫是一个使用 @Injectable() 装饰器注解的类,它实现了 CanActivate 接口。守卫可以决定是否允许某个请求继续执行,通常是通过验证用户是否拥有执行操作的权限。
如果请求被允许执行,守卫应该返回 true 或者一个 ObservablePromise,这个 ObservablePromise 解析为 true
如果请求不被允许,守卫应该返回 false 或者一个解析为 falseObservablePromise,或者抛出一个异常。

守卫示例:

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

@Injectable()
export class RolesGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest<Request>();
    const cookies = request.cookies; // 获取 Cookie
    const session = request.session; // 获取 Session

    const user = session.user || request.user;
    // 校验用户是否拥有特定的角色
    return user && user.roles.includes('admin');
  }
}

这个方法接收一个 ExecutionContext 参数,它提供了请求的详细信息。
canActivate 方法中,我们可以获取到当前请求的用户信息,并检查用户是否拥有 ‘admin’ 角色。
如果用户是 ‘admin’,则返回 true,请求继续执行;如果不是,则返回 false,请求将被拒绝。

使用 @UseGuards() 装饰器应用守卫:

import { Controller, Get, UseGuards } from '@nestjs/common';
import { RolesGuard } from './roles.guard';

@Controller('items')
export class ItemsController {
  @Get()
  @UseGuards(RolesGuard)
  findAll() {
    // 只有当 RolesGuard 允许时,才会执行这里的代码
    return 'This route is protected by a roles guard';
  }
}

在上面的代码中,ItemsControllerfindAll 方法被 @UseGuards(RolesGuard) 装饰。
这意味着每次调用 findAll 方法时,RolesGuard 都会被触发,以确保用户具有正确的角色。

配置守卫方式

  1. 控制器级别的守卫
@Controller('cats')
@UseGuards(RolesGuard)
export class CatsController {
  // 所有 CatsController 中的路由都将受到 RolesGuard 的保护
}
  1. 方法级别的守卫
@Controller('cats')
export class CatsController {
  @Get()
  @UseGuards(RolesGuard)
  findOne() {
    // 只有 findOne 方法受到 RolesGuard 的保护
  }
}
  1. 全局守卫
@Module({
  // ...
  providers: [
    {
      provide: APP_GUARD,
      useClass: RolesGuard,
    },
  ],
})
export class AppModule {}

通过 provider 的方式声明的 Guard 是在 IoC 容器里的,可以注入别的 provider:

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private authService: AuthService) {}

  canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
    // 在这里可以使用注入的 AuthService 进行身份验证逻辑
    return this.authService.isAuthenticated();
  }
}
  1. 模块级别的守卫:虽然 Nest.js 直接不支持模块级别的守卫配置,但你可以在模块的 providers 数组中配置守卫,并使用模块的导出功能来共享这个守卫。这样,其他导入了该模块的模块可以复用同一个守卫。
@Module({
  providers: [RolesGuard],
  exports: [RolesGuard],
})
export class SharedModule {}

动态守卫

动态守卫指的是可以根据特定的条件或参数动态创建的守卫。
这种守卫通常是通过工厂函数或者类的静态方法来实现的,允许开发者在运行时根据需要构造不同的守卫实例。
动态守卫的一个常见用例是基于角色的访问控制,其中你可能希望根据不同的角色来限制对路由的访问。

下面是一个如何实现动态守卫的示例:

  1. 创建一个接受参数的工厂函数:该函数接受一些参数(例如角色列表),并返回一个新的守卫实例
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private readonly roles: string[]) {}

  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    return this.roles.some((role) => user.roles.includes(role));
  }
}

export function createRolesGuard(...roles: string[]): Type<CanActivate> {
  @Injectable()
  class DynamicRolesGuard extends RolesGuard {
    constructor() {
      super(roles);
    }
  }
  return DynamicRolesGuard;
}

在上面的代码中,createRolesGuard 是一个工厂函数,它接受一个角色列表并返回一个继承自 RolesGuard 的新守卫类。
新类 DynamicRolesGuard 通过构造函数将角色列表传递给 RolesGuard

  1. 在控制器中使用动态守卫:控制器中使用 @UseGuards() 装饰器和工厂函数来应用动态创建的守卫
@Controller('cats')
export class CatsController {
  @Get()
  @UseGuards(createRolesGuard('admin', 'moderator'))
  findAll() {
    // 只有 'admin' 或 'moderator' 角色的用户可以访问
  }

  @Post()
  @UseGuards(createRolesGuard('admin'))
  create() {
    // 只有 'admin' 角色的用户可以访问
  }
}

在上面的例子中,findAll 方法使用了一个允许 ‘admin’ 或 ‘moderator’ 角色的用户访问的动态守卫,而 create 方法使用了一个只允许 ‘admin’ 角色用户访问的守卫。
良好运用动态守卫能减少很多重复代码,提高灵活性。

原文链接:https://juejin.cn/post/7340539136897335322 作者:云牧牧

(0)
上一篇 2024年2月29日 上午10:52
下一篇 2024年2月29日 上午11:02

相关推荐

发表回复

登录后才能评论