权限方案

用户权限缓存

接口权限限制

权限表设计

权限表设计

代码逻辑

代码规范

接口权限设置

// src/types/common.type
/** 通用元信息Key */
export enum MetadataKeyEnum {
  /** 鉴权 */
  Authentication = 'authentication',
  /** 权限 */
  Permissions = 'permissions'
}

// src/common/decorators/permission.decorator.ts
import { SetMetadata } from '@nestjs/common';
import { MetadataKeyEnum } from 'src/types/common.type';
import { PermissionCodeEnum } from 'src/types/permission.type';

/** 设置接口访问权限列表 */
export const Permission = (permssions: PermissionCodeEnum[]) => SetMetadata(MetadataKeyEnum.Permissions, permssions);

权限清单

src/types/permission.type.ts中管理,书写注意规范:

// src/types/permission.type.ts
/** 权限标识(提供给前端和接口判断权限) */
export enum PermissionCodeEnum {
  /** WARN: 用户 */
  User = '01',
  /** 用户 - 用户列表 */
  UserList = '0101',
  /** 用户 - 用户详情 */
  UserDetail = '0102',
  /** 用户 - 用户审核 */
  UserReviewer = '0103',
}

鉴权步骤

接口添加访问权限

import { Permission } from 'src/common/decorators/permission.decorator'
import { PermissionCodeEnum } from 'src/types/permission.type.ts'

@Get('users')
@Permission([
	PermissionCodeEnum.UserList,
])
@ApiOperation({ summary: '获取用户列表' })
async getUserList() {}

登录获取用户权限

  1. 根据 admin.role_id 查询 admin_role_permission_relationship(关联 admin_permission)获取用户所有权限的 code
  2. role:${roleId}的形式将角色的code存入Redis中(无限期有效)
  3. 将code字符串化后传给前端,前端根据code判断展示界面元素

接口鉴权(AuthGuard)

  1. 判断Token是否有效,无效则返回401
  2. 获取当前接口所需要的权限(@Permission中指定的)
  3. AuthGuard里根据TokenRedis获取用户信息(为了获取用户roleId
  4. 根据roleId查询当前角色所有权限的code
  5. 判断当前用户拥有的权限和接口的权限是否存在交集,如果有正常返回数据,没有返回403
// src/common/guards/auth.guard.ts

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(
    private readonly redisPlusService: RedisPlusService,
    private readonly reflector: Reflector,
  ) {}

  async canActivate(
    context: ExecutionContext,
  ): Promise<boolean> {
    /** 请求头部信息 */
    const { headers } = context.switchToHttp().getRequest();
    /**
     * 需要鉴权
     * @description 用作判读是否需要登录
     * @example @Metadata
     */
    const needAuth = this.reflector.get<boolean>(
      MetadataKeyEnum.Authentication,
      context.getHandler(),
    );
    if (needAuth !== false) {
      if (!headers[TOKEN_KEY]) {
        throw new AuthException('未登录', APICode.FAILED);
      }
      /** Redis用户信息 */
      const userInfo = await this.redisPlusService
        .getUserInfo(headers[TOKEN_KEY]);
      if (!userInfo) {
        throw new AuthException('未登录', APICode.FAILED);
      }
      /**
       * 接口所需权限
       * @description 调用此接口所需要的权限
       */
      const permissions = this.reflector.get<string[]>(
        MetadataKeyEnum.Permissions,
        context.getHandler(),
      );
      if (permissions && permissions.length > 0) {
        /** 当前用户角色的权限列表 */
        const rolePermission = await this.redisPlusService
          .getRolePermission(userInfo.roleId);
        if (permissions.some(o => rolePermission.includes(o))) {
          return true;
        } else {
          throw new PermissionException('无权访问', APICode.FAILED);
        }
      } else {
        throw new PermissionException('无权访问', APICode.FAILED);
      }
    } else {
      return true;
    }
  }
}

总结

总得来说,权限方面的管理蛮简单的,没有角色继承,权限分组等复杂的应用权限管理。本文重点是在接口层面的权限限制。