天天看點

Angular 權限管理的兩種解決方案Angular 權限管理的兩種解決方案

Angular 權限管理的兩種解決方案

在做背景管理系統的時候,權限管理應該是必備的功能點了。這一節我們介紹兩種方案來确定使用者權限。

首先,我們面闆是這個樣子,先讓大家有一個基礎印象:

Angular 權限管理的兩種解決方案Angular 權限管理的兩種解決方案

準備工作

  1. 首先我們通過

    cli

    工具建立了一個

    heroes

    子產品,所有工作我們都将在這個子產品中完成;
  2. 其次建立了

    heroes-add

    heroes-list

    heroes-login

    heroes-modify

    四個頁面子產品,來分别實作不同的功能;
  3. 最後通過子路由的方式配置了項目的路由資訊,以便讓項目跑起來。
  4. 封裝一些常用的方法為服務,以便多處使用:
  5. 添加請求攔截器,為已登入使用者每次的請求頭添加

    token

    。(攔截器請參照7.2節介紹)

使用路由守衛控制權限

目前我們項目的狀态是:無論使用者是否登入,或者登入使用者的權限如何,都能直接進行新增、修改、删除等操作。顯然,這不是我們想要的。

是以,我們可以通過路由守衛來控制權限。

我們先給角色配置設定一下權限:

  • superadmin: 擁有所有權限;
  • admin: 隻有修改權限,沒有删除、新增權限;
  • user: 隻有檢視權限,沒有操作權限。

給路由配置添加角色(

roles

數組):

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

守衛

ng g g demos/heroes/guards/auth
           
// auth.guard.ts
// ...
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

Angular 權限管理的兩種解決方案Angular 權限管理的兩種解決方案

但是你會發現,我們還有個删除功能沒做權限管理。

一般情況下,删除應該是不會跳轉路由的,是以,我們需要另辟蹊徑來處理。

通過指令控制權限

我們想要的結果其實就是:根據角色,頁面上隻展示有權限的按鈕或其他跟權限有關的入口。

ng g d demos/heroes/directives/auth
           
// auth.directive.ts
import {Directive, Input, OnChanges, SimpleChanges, TemplateRef, ViewContainerRef} from '@angular/core';
import {UserService} from '../services/user.service';

@Directive({
  selector: '[appAuth]'
})
// 實作 OnChanges 接口
export class AuthDirective implements OnChanges{
  // 輸入屬性傳值,擷取有權限的角色
  @Input('appAuth') roles: string[] = [];
  hasView = false;
  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainer: ViewContainerRef,
    private userServe: UserService
  ) {}
  // 在 ngOnChanges 階段才能拿到輸入屬性的傳值
  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;
  }
}
           

使用指令:

Angular 權限管理的兩種解決方案Angular 權限管理的兩種解決方案

頁面表現:

Angular 權限管理的兩種解決方案Angular 權限管理的兩種解決方案

至此,我們就實作了通過角色來進行權限管理的全部功能。

通過動态配置權限實作權限管理

在實際工作中,我們可能還會遇到這樣的情況:使用者的角色是不固定的,所擁有的權限也是動态配置的。這樣的情況,我們如果采用上面的方式來做權限,那勢必會經常修改我們頁面上所配置的角色。是以,針對這樣的情況就要采取另一種方式。

我們打算通過頁面名與背景傳入的權限進行

view

new

delete

edit

等相應的權限控制。

為了示範,我們将會建立四個

normal

skill

grade

level

元件。

normal

是沒有被權限控制的,所有使用者都可以通路。

假如每個登入使用者資訊是這樣的:

{
  "name": "卡特", 
  "rights": {
    "skill": ["edit", "new"],
    "grade": ["view"]
  },
  ...
}
           

上面表示:使用者‘卡特’沒有通路

level

頁面的權限,可以在

skill

頁面編輯、建立,在

grade

頁面隻能檢視。

我們還是通過結構性指令來實作,如果沒有權限,完全不顯示對應入口的功能。

ng g d demos/heroes/directives/rights
           

對應需要控制的頁面入口,我們通過傳入頁面名進行控制:

Angular 權限管理的兩種解決方案Angular 權限管理的兩種解決方案

頁面中需要控制的操作入口,通過傳入操作類型來進行控制:

Angular 權限管理的兩種解決方案Angular 權限管理的兩種解決方案

方案确定,隻差實作指令:

// rights.directive.ts
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;
  }
  // 通過URL擷取頁面名
  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;
  }
}
           

來看效果:

Angular 權限管理的兩種解決方案Angular 權限管理的兩種解決方案

想要的效果已經實作,通過頁面名來比對可能不是最好的解決方式,因為這樣必須要求頁面名是唯一的,如有更好的解決方案,歡迎私信~

其實這裡還遇到一個問題:

權限管理必定會配合着路由懶加載,但是懶加載的元件是不需要在任何子產品中

declarations

數組中引入的,如果沒有引入元件,那麼指令就不會在子子產品中的元件中生效,會報錯。

是以,最後的解決方式就是在提供指令的子產品中同時引入懶加載路由的元件。不用擔心,懶加載依然有意義。

Angular 權限管理的兩種解決方案Angular 權限管理的兩種解決方案

總結

1. 在比較固定角色的情況下,采取“路由守衛 + 結構性指令”方案是不錯的選擇,相反的話第二種方式則更推薦;

2. 權限管理必定會配合着路由懶加載。

權限管理的處理方式可能還有其他方案,如果你的更好,請告訴我~

歡迎關注我的公衆号,公衆号将第一時間更新angular教程:

Angular 權限管理的兩種解決方案Angular 權限管理的兩種解決方案

繼續閱讀