【NestJS应用从0到1】5. 处理管理员角色

如果你的应用规模没有很大,你完全可以将普通用户和超管的后台放到同一个服务上。 常见的角色鉴权步骤:

  • 引入角色表存储用户的角色。 如果实在没必要引入,在use表中增加一个字段来区分角色。可以是bool值的isAdmin 或者是一个枚举类型。更复杂点使用二进制转十进制来区分多个角色。
  • 用户在登录生成token时将角色信息用payload存到token中,需要角色判断时取token角色字段用来判断。(弊端,token有效时间内角色被剔除。解决:引入黑名单/白名单进行token失效处理)
    • 为了保证管理员登录和用户登录区分开,可以在管理员登录时对通过密码校验的用户再增加角色校验,判断是否是管理员才下发token,否则抛出Forbidden错误。
  • 在需要特定角色访问的接口,增加角色守卫设置来判断当前用户角色

流程

根据上方角色鉴权流程

登录流程

为了简化,将管理员登录和普通用户登录放到一张图里,可以清楚的看到管理员是在用户凭据(密码||手机号||微信 ==)校验成功后继续校验角色。

image.png

鉴权流程

鉴权流程也很简单,分为两步:

  • 1.JWT校验token
  • 2.角色守卫校验角色

需要注意的是:在角色守卫中也增加了是否需要JWT校验(需登录凭证)的判断,这是因为如果接口本身增加了无需登录凭证的设置时,即便在有凭证的情况下,也并不会去校验和将token转成payload放到上下文中。

(当然,你可以采取另一种办法。JWT的验证中去判断是否设置角色,如果设置了角色即便接口设置了无需登录凭证,也仍然需要进行凭证验证。)

image.png

Code it

在这之前,默认你已经读过前序文章,已经对守卫,passport等等有足够的了解。

准备工作

定义用户角色的方式

就像最前面说的,你有若干种区分用户角色的办法,你可以选择你适合的,只要能来判断用户角色即可。

其实角色可以很简单也可以很复杂,这些都没有特定的规范,如果你对角色权限内容有自定义的要求,你可能还要引入一个permission表,来存储某个角色具体拥有哪些权限。

定义角色装饰器以及角色守卫

角色装饰器:为了在具体的controller / module / method 上来定义角色。 角色守卫:通过判断是否使用角色装饰器设置了角色,以及判断当前用户是否拥有角色。

角色装饰器

代码示意了使用EUseRole的枚举来定义不同的角色,装饰器接受一个RoleEnum的数组,允许多个角色访问当前设置对象。

 
import { SetMetadata } from '@nestjs/common';
import { EUserRole } from '../enums/role.enum';

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

用户角色枚举定义

// 用户角色
export enum EUserRole {
  SuperAdmin = 'SuperAdmin',
  Admin = 'Admin',
  Default = 'Default',
}

角色守卫

import {
  Injectable,
  CanActivate,
  ExecutionContext,
  ForbiddenException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { EUserRole } from '../enums/role.enum';
import { ROLES_KEY } from 'src/decorator/role.decorator';
import { AUTH_NOT_REQUIRE_KEY } from '@/decorator/auth.decorator';
import { IPayload } from '@/interface/auth.interface';

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

  async canActivate(context: ExecutionContext): Promise<boolean> {
  
  // 获取当前使用装饰器设置的角色
    const requiredRoles =
      this.reflector.getAllAndOverride<EUserRole[]>(ROLES_KEY, [
        context.getHandler(),
        context.getClass(),
      ]);

     // 判断是否需要JWT鉴权
    const AuthNotRequire = this.reflector.getAllAndOverride<boolean>(
      AUTH_NOT_REQUIRE_KEY,
      [context.getHandler(), context.getClass()],
    );
    // 如果没有设置角色或者无需JWT鉴权,则直接跳过
    if (!requiredRoles || requiredRoles.length < 1 || AuthNotRequire) {
      return true;
    }
    // 在JWT的校验后,会将user放到request上(请参考之前passport系列内容)
    const req = context.switchToHttp().getRequest();
    if (!req.user) {
      throw new ForbiddenException();
    }
    const { roles } = req.user as IPayload;
    // 角色匹配判断
    if (!roles || !roles.some((roleType) => requiredRoles.includes(roleType))) {
      throw new ForbiddenException();
    } else {
      return true;
    }
  }
}

应用

  1. 将角色守卫绑定到全局 app.module上
  providers: [
    // 用户角色的守卫
    {
      provide: APP_GUARD,
      useClass: RolesGuard,
    },
    ]
2.在接口上使用角色装饰器限制角色访问
  @Roles(EUserRole.Admin)
  adminLimitCreate(@Body() someDto: SomeDto) {
    return this.limitService.adminLimitCreate(someDto);
  }

 完结,撒花❀❀❀❀❀

原文:https://juejin.cn/post/7381784676720377895

  • 13
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值