Nest.js 授权验证的方法示例
0x0前言
系统授权指的是登录用户执行操作过程,比如管理员可以对系统进行用户操作、网站帖子管理操作,非管理员可以进行授权阅读帖子等操作,所以实现需要对系统的授权需要身份验证机制,下面来实现最基本的基于角色的访问控制系统。
0x1RBAC实现
基于角色的访问控制(RBAC)是围绕角色的特权和定义的策略无关的访问控制机制,首先创建个代表系统角色枚举信息role.enum.ts:
exportenumRole{ User='user', Admin='admin' }
如果是更复杂的系统,推荐角色信息存储到数据库更好管理。
然后创建装饰器和使用@Roles()来运行指定访问所需要的资源角色,创建roles.decorator.ts:
import{SetMetadata}from'@nestjs/common' import{Role}from'./role.enum' exportconstROLES_KEY='roles' exportconstRoles=(...roles:Role[])=>SetMetadata(ROLES_KEY,roles)
上述创建一个名叫@Roles()的装饰器,可以使用他来装饰任何一个路由控制器,比如用户创建:
@Post() @Roles(Role.Admin) create(@Body()createUserDto:CreateUserDto):Promise{ returnthis.userService.create(createUserDto) }
最后创建一个RolesGuard类,它会实现将分配给当前用户角色和当前路由控制器所需要角色进行比较,为了访问路由角色(自定义元数据),将使用Reflector工具类,新建roles.guard.ts:
import{Injectable,CanActivate,ExecutionContext}from'@nestjs/common' import{Reflector}from'@nestjs/core' import{Role}from'./role.enum' import{ROLES_KEY}from'./roles.decorator' @Injectable() exportclassRolesGuardimplementsCanActivate{ constructor(privatereflector:Reflector){} canActivate(context:ExecutionContext):boolean{ constrequireRoles=this.reflector.getAllAndOverride(ROLES_KEY,[context.getHandler(),context.getClass()]) if(!requireRoles){ returntrue } const{user}=context.switchToHttp().getRequest() returnrequireRoles.some(role=>user.roles?.includes(role)) } }
假设request.user包含roles属性:
classUser{ //...otherproperties roles:Role[] }
然后RolesGuard在控制器全局注册:
providers:[ { provide:APP_GUARD, useClass:RolesGuard } ]
当某个用户访问超出角色范畴内的请求出现:
{ "statusCode":403, "message":"Forbiddenresource", "error":"Forbidden" }
0x2基于声明的授权
创建身份后,系统可以给身份分配一个或者多个声明权限,表示告诉当前用户可以做什么,而不是当前用户是什么,在Nest系统里实现基于声明授权,步骤和上面RBAC差不多,但有个区别是,需要比较权限,而不是判断特定角色,每个用户分配一组权限,比如定一个@RequirePermissions()装饰器,然后访问所需的权限属性:
@Post() @RequirePermissions(Permission.CREATE_USER) create(@Body()createUserDto:CreateUserDto):Promise{ returnthis.userService.create(createUserDto) }
Permission表示类似PRAC中的Role枚举,包含其中系统可访问的权限组:
exportenumRole{ CREATE_USER=['add','read','update','delete'], READ_USER=['read'] }
0x3集成CASL
CASL是一个同构授权库,可以限制客户端访问的路由控制器资源,安装依赖:
yarnadd@casl/ability
下面使用最简单的例子来实现CASL的机制,创建User和Article俩个实体类:
classUser{ id:number isAdmin:boolean }
User实体类俩个属性,分别是用户编号和是否具有管理员权限。
classArticle{ id:number isPublished:boolean authorId:string }
Article实体类有三个属性,分别是文章编号和文章状态(是否已经发布)以及撰写文章的作者编号。
根据上面俩个最简单的例子组成最简单的功能:
- 具有管理员权限的用户可以管理所有实体(创建、读取、更新和删除)
- 用户对所有内容只有只读访问权限
- 用户可以更新自己撰写的文章authorId===userId
- 已发布的文章无法删除article.isPublished===true
针对上面功能可以创建Action枚举,来表示用户对实体的操作:
exportenumAction{ Manage='manage', Create='create', Read='read', Update='update', Delete='delete', }
manage是CASL中的特殊关键字,表示可以进行任何操作。
实现功能需要二次封装CASL库,执行nest-cli进行创建需要业务:
nestgmodulecasl nestgclasscasl/casl-ability.factory
定义CaslAbilityFactory的createForUser()方法,来未用户创建对象:
typeSubjects=InferSubjects|'all' exporttypeAppAbility=Ability<[Action,Subjects]> @Injectable() exportclassCaslAbilityFactory{ createForUser(user:User){ const{can,cannot,build}=newAbilityBuilder< Ability<[Action,Subjects]> >(AbilityasAbilityClass ); if(user.isAdmin){ can(Action.Manage,'all')//允许任何读写操作 }else{ can(Action.Read,'all')//只读操作 } can(Action.Update,Article,{authorId:user.id}) cannot(Action.Delete,Article,{isPublished:true}) returnbuild({ //详细:https://casl.js.org/v5/en/guide/subject-type-detection#use-classes-as-subject-types detectSubjectType:item=>item.constructorasExtractSubjectType }) } }
然后在CaslModule引入:
import{Module}from'@nestjs/common' import{CaslAbilityFactory}from'./casl-ability.factory' @Module({ providers:[CaslAbilityFactory], exports:[CaslAbilityFactory] }) exportclassCaslModule{}
然后在任何业务引入CaslModule然后在构造函数注入就可以使用了:
constructor(privatecaslAbilityFactory:CaslAbilityFactory){} constability=this.caslAbilityFactory.createForUser(user) if(ability.can(Action.Read,'all')){ //"user"对所有内容可以读写 }
如果当前用户是普通权限非管理员用户,可以阅读文章,但不能创建新的文章和删除现有文章:
constuser=newUser() user.isAdmin=false constability=this.caslAbilityFactory.createForUser(user) ability.can(Action.Read,Article)//true ability.can(Action.Delete,Article)//false ability.can(Action.Create,Article)//false
这样显然有问题,当前用户如果是文章的作者,应该可以对此进行操作:
constuser=newUser() user.id=1 constarticle=newArticle() article.authorId=user.id constability=this.caslAbilityFactory.createForUser(user) ability.can(Action.Update,article)//true article.authorId=2 ability.can(Action.Update,article)//false
0x4PoliceiesGuard
上述简单的实现,但在复杂的系统中还是不满足更复杂的需求,所以配合上一篇的身份验证文章来进行扩展类级别授权策略模式,在原有的CaslAbilityFactory类进行扩展:
import{AppAbility}from'../casl/casl-ability.factory' interfaceIPolicyHandler{ handle(ability:AppAbility):boolean } typePolicyHandlerCallback=(ability:AppAbility)=>boolean exporttypePolicyHandler=IPolicyHandler|PolicyHandlerCallback
提供支持对象和函数对每个路由控制器进行策略检查:IPolicyHandler和PolicyHandlerCallback。
然后创建一个@CheckPolicies()装饰器来运行指定访问特定资源策略:
exportconstCHECK_POLICIES_KEY='check_policy' exportconstCheckPolicies=(...handlers:PolicyHandler[])=>SetMetadata(CHECK_POLICIES_KEY,handlers)
创建PoliciesGuard类来提取并且执行绑定路由控制器所有策略:
@Injectable() exportclassPoliciesGuardimplementsCanActivate{ constructor( privatereflector:Reflector, privatecaslAbilityFactory:CaslAbilityFactory, ){} asynccanActivate(context:ExecutionContext):Promise{ constpolicyHandlers= this.reflector.get ( CHECK_POLICIES_KEY, context.getHandler() )||[] const{user}=context.switchToHttp().getRequest() constability=this.caslAbilityFactory.createForUser(user) returnpolicyHandlers.every((handler)=> this.execPolicyHandler(handler,ability) ) } privateexecPolicyHandler(handler:PolicyHandler,ability:AppAbility){ if(typeofhandler==='function'){ returnhandler(ability) } returnhandler.handle(ability) } }
假设request.user包含用户实例,policyHandlers是通过装饰器@CheckPolicies()分配,使用aslAbilityFactory#create构造Ability对象方法,来验证用户是否具有足够的权限来执行特定的操作,然后将此对象传递给策略处理方法,该方法可以实现函数或者是类的实例IPolicyHandler,并且公开handle()方法返回布尔值。
@Get() @UseGuards(PoliciesGuard) @CheckPolicies((ability:AppAbility)=>ability.can(Action.Read,Article)) findAll(){ returnthis.articlesService.findAll() }
同样可以定义IPolicyHandler接口类:
exportclassReadArticlePolicyHandlerimplementsIPolicyHandler{ handle(ability:AppAbility){ returnability.can(Action.Read,Article) } }
使用如下:
@Get() @UseGuards(PoliciesGuard) @CheckPolicies(newReadArticlePolicyHandler()) findAll(){ returnthis.articlesService.findAll() }
到此这篇关于Nest.js授权验证的方法示例的文章就介绍到这了,更多相关Nest.js授权验证内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。