Angular 权限管理的两种解决方案
在做后台管理系统的时候,权限管理应该是必备的功能点了。这一节我们介绍两种方案来确定用户权限。
首先,我们面板是这个样子,先让大家有一个基础印象:

准备工作
- 首先我们通过
cli
工具新建了一个 heroes
模块,所有工作我们都将在这个模块中完成;
- 其次新建了
heroes-add
、 heroes-list
、 heroes-login
、 heroes-modify
四个页面模块,来分别实现不同的功能;
- 最后通过子路由的方式配置了项目的路由信息,以便让项目跑起来。
- 封装一些常用的方法为服务,以便多处使用:
- 添加请求拦截器,为已登录用户每次的请求头添加
token
。(拦截器请参照7.2节介绍)
使用路由守卫控制权限
目前我们项目的状态是:无论用户是否登录,或者登录用户的权限如何,都能直接进行新增、修改、删除等操作。显然,这不是我们想要的。
所以,我们可以通过路由守卫来控制权限。
我们先给角色分配一下权限:
- superadmin: 拥有所有权限;
- admin: 只有修改权限,没有删除、新增权限;
- user: 只有查看权限,没有操作权限。
给路由配置添加角色(roles
数组):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| const routes: Routes = [ { path: 'heroes', component: HeroesComponent, children: [ { path: 'list', component: HeroesListComponent}, { path: 'login', loadChildren: () => import('./heroes-login/heroes-login.module').then(m => m.HeroesLoginModule), canActivate: [LoginAuthGuard] }, { path: 'add', loadChildren: () => import('./heroes-add/heroes-add.module').then(m => m.HeroesAddModule), canActivate: [AuthGuard], data: {roles: ['superadmin']} }, { path: 'modify/:id', loadChildren: () => import('./heroes-modify/heroes-modify.module').then(m => m.HeroesModifyModule), canActivate: [AuthGuard], data: {roles: ['superadmin', 'admin']} }, { path: '', redirectTo: 'list', pathMatch: 'full' } ] } ];
|
新建一个 auth
守卫
1
| ng g g demos/heroes/guards/auth
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
export class AuthGuard implements CanActivate { constructor( private userServe: UserService, private router: Router, private accountServe: AccoutService, private windowServe: WindowService ) {} canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> { const roles: string[] = route.data.roles; return this.userServe.user$.pipe( switchMap(user => { if (user) { if (roles.includes(user.role)) { return of(true); } else { this.windowServe.alert('没有权限'); return of(false); } } this.accountServe.redirectTo = state.url; this.windowServe.alert('请先登录'); this.router.navigateByUrl('/heroes/login').then(); return of(false); }) ); } }
|
这样,我们就能大概实现拦截功能:
tips: 艾科–user 莫甘娜–superadmin 卡特–admin

但是你会发现,我们还有个删除功能没做权限管理。
一般情况下,删除应该是不会跳转路由的,所以,我们需要另辟蹊径来处理。
通过指令控制权限
我们想要的结果其实就是:根据角色,页面上只展示有权限的按钮或其他跟权限有关的入口。
1
| ng g d demos/heroes/directives/auth
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| import {Directive, Input, OnChanges, SimpleChanges, TemplateRef, ViewContainerRef} from '@angular/core'; import {UserService} from '../services/user.service';
@Directive({ selector: '[appAuth]' })
export class AuthDirective implements OnChanges{ @Input('appAuth') roles: string[] = []; hasView = false; constructor( private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef, private userServe: UserService ) {} ngOnChanges(changes: SimpleChanges): void { if (this.roles.length) { this.userServe.user$.subscribe(res => { if (this.roles.includes(res?.role)){ this.createView(); } else { this.viewContainer.clear(); this.hasView = false; } }); } else { this.createView(); } } private createView(): void { this.viewContainer.createEmbeddedView(this.templateRef); this.hasView = true; } }
|
使用指令:

页面表现:

至此,我们就实现了通过角色来进行权限管理的全部功能。
通过动态配置权限实现权限管理
在实际工作中,我们可能还会遇到这样的情况:用户的角色是不固定的,所拥有的权限也是动态配置的。这样的情况,我们如果采用上面的方式来做权限,那势必会经常修改我们页面上所配置的角色。所以,针对这样的情况就要采取另一种方式。
我们打算通过页面名与后台传入的权限进行 view
、new
、delete
、edit
等相应的权限控制。
为了演示,我们将会新建四个 normal
、 skill
、 grade
、 level
组件。normal
是没有被权限控制的,所有用户都可以访问。
假如每个登录用户信息是这样的:
1 2 3 4 5 6 7 8
| { "name": "卡特", "rights": { "skill": ["edit", "new"], "grade": ["view"] }, ... }
|
上面表示:用户‘卡特’没有访问 level
页面的权限,可以在 skill
页面编辑、新建,在 grade
页面只能查看。
我们还是通过结构性指令来实现,如果没有权限,完全不显示对应入口的功能。
1
| ng g d demos/heroes/directives/rights
|
对应需要控制的页面入口,我们通过传入页面名进行控制:

页面中需要控制的操作入口,通过传入操作类型来进行控制:

方案确定,只差实现指令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| import {Directive, Input, OnChanges, SimpleChanges, TemplateRef, ViewContainerRef} from '@angular/core'; import {UserService} from '../services/user.service'; import { Router} from '@angular/router';
@Directive({ selector: '[appRights]' }) export class RightsDirective implements OnChanges{ @Input('appRights') rights = ''; hasView = false; constructor( private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef, private userServe: UserService, private router: Router ) {}
ngOnChanges(changes: SimpleChanges): void { const pageName = this.getPageName(this.router.url); if (this.rights) { this.userServe.user$.subscribe(res => { if (res?.rights) { if ( res.rights[this.rights] || (res.rights[pageName] && res.rights[pageName].includes(this.rights)) ) { this.createView(); } } else { this.clearView(); } }); } else { this.clearView(); } } private createView(): void { this.viewContainer.createEmbeddedView(this.templateRef); this.hasView = true; } private clearView(): void { this.viewContainer.clear(); this.hasView = false; } private getPageName(url: string): string { const str = url.split('/').pop(); if (str.includes('?')) { return str.split('?')[0]; } else if (str.includes('#')){ return str.split('#')[0]; } return str; } }
|
来看效果:

想要的效果已经实现,通过页面名来匹配可能不是最好的解决方式,因为这样必须要求页面名是唯一的,如有更好的解决方案,欢迎私信~
其实这里还遇到一个问题:
权限管理必定会配合着路由懒加载,但是懒加载的组件是不需要在任何模块中 declarations
数组中引入的,如果没有引入组件,那么指令就不会在子模块中的组件中生效,会报错。
所以,最后的解决方式就是在提供指令的模块中同时引入懒加载路由的组件。不用担心,懒加载依然有意义。

总结
1. 在比较固定角色的情况下,采取“路由守卫 + 结构性指令”方案是不错的选择,相反的话第二种方式则更推荐;
2. 权限管理必定会配合着路由懒加载。
权限管理的处理方式可能还有其他方案,如果你的更好,请告诉我~
欢迎关注我的公众号,公众号将第一时间更新angular教程:
