☰
Nebular Security 权限控制指南:基于 ACL 的角色授权体系详解
2026/9/26 10:36:44 网站建设 项目流程
  • 前端
  • UI组件

【免费下载链接】nebular

:boom: Customizable Angular UI Library based on Eva Design System :new_moon_with_face::sparkles:Dark Mode

项目地址:https://gitcode.com/gh_mirrors/ne/nebular
点击查看免费下载

导读

本文讲解 Nebular(基于 Eva Design System 的可定制 Angular UI 库)中@nebular/security模块的完整使用方式:从安装注册、ACL(访问控制列表)规则配置、RoleProvider角色提供,到模板指令与NbAccessChecker服务的实际授权用法。读完本文,你将掌握如何在不依赖@nebular/auth的前提下,为 Angular 应用搭建一套独立、灵活、可响应运行时角色变化的"谁能对什么资源做什么操作"的前端授权体系,并能直接照抄示例代码落地到自己的项目中。

认证(Authentication)与授权(Authorization)的边界

在深入代码之前,先厘清两个常被混淆的概念。Nebular 文档明确区分:

  • Nebular Auth(认证):回答"你是谁",负责验证用户身份(登录、注册、Token 管理等)。
  • Nebular Security(授权):回答"你能做什么",负责在身份已知后,判定该用户是否有权访问应用中的特定资源。

Nebular Security 与 Auth 模块互相独立,@nebular/security不依赖 Auth 或 Theme 模块,因此可以单独使用;文档同时建议在实际项目中与 Auth 模块配合,以形成"先认证、再授权"的完整链路。

⚠️安全警告:前端 ACL 无法解决全部安全问题,它只负责提供更好的用户体验。安全规则必须在后端重复实现(参见 docs/articles/security/intro.md)。本文所有示例仅用于前端 UI 控制(如按钮显隐、路由拦截的 UX 层面),绝不能替代服务端鉴权。

模块包含的能力清单

根据 docs/articles/security/intro.md,Security 模块开箱提供以下能力:

  • ACL 角色 / 权限 / 资源配置:通过一份声明式配置对象描述"谁(角色)能对什么(资源)做什么(权限)"。
  • RoleProvider(角色提供者):决定当前登录用户的角色,与认证流程解耦(authentication agnostic),返回Observable<string | string[]>。
  • NbAccessChecker(访问检查服务):基于当前角色与 ACL 配置,以Observable<boolean>形式返回授权结果,可注入任意组件、服务与路由守卫。
  • *nbIsGranted条件指令:类似*ngIf的模板级指令,根据角色动态显隐内容块。

此外文档预告了一个Security Decorator(安全装饰器),用于在方法级别管理访问权限,当时标注为 "coming soon",仓库当前版本未提供该实现。

安装与模块注册

若你已基于 ngx-admin starter kit 搭建应用,则 Security 模块已经内置就绪,可直接跳到配置环节。否则按以下步骤手动安装(依据 docs/articles/security/install.md):

npm i @nebular/security

在根模块(通常是app.module.ts)中导入并注册:

import { NbSecurityModule } from '@nebular/security'; @NgModule({ imports: [ // ... NbSecurityModule.forRoot(), ], }) export class AppModule {}

从源码看,NbSecurityModule.forRoot()的定义在 src/framework/security/security.module.ts:它接收可选的NbAclOptions配置,并通过NB_SECURITY_OPTIONS_TOKEN注入令牌向模块提供器(providers)注册NbAclService与NbAccessChecker,同时声明并导出NbIsGrantedDirective。也就是说,只要forRoot()被调用,上述三个核心件便在整个应用中可用。

ACL 配置:角色、权限与资源

配置模型

ACL 的核心思想是"谁(role)能对什么资源(resource)做什么权限(permission)操作"。配置通过NbSecurityModule.forRoot({ accessControl: ... })传入,其 TypeScript 类型定义位于 src/framework/security/security.options.ts:

export interface NbAclRole { parent?: string, [permission: string]: string | string[] | undefined, } export interface NbAccessControl { [role: string]: NbAclRole, } export interface NbAclOptions { accessControl?: NbAccessControl, }

即:accessControl是一个以角色名为键的对象;每个角色值中,parent是可选的角色继承字段,其余键为权限名,值为一个资源字符串或资源字符串数组。

典型三角色配置示例

沿用 docs/articles/security/acl-configuration.md 的场景:应用包含guest(游客)、user(用户)、moderator(版主)三种角色,view(查看)、create(创建)、remove(删除)三种权限,以及news(新闻)、comments(评论)两类受保护资源。规则为:

  • 游客只能view(查看)news与comments;
  • 用户继承游客的全部能力,额外可create(创建)comments;
  • 版主继承用户的能力,额外可create(创建)news,并可remove(删除)一切资源。

转换为 ACL 配置对象,在app.module.ts中修改forRoot()调用:

@NgModule({ imports: [ // ... NbSecurityModule.forRoot({ accessControl: { guest: { view: ['news', 'comments'], }, user: { parent: 'guest', create: 'comments', }, moderator: { parent: 'user', create: 'news', remove: '*', }, }, }), ], }) export class AppModule {}

要点解读:

  • 每个角色的值为"权限 -> 资源列表"映射,资源可以是一个字符串(如create: 'comments')或字符串数组(如view: ['news', 'comments'])。
  • *通配资源:remove: '*'表示拥有针对任意资源的删除权限,例如版主可删除news和comments。
  • parent角色继承:user继承guest,moderator继承user,权限沿父链逐级向上合并,实现"子角色包含父角色全部能力"的层级模型,避免重复声明。

底层实现原理

ACL 配置在启动时被NbAclService消费,实现在 src/framework/security/services/acl.service.ts:

  • 构造函数(L26-L30)通过@Optional() @Inject(NB_SECURITY_OPTIONS_TOKEN)接收配置,若存在accessControl则调用setAccessControl初始化内部状态。
  • setAccessControl(L36-L42)遍历每个角色,把角色值中的parent字段剥离出来,调用register注册"角色 -> { parent, abilities }"。
  • register(L50-L61)校验角色名非空,将字符串形式的资源统一归一化为数组,再调用allow逐个写入权限映射。
  • allow(L69-L82)维护state[role][permission]资源列表,并通过去重(filter((item, pos) => resources.indexOf(item) === pos))保证同一权限下资源不重复。
  • 核心判定方法can(role, permission, resource)(L91-L97):先递归检查父角色是否允许,再检查自身是否精确允许(exactCan),形成自底向上的继承链求值。exactCan(L115-L118)命中条件为资源精确匹配,或角色权限中包含'*'通配资源。
  • 一个值得注意的约束:can方法不允许传入空字符串或'*'作为资源参数(见 L109-L113 的validateResource),即'*'只能写在 ACL 配置里,不能作为运行时查询参数。

这套"父链递归 + 通配符"的判定逻辑,是理解后文NbAccessChecker行为的关键。

角色提供:RoleProvider

配置好 ACL 后,还需要告诉 Nebular "当前用户是哪个角色"。这一步由RoleProvider完成,其抽象定义在 src/framework/security/services/role.provider.ts:

import { Observable } from 'rxjs'; export abstract class NbRoleProvider { abstract getRole(): Observable<string | string[]>; }

getRole()返回一个发出角色名的Observable;返回类型既支持单个角色字符串,也支持角色数组(多角色场景,见后文)。由于是 Observable 流,角色变化可在应用运行期间被实时推送给授权检查方。

最简单形式:静态角色

直接在主模块providers中用useValue提供一个对象字面量(依据 docs/articles/security/acl-configuration.md):

// ... import { of as observableOf } from 'rxjs/observable/of'; import { NbSecurityModule, NbRoleProvider } from '@nebular/security'; @NgModule({ imports: [ // ... NbSecurityModule.forRoot({ // ... ACL 配置 }), ], providers: [ // ... { provide: NbRoleProvider, useValue: { getRole: () => { return observableOf('guest'); }, }, }, ], }) export class AppModule {}

这种配置的优点在于:角色来源与你的认证流程完全解耦,你可以随时替换为任何自定义实现。

与 Nebular Auth 集成的动态角色

真实应用中角色通常是动态的、随登录用户变化的。若已配置好基于 JWT 的Nebular Auth,可以从用户 Token 中提取角色。创建独立的role.provider.ts服务(依据 docs/articles/security/acl-configuration.md):

import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { map } from 'rxjs/operators/map'; import { NbAuthService, NbAuthJWTToken } from '@nebular/auth'; import { NbRoleProvider } from '@nebular/security'; @Injectable() export class RoleProvider implements NbRoleProvider { constructor(private authService: NbAuthService) { } getRole(): Observable<string> { return this.authService.onTokenChange() .pipe( map((token: NbAuthJWTToken) => { return token.isValid() ? token.getPayload()['role'] : 'guest'; }), ); } }

实现逻辑说明:

  • 订阅authService.onTokenChange()这个 Observable,每次认证状态变化(登录、登出、刷新 Token)都会产生一个新 Token;
  • token.isValid()为真时,从 Token payload 中读取role字段返回(示例假定 payload 中恒有 role);Token 无效时回退为默认角色guest。

最后在模块中注册为类提供者:

// ... import { RoleProvider } from './role.provider'; import { NbSecurityModule, NbRoleProvider } from '@nebular/security'; @NgModule({ imports: [ // ... NbSecurityModule.forRoot({ // ... ACL 配置 }), ], providers: [ // ... { provide: NbRoleProvider, useClass: RoleProvider }, // provide the class ], }) export class AppModule {}

如果你的项目不使用 Nebular Auth,完全可以将getRole的实现替换为从你自己的任意用户服务中读取角色——这正是RoleProvider设计上"认证无关"的灵活之处。

使用授权:模板指令与访问检查服务

方式一:*nbIsGranted条件指令

假设应用中有一个"Post Comment"(发表评论)按钮,只对拥有create权限于comments资源的用户(即user及以上角色)可见,游客不可见。使用指令方式(依据 docs/articles/security/acl-configuration.md):

@Component({ // ... template: ` <button *nbIsGranted="['create', 'comments']" >Post Comment</button> `, }) export class CommentFormComponent { // ... }

指令接收一个[permission, resource]二元数组作为输入。其底层实现见 src/framework/security/directives/is-granted.directive.ts:

  • 指令注入NbAccessChecker,在nbIsGrantedsetter 中调用accessChecker.isGranted(permission, resource);
  • 订阅结果并使用takeUntil(this.destroy$)管理生命周期;结果为true且视图尚未创建时,通过viewContainer.createEmbeddedView(this.templateRef)渲染内嵌视图;结果为false且视图已存在时调用viewContainer.clear()移除视图(L28-L36);
  • 因为订阅的是 Observable 流,当角色在运行时发生变化(如用户登出、切换账号)时,指令会自动重新求值并同步显隐,无需手动刷新页面。

方式二:NbAccessChecker.isGranted()服务

更高级的场景可直接注入NbAccessChecker服务。在comment-form.component.ts中:

import { Component } from '@angular/core'; import { NbAccessChecker } from '@nebular/security'; @Component({ // ... }) export class CommentFormComponent { constructor(public accessChecker: NbAccessChecker) { } }

配合async管道在模板中控制按钮显隐:

@Component({ // ... template: ` <button *ngIf="accessChecker.isGranted('create', 'comments') | async" >Post Comment</button> `, }) export class CommentFormComponent { // ... }

isGranted的返回类型与内部机制(src/framework/security/services/access-checker.service.ts):

isGranted(permission: string, resource: string): Observable<boolean> { return this.roleProvider.getRole() .pipe( map((role: string | string[]) => Array.isArray(role) ? role : [role]), map((roles: string[]) => { return roles.some(role => this.acl.can(role, permission, resource)); }), ); }
  • 从RoleProvider.getRole()获取当前角色流,把单个角色统一包装为数组;
  • 用roles.some(...)判定:只要任一角色通过NbAclService.can()检查即视为授权通过;
  • 由于整体是 Observable,*ngIf="... | async"会在角色流每次发出新值时重新渲染,同样具备运行时角色变更响应能力。

在其他场景使用

isGranted返回 Observable 的特性使其可以注入到任意位置使用,而不仅是组件模板——文档明确提到可应用于路由守卫(router guards)与业务服务中,从而获得一种"透明且可灵活配置"的资源访问管控方式。

进阶:为用户分配多个角色

当需求升级为用户同时拥有多个角色时,无需改动 ACL 配置,只需让RoleProvider.getRole()返回角色数组(依据 docs/articles/security/multiple-roles.md):

// ... import { of as observableOf } from 'rxjs/observable/of'; import { NbSecurityModule, NbRoleProvider } from '@nebular/security'; @NgModule({ imports: [ // ... NbSecurityModule.forRoot({ // ... ACL 配置 }), ], providers: [ // ... { provide: NbRoleProvider, useValue: { getRole: () => { // 返回当前用户的角色列表 return observableOf(['guest', 'user', 'editor']); }, }, }, ], }) export class AppModule {}

配合上面isGranted源码中的roles.some(...)逻辑可以确认:只要角色列表中至少有一个角色能访问该资源,isGranted即返回true。这与单元测试中"多个角色、任一命中即授权"的行为完全一致(见 src/framework/security/services/access-checker.spec.ts 中acl returns true (one of the roles return true)的用例)。

总结

Nebular Security 以一张声明式 ACL 配置表为核心,配合认证无关的RoleProvider提供当前角色,再经由NbAccessChecker(含*nbIsGranted指令封装)以响应式 Observable 的方式完成运行时授权判定。其设计亮点在于:

  1. 认证与授权解耦:RoleProvider可以从 Auth Token、自有用户服务或任何数据源读取角色;
  2. 角色层级继承:parent字段让权限沿角色链自动合并,配置量最小化;
  3. 通配资源*:一条规则即可覆盖任意资源;
  4. 运行时响应:基于 Observable 的流式设计,登录、登出、切换角色时 UI 权限实时刷新;
  5. 多角色支持:getRole返回数组即可,判定遵循"任一命中"语义。

再次强调:以上所有机制均作用于前端用户体验层,真正的安全防线必须由后端在每个 API 请求上独立强制执行。

  • 前端
  • UI组件

【免费下载链接】nebular

:boom: Customizable Angular UI Library based on Eva Design System :new_moon_with_face::sparkles:Dark Mode

项目地址:https://gitcode.com/gh_mirrors/ne/nebular
点击查看免费下载
上一篇:RT-Thread STM32F401 Nucleo-64 开发板 BSP 使用指南:从快速上手到外设配置实战
下一篇:Joplin 前端元数据(YAML Frontmatter)日期导入机制详解:以 short_date.md 测试样例为起点

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询