AdSense

網頁

2020/4/10

Angular CanActivate limit Route path

Angular CanActivate限制Route.path存取Component的範例。

範例環境:

  • Windows 64 Bit
  • Angular CLI: 7.3.9
  • Angular: 7.2.16

請先參考「Angular 使用Session Storage登入登出簡單範例」,本篇將以該篇進行修改。

在「Angular 使用Session Storage登入登出簡單範例」只使用了單一個LoginComponent演示登入登出的效果;本篇則拆開為登入前LoginComponent與登入後HelloComponent

新增一個Service名為AuthService並實作CanActivate介面的canActivate()來決定/hello是否可被存取,返回true代表可存取,false代表不可存取。

此外把原本LoginComponent的登入登出邏輯搬到這裡。

AuthService (auth.service.ts)

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';

@Injectable({
  providedIn: 'root'
})
export class AuthService implements CanActivate { // <--實作CanActivate

  constructor() { }

  /** 實作CanActivate.canActivate() */
  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    const user = sessionStorage.getItem('user');
    if (user === null) {
      return false;
    } else {
      return true;
    }
  }

  /** 驗證使用者身分 */
  authentcate(username: string, password: string) {
    if (username === 'matt' && password === '123') {
      sessionStorage.setItem('user', username);
      return true;
    } else {
      return false;
    }
  }

  /** 清除session storage的使用者名稱 */
  clearSession() {
    sessionStorage.removeItem('user');
  }
}

修改LoginComponent如下。login()驗證的邏輯委託給AuthService.authentcate()

LoginComponent (login.component.ts)

import { Component, OnInit } from '@angular/core';
import { AuthService } from '../service/auth.service';
import { Router } from '@angular/router';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {

  username: string;
  password: string;
  isFail: boolean = false;
  warnMessage: string = '帳號或密碼錯誤';

  constructor(
    private authService: AuthService,
    private router: Router
  ) {
  }

  ngOnInit() {
  }

  login() {
    if (this.authService.authentcate(this.username, this.password)) {
      this.isFail = false;
      this.router.navigate(['hello']); // 登入後導向 /hello 頁面
    } else {
      this.isFail = true;
    }
  }

}

修改login.component.html如下。

login.component.html

<div>
  <div style="color:red;" *ngIf='isFail'>{{warnMessage}}</div>

  <div>
    <label>帳號:<input type="text" name="username" [(ngModel)]="username"></label><br>
    <label>密碼:<input type="password" name="password" [(ngModel)]="password"></label><br>
    <button (click)=login()>Login</button>
  </div>

</div>

新增一個Component名為HelloComponent做為登入後的頁面。

修改HelloComponent如下。

HelloComponent (hello.component.ts)

import { Component, OnInit } from '@angular/core';
import { AuthService } from '../service/auth.service';
import { Router } from '@angular/router';

@Component({
  selector: 'app-hello',
  templateUrl: './hello.component.html',
  styleUrls: ['./hello.component.css']
})
export class HelloComponent implements OnInit {

  username: string;

  constructor(
    private authService: AuthService,
    private router: Router
  ) {
    this.username = sessionStorage.getItem('user'); // HelloComponent初始時從session storage取得登入者名稱
  }

  ngOnInit() {
  }

  logout() {
    this.authService.clearSession(); // 清除session storage的使用者名稱
    this.router.navigate(['login']); // 登出後導向 /login 頁面
  }

}

修改hello.component.html如下。

hello.component.html

<div>
  <h1>Hello {{username}}</h1>
  <button (click)=logout()>Log out</button>
</div>

開啟AppRoutingModule (app-routing.module.ts),設定HelloComponentRoute.path'hello'

AppRoutingModule (app-routing.module.ts)

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LoginComponent } from './login/login.component';
import { HelloComponent } from './hello/hello.component';
import { AuthService } from './service/auth.service';

const routes: Routes = [
  {path:'login', component:LoginComponent},
  {path:'hello', component:HelloComponent} // 設定/hello為導向HelloComponent的路徑
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

完成以上後啟動專案,登入登出的效果如下。可以發現就算沒有登入也是能直接在瀏覽器網址輸入/hello導向原本要登入才能存取的HelloComponent


為了解決以上問題,可以利用Route.canActivate設定CanActivate的實作,也就是上面的AuthService.canActivate()來決定/hello是否能被存取。

AppRoutingModule設定/hellocanActivateAuthService如下。

AppRoutingModule (app-routing.module.ts)

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LoginComponent } from './login/login.component';
import { HelloComponent } from './hello/hello.component';
import { AuthService } from './service/auth.service';

const routes: Routes = [
  {path:'login', component:LoginComponent},
  {path:'hello', component:HelloComponent, canActivate: [AuthService]} // 設定/hello需經AuthService.canActivate()判斷是否可存取
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

設定canActivate後若未登入而直接在瀏覽器輸入/hello則返回空白結果。


或是改成導回登入頁面。修改AuthService如下。

AuthService (auth.service.ts)

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';

@Injectable({
  providedIn: 'root'
})
export class AuthService implements CanActivate {

  constructor(private router: Router) { } // 注入Router

  /** 實作CanActivate.canActivate() */
  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    const user = sessionStorage.getItem('user');
    if (user === null) {
      this.router.navigate(['login']); // 改為導向 /login
    } else {
      return true;
    }
  }

  /** 驗證使用者身分 */
  authentcate(username: string, password: string) {
    if (username === 'matt' && password === '123') {
      sessionStorage.setItem('user', username);
      return true;
    } else {
      return false;
    }
  }

  /** 清除session storage的使用者名稱 */
  clearSession() {
    sessionStorage.removeItem('user');
  }
}

修改後未登入直接在瀏覽器網址輸入/hello則導回/login登入頁面。


範例專案src/app目錄結構如下。

../src/app
│
│  app-routing.module.ts
│  app.component.css
│  app.component.html
│  app.component.spec.ts
│  app.component.ts
│  app.module.ts
│
├─hello
│      hello.component.css
│      hello.component.html
│      hello.component.spec.ts
│      hello.component.ts
│
├─login
│      login.component.css
│      login.component.html
│      login.component.spec.ts
│      login.component.ts
│
└─service
        auth.service.spec.ts
        auth.service.ts

參考:

沒有留言:

AdSense