如果你的应用规模没有很大,你完全可以将普通用户和超管的后台放到同一个服务上。 常见的角色鉴权步骤:
- 引入角色表存储用户的角色。 如果实在没必要引入,在use表中增加一个字段来区分角色。可以是bool值的isAdmin 或者是一个枚举类型。更复杂点使用二进制转十进制来区分多个角色。
- 用户在登录生成token时将角色信息用payload存到token中,需要角色判断时取token角色字段用来判断。(弊端,token有效时间内角色被剔除。解决:引入黑名单/白名单进行token失效处理)
- 为了保证管理员登录和用户登录区分开,可以在管理员登录时对通过密码校验的用户再增加角色校验,判断是否是管理员才下发token,否则抛出Forbidden错误。
- 在需要特定角色访问的接口,增加角色守卫设置来判断当前用户角色。
流程
根据上方角色鉴权流程
登录流程
为了简化,将管理员登录和普通用户登录放到一张图里,可以清楚的看到管理员是在用户凭据(密码||手机号||微信 ==)校验成功后继续校验角色。
鉴权流程
鉴权流程也很简单,分为两步:
- 1.JWT校验token
- 2.角色守卫校验角色
需要注意的是:在角色守卫中也增加了是否需要JWT校验(
需登录凭证
)的判断,这是因为如果接口本身增加了无需登录凭证的设置时,即便在有凭证的情况下,也并不会去校验和将token转成payload放到上下文中。(当然,你可以采取另一种办法。JWT的验证中去判断是否设置角色,如果设置了角色即便接口设置了无需登录凭证,也仍然需要进行凭证验证。)
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;
}
}
}
应用
- 将角色守卫绑定到全局 app.module上
providers: [
// 用户角色的守卫
{
provide: APP_GUARD,
useClass: RolesGuard,
},
]
2.在接口上使用角色装饰器限制角色访问
@Roles(EUserRole.Admin)
adminLimitCreate(@Body() someDto: SomeDto) {
return this.limitService.adminLimitCreate(someDto);
}
完结,撒花❀❀❀❀❀