在各种业务系统中,为了保证业务及数据安全,除了要求用户必须登录后才能操作外,还针对不同的角色对不同用户设置了各自的访问权限,包括确定的某个页面的权限和页面中特定元素的权限。
本文记录了一种Angular页面常用的权限管理方法。
1、实现原理
本方法采用权限码对需要进行权限控制的页面路由和页面元素进行标记,用户登录系统的时候,后台返回对应用户具有权限的全部权限码,页面通过将路由或元素的权限码与用户的权限码进行匹配,进而判断用户是否具有访问权限。对于路由地址,没有权限将阻止访问并重定向到指定路由页面(通常是登录页面),对于页面元素,没有权限将移除对应元素。
2、示例说明
本例在根级路由添加了登录页面(http://localhost:4200/sign):
然后添加了两个模块AModule和BModule,两个模块中分别添加页面A1(http://localhost:4200/a/a1)、A2(http://localhost:4200/a/a2)、B1(http://localhost:4200/b/b1)、B2(http://localhost:4200/b/b2)。A1、A2、B1、B2页面要求登录才能访问。A1页面中添加四个按钮,分别要求具有对应权限码权限才能访问:
普通用户登录后A1页面中只能看到普通用户按钮,管理员按钮不可见,管理员用户登录后所有按钮均可见。
3、实现步骤
3.1、页面路由标记权限码
在各级路由配置文件中为各个路由添加路由守卫,在路由的data参数中添加acl属性,指定标记的权限码:
//AModule路由配置
{ path: 'a1', component: A1Component, canActivate: [AclGuard], data: { acl: "a.a1" } },
{ path: 'a2', component: A2Component, canActivate: [AclGuard], data: { acl: "a.a2" } },
//BModule路由配置
{ path: 'b1', component: B1Component, canActivate: [AclGuard], data: { acl: "b.b1" } },
{ path: 'b2', component: B2Component, canActivate: [AclGuard], data: { acl: "b.b2" } },
3.2、页面元素标记权限码
在html模板中,在想要进行权限控制的页面元素上添加权限控制指令([acl]),标记对应的权限码:
<button [acl]="'a.a1.user.btn1'">普通用户按钮1</button>
<button [acl]="'a.a1.admin.btn1'">管理员按钮1</button>
<button [acl]="'a.a1.user.btn2'">普通用户按钮2</button>
<button [acl]="'a.a1.admin.btn2'">管理员按钮2</button>
3.3、路由守卫检查权限
在路由守卫AclGuard中针对路由配置data参数中的权限码进行权限检查,拦截没有权限的访问:
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | Observable<boolean> | Promise<boolean> {
let data: any = { acl: "anonymous" };
Object.assign(data, route.data);
if (this.acl.isAclAuthorized(data.acl)) {
return true;
} else {
alert("没有访问权限!");
this.router.navigate(['/sign'], { queryParams: { from: state.url } });
return false;
}
}
代码中初始的 let data: any = { acl: "anonymous" } 是将路由配置中未设置data参数的路由地址标记为允许匿名访问,this.acl.isAclAuthorized(data.acl) 是调用权限控制服务类(AclService)对权限码进行检查。
3.4、指定控制元素展示
权限控制指令 AclDirective 通过读取传入参数获取元素标记的权限码:
/**
* 传入的权限码
*/
@Input('acl')
set acl(value: string) {
this.value = value;
this.set(value);
}
set方法通过权限码设置元素的显示状态:
private set(val: string): void {
const el = this.el.nativeElement;
if (this.aclService.isAclAuthorized(val)) {
//有权访问不做处理
} else {
//无权访问移除元素
el.style = "display:none!important;"
setTimeout(() => {
el.remove();
}, 100);
}
}
set方法中也是通过调用权限控制服务类(AclService)对权限码进行检查(this.acl.isAclAuthorized(data.acl))。当用户没有访问权限时先将元素隐藏(el.style = "display:none!important;"),再延时100ms将元素移出,添加延时是为了等待元素的渲染完成,渲染未完成时移除会引发异常。
3.5、权限控制服务类
通过权限服务类统一对用户权限进行管理,保存用户具有访问权限的全部权限码集合:
/**
* 设置用户权限码
* @param acl api返回的用户权限码
*/
setAcl(acl: Array<any>) {
this.acl = [...acl];
this.aclChange.next([...acl]);
}
判断目标权限码是否在用户权限码集合中:
/**
* 判断用户是否具有权限码对应权限
* @param aclCode 权限码
*/
isAclAuthorized(aclCode: string) {
return aclCode == "anonymous" || this.acl.findIndex(code => code == aclCode) >= 0;
}
3.6、调用后台接口获取权限
在登录页面,调用后台登录接口,后台返回登录结果及用户对应的有权限的权限码集合。页面获取返回结果后调用权限服务控制类保存权限码集合:
/**
* 用户登录
*/
signIn() {
this.api.signIn(this.userName, this.password).subscribe(data => {
this.acl.setAcl(data.acl);
this.router.navigate(["a/a1"]);
});
}
至此,前端页面整套的权限控制机制就完全实现了。