GSS 技術部落格
在這個園地裡我們將從技術、專案管理、客戶對談面和大家分享我們多年的經驗,希望大家不管是喜歡或是有意見,都可以回饋給我們,讓我們有機會和大家對話並一起成長!
若有任何問題請來信:gss_crm@gss.com.tw
字體大小: +
5 分鐘閱讀時間 (1056 個字)

Angular # 32 - 深入 Routing [5]

shutterstock_198004562
  • Guarding routes

    • 這個主要是拿來防禦(X)某個 path 被 activate

      • 什麼情境下會有這種需求呢?比方說,使用者未登入,卻直接在瀏覽器的網址列輸入他先前偷偷背下來的路徑,這時候是絕對不能讓使用者看到內容的,不然大家就隨便亂看別人資料了啊
      • Guard 跟前面的 LifeCycle Hook 有點像,只不過它是在 Routing 的機制被觸發時,可以事前驗證某些條件,或是備齊某些資料用(p.s. 被觸發可以是 routerLink 或程式導)
      • Guard 主要有分為 5 種:
        • CanActivate: 決定某個 route 是否能被 activate(通常是身份驗證的結果 true/false)
        • CanActivateChild: 同上,但限定範圍在 Child route
        • CanDeactivate: 決定某個 route 是否能被 deactivate(像是要離開某個頁面但尚未儲存已編輯的資料)
        • CanLoad: 決定是否能 route 至被設定為 Lazy Load 的 module(而且是在 module 被載入前就 route)
        • Resolve: 是否能存取 route params 或是傳遞資訊給 component 的 providers
    • 那就來設定一個防止未登入使用者任意存取 view 的 Guard:

      • 先來建立一個新的 guard

          ng g s services/auth-guard
        • 沒錯的,Guard 跟 Interceptor 一樣是一種 service
      • 接著要實作 Guard 的內容了

          import { Injectable } from '@angular/core';
          import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot} from '@angular/router';
          import { UserService } from './user.service';
        
          @Injectable()
          export class AuthGuardService implements CanActivate {
        
            constructor(
              private userService: UserService,
              private router: Router
            ) { }
        
            canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
              if (!this.userService.isGuest()){
                return true;
              }
              else {
                this.router.navigate(['/login'], {
                  queryParams: {
                    return: state.url
                  }
                });
                return false
              }
            }
        
          }
        • 一開始就像一般的 service 要掛上 @Injectable,並 import 需要的東西,像是 CanActivate
        • CanActivate 是個 interface, 實作它之後必須要定義一個方法叫 canActivate,而這個方法回傳 true/false 會決定當前的使用者是否能 route 到某個 path
        • 承上,方法本身有兩個參數,第一個是目前所在的 route,第二個是想要存取的 route
        • 如果使用者是 guest 就可以 route,但如果不是,就導回登入的 view,而且這個導回是有 metadata
        • 承上,多傳的那個 queryParams 與 state.url 是會串在 URL 後面顯示著使用者是從哪裡被導到登入的 view 的(e.g. /login?return=/forums(chat:user))
      • Guard 定義好了要怎麼掛?答案是要設定在 route 的規則如下:

          const appRoutes: Routes = [
            { path: 'login', component: LoginComponent },
            { path: 'users', component: ChatListComponent, outlet: 'chat', canActivate: [AuthGuardService]},
            { path: 'users/:username', component: ChatComponent, outlet: 'chat', canActivate: [AuthGuardService]},,
            { path: 'blogs', loadChildren: 'app/blogs/blogs.module#BlogsModule' },
            { path: '', redirectTo: '/forums', pathMatch: 'full'},
            { path: '**', component: NotFoundComponent }
          ];
        • 這裡使用的是 canActivate 這種 Guard,並提供了 AuthGuardService
        • 記得要 import 並將 AuthGuardService 放到 providers
      • 來試看看是不是能守備成功

        • 嘗試打開聊天室窗,結果被導回登入的 view
        • 由此可證 Guard 是跑在 route 之前
        • 留意 URL 上真的有如我們設定的,會把被 redirect 前嘗試要 route 的 path 放在 return 後面
    • 登入完成之後,要把使用者導回原本的 view

      • 首先,要調整 LoginComponent 的 controller 如下:

          import { Component, OnInit } from '@angular/core';
          import { Router, ActivatedRoute } from '@angular/router';
          import { UserService } from '../services/user.service';
        
          @Component({
            selector: 'app-login',
            templateUrl: './login.component.html',
            styleUrls: ['./login.component.css']
          })
          export class LoginComponent implements OnInit {
            username: string = '';
            password: string = '';
            return: string = '';
        
            constructor(
              private userService: UserService,
              private router: Router,
              private route: ActivatedRoute
            ) { }
        
        
          ngOnInit() {
            this.route.queryParams.subscribe((params) => {
              this.return = params['return'] || '/forums';
              if (!this.userService.isGuest()) {
                this.go();
              }
            });
          }

          login() {
            if (this.username && this.password) {
              this.userService.login(this.username);
              this.go();
            }
          }

          go() {
            this.router.navigateByUrl(this.return);
          }
        }
        ```
        - 上面的重點在於 ngOnInit 的時候,先透過 **queryParams** 這個 observable 取得 **return** (如果有的話,沒有就給定 /forums),並設定到 this.return
        - 這兒使用的是 router 的 **navigateByUrl** 而非 **navigate**,前者是依我們提供的完整 url 作 routing,後者是會依提供的字串去調整現有的 url
        - 詳細的比較請參考[連結](https://stackoverflow.com/questions/45025334/how-to-use-router-navigatebyurl-and-router-navigate-in-angular)
    - 調整 AppComponent's 的 template
        ```html=
        <a class="nav-link nav-icon" (
           click)="logout()" 
           *ngIf="!userService.isGuest()">
        <clr-icon shape="logout"></clr-icon>
        </a>
        ```
        - 登入的圖示會依使用者目前的登入狀態決定
        - 而登入狀態又由 userService 的實作決定
        - 不過那個實著只是假的,實務上建議還是走正規的驗證
- Secondary Route 目前已經受到 Guard 守護了,但還是要避免登出時沒有關閉的情形發生
    - 因此我們要調整 UserService
        ```typescript=
        logout() {
            username = '';
            guest = true;
            localStorage.setItem('username', '');
            this.router.navigate([{ outlets: { chat: null }}]);
         }
        ```
        - 主要是在登出的時候,順便用注入的 **Router** 將 **chat** 這個 outlet 的 Secondary route 關閉
  • 小結
    • 此篇重點是權限
      • 在 Angular 可以透過 Guard 達到這件事
      • Guard 本身也只是一個 service
      • 盡量把權限控管做在 Guard 而非 Component 的 controller 實作邏輯
×
Stay Informed

When you subscribe to the blog, we will send you an e-mail when there are new updates on the site so you wouldn't miss them.

Angular #33 - 深入 Routing [6]
Angular #31 - 深入 Routing [4]

相關文章

 

評論

尚無評論
已經注冊了? 這裡登入
2025年12月28日, 星期日

Captcha 圖像