一看就会NestJS 基于角色最简单的权限设计

本文基于 NestJS 技术栈的数据库权限设计,适合对 NestJS 和数据库感兴趣的前后端开发者。

一、简介

基于角色的权限设计是使用最为广泛的设计之一,本文会以最简单的方式开始探索基于角色的权限设计,本设计基于 NestJS 和 TypeORM。

二、授权流程流程

2.1)登录(无需授权)

  • 授权认证从登录开始,客户端请求数据
  • 服务端的授权接口获取到登录相关数据
  • 服务端根据请求中数据与数据库存储的数据进行授权操作
  • 服务端授权操作之后响应数据

2.2)需要授权认证的接口

  • 请求过来,首先进入守卫
  • 守卫中判单角色是否满足要求
  • 满足守卫授权要求,进入接口路由处理
  • 返回数据

三、设计一个最简单的数据表模型

将用户与角色设计在一张表内,简化表设计难度(注意是最简单的设计,适合新手入门)

RABC(Role-Based Access Control) 基础知识

  • 用户拥有角色
  • 用户与角色:多对多的关系
  • 用户控制级别:
    • 基本模型: RABC0
    • 角色分级模型: RABC1
    • 角色限制模型: RABC2
    • 统一模型: RABC3。

本文趋近于 RABC0 进行设计,为最简单的设计,也就是只设计了一张表 User 表,User 中包含了角色。角色配合 jwt 完成角色授权设计。

四、定义一个角色枚举类型

ts 复制代码
export enum Role {
  ADMIN = 'admin',
  USER = 'user',
}

角色中包含两种,一种是 admin 和 user, 即管理员和普通用户。有了枚举类型就方便制作装饰器,对权限进行控制。

五、制作权限装饰器

ts 复制代码
import { SetMetadata } from '@nestjs/common';
import { Role } from '../../enums/role.enum';

export const ROLES_KEY = 'roles';

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

使用 TypeORM 定义 User 实体

User 实体是重要的实体,此版本为最简单的角色设计,角色被设计在 User 表内,意味着在创建用户的时候需要传递角色的相关的参数。

ts 复制代码
import { Role } from '../../enums/role.enum';
import {
  Column,
  Entity,
  PrimaryGeneratedColumn,
  CreateDateColumn,
  UpdateDateColumn,
} from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ nullable: false })
  username: string;

  @Column({ nullable: false, unique: true })
  email: string;

  @Column({ nullable: false })
  password: string;

  @Column({ type: 'enum', enum: Role, default: Role.USER })
  role: string;

  @CreateDateColumn({ name: 'created_at' })
  createdAt: Date;

  @UpdateDateColumn({ name: 'updated_at' })
  updatedAt: Date;
}

数据相关的角色相关已经完成了,下面就是使用角色,完成一些基本的业务。

六、登录获取请求中 user 数据

ts 复制代码
async login(@Req() req: Request) {
    return await this.authService.login(req.user);
}

七、定义授权守卫

之前已经定义了 Role 装饰器,授权守卫中完成的逻辑很简单,就是将当前的 req 中的角色数据与当前路由的 role 角色进行对比,如果符合存在 some 关系,守卫就通过。

ts 复制代码
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Role } from '../../enums/role.enum';
import { ROLES_KEY } from '../decorator/roles.decorator';

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

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
    const request = context.switchToHttp().getRequest();

    const authorization = request.headers;

    if (!requiredRoles) {
      return true;
    }
    const { user } = context.switchToHttp().getRequest();
    return requiredRoles.some((role) => user.roles?.includes(role));
  }
}

八、在接口中使用

定义一个 create 接口,此接口需要 Roles 的 ADMIN,也就是说一个用户,如果请求对象中 role 对应不是 ADMIN 权限,那么此接口不能访问。

ts 复制代码
@Post('/create')
@Roles(Role.ADMIN)
@UseGuards(JwtAuthGuard, RolesGuard)
async create(@Body() createProductDto: CreateProductDto) {
    return await this.productsService.create(createProductDto);
}

九、突破授权

因为守卫能够在控制器上使用,有时候一些控制器内部接口,需要放开权限,为了方便设置另外一个 Public 装饰器

ts 复制代码
mport { SetMetadata } from '@nestjs/common';

export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

十、小结

本文介绍了基于最为基本的角色设计,角色被设计在 User 中,也就意味着,每次请求中需要携带角色相关的信息。通过路由和守卫,判断当前路由接口是否能够访问。它有着明显的局限性,适合新手初级权限管理和设计一个基本的权限系统,侧重于角色设计,对于安全认证可以考虑通用的 jwt 等工具。

相关推荐
孟健1 小时前
从5000亿Token到83%字节匹配:AI逆向工程与重构的工程闭环实践
后端·架构·ai编程
IMPYLH1 小时前
HTML 的 <wbr> 元素
前端·html
Dovis(誓平步青云)2 小时前
常用命令藏得太深?怎么做一个快捷命令坞
运维·前端·自动化·音视频·prometheus
码事漫谈2 小时前
丢得静悄悄,谈一谈 Agent 的记忆是怎么没的
后端
迪飞特科技2 小时前
智慧物业平台实战:Spring Boot 整合小程序实现报修巡检费用全流程
spring boot·后端·小程序
考虑考虑2 小时前
@JsonInclude注解使用
spring boot·后端·spring
IT_陈寒3 小时前
Vite的这个问题坑了我三天,原来如此解决!
前端·人工智能·后端
网易云信3 小时前
大模型能上岗做销售吗?我们让 12 款国产模型交了 420 份作业
前端·后端
CCblandy3 小时前
HTTP 长连接 3 类故障实测:[WinError 10053]、99 个 TIME_WAIT、69 次多余握手,4 种对策怎么选
后端·python·网络协议·http·云原生·性能优化·架构
蜗牛互联网3 小时前
Python Agent工具调用的白名单、参数校验与审批门禁
java·服务器·前端·人工智能·后端·python