iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 8
0
Modern Web

Nest.js framework 30天初探系列 第 8

Nestjs framework 30天初探:Day08 Guards

  • 分享至 

  • xImage
  •  

Guards

https://ithelp.ithome.com.tw/upload/images/20171211/20107195mBiZ14FRKr.png

Guard是一個帶有@Guard()裝飾器的類,Guard要去實作CanActivate介面。
Guard只做一件事情,就是擔任路由警衛,決定程式在收到HTTP請求後,是否要執行 route handler。 先前有介紹到Middleware,為何不使用Middleware作為路由警衛?因為Middleware只要有呼叫next(),必然會讓程式繼續執行下一個事件,所以不適合,角色權限的設計也會更顯麻煩。
注意:客戶發動請求的流程為 Request->Middleware->Guard->Pipe->route handler。

  1. 請在Shared資料夾創一個Guards資料夾,並新增roles.guard.ts。
    cmd指令:
cd src/modules/Shared & mkdir Guards

程式碼如下:
src/modules/Shared/Guards/roles.guard.ts

import { Guard, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs/Observable';

@Guard()
export class RolesGuard implements CanActivate {
    /*
    1.dataOrRequest參數代表你可以傳入expressjs中的request object、或經由microservice、websocket傳遞的data。
    2.ExecutionContext帶有兩個成員,parent和handler,其中,parent代表哪個Controller,handler是route handler的參考。
    3.Promise<boolean> | Observable<boolean>,代表路由警衛可以用async寫法。
    */
    canActivate(dataOrRequest, context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
        /*true會通過,false會回傳    
        {
        "statusCode": 403,
        "message": "Forbidden resource"
        }
        */
        return true;
    }
}

說明:我們先一睹Guard樣貌,後續會有較詳細的實作。
Guard跟Middleware和Pipe一樣可以作用在Method、Controller、Global。

  1. 接著我們將RolesGuard應用在UsersController。
    程式碼如下:
    src/modules/Users/users.controller.ts
import{ UseGuards } from '@nestjs/common';
import { RolesGuard } from '../Shared/Guards/roles.guard';
@Controller()
@UseGuards(RolesGuard)
//@UseFilters(new HttpExceptionFilter())
export class UsersController {...省略...}
  1. 打開Postman,對http://localhost:3000/users 做GET請求,結果如下。
    https://ithelp.ithome.com.tw/upload/images/20171211/20107195nJ3kzEHEle.png

說明:正常顯示。

  1. 將RolesGuard裡的return true改成false。
    程式碼如下:
    src/modules/Shared/Guards/roles.guard.ts
@Guard()
export class RolesGuard implements CanActivate {
    /*
    1.dataOrRequest參數代表你可以傳入expressjs中的request object、或經由microservice、websocket的data。
    2.ExecutionContext帶有兩個成員,parent和handler,parent代表哪個Controller,handler是route handler的參考。
    3.Promise<boolean> | Observable<boolean>,代表路由警衛可以用async寫法。
    */
    canActivate(dataOrRequest, context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
        /*true會通過,false會回傳    
        {
        "statusCode": 403,
        "message": "Forbidden resource"
        }
        */
        return false;
    }
}
  1. 打開Postman,對http://localhost:3000/users 做GET請求,結果如下。
    https://ithelp.ithome.com.tw/upload/images/20171211/20107195dAqEWxJa7G.png

說明:符合false的預期。

  1. 到目前為止,我們知道canActivate()透過return true/false作為通過與否,但一般都會有角色權限需要設定,透過角色權限決定能否通過,所以我們來實作Role。
    6.1 cmd指令:
cd src/modules/Shared & mkdir Decorators

6.2 在Decorators資料夾新增roles.decorator.ts。
src/modules/Shared/Decorators/roles.decorator.ts
程式碼如下:

import { ReflectMetadata } from '@nestjs/common';

//實作一個@Roles()裝飾器
export const Roles = (...roles: string[]) => ReflectMetadata('roles', roles);

6.3 修改RolesGuard。
程式碼如下:
src/modules/Shared/Guards/roles.guard.ts


import { Guard, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs/Observable';
import { Reflector } from '@nestjs/core';

@Guard()
export class RolesGuard implements CanActivate {
    constructor(private readonly reflector: Reflector) { }

    canActivate(req, context: ExecutionContext): boolean {
        const { parent, handler } = context;
        const roles = this.reflector.get<string[]>('roles', handler);
        if (!roles) {
            return true;
        }
        /*req.user是假資料,這是在模擬登入後,有一組user資訊放在req object裡,
        也可以放在session等,登入資訊的roles表示角色權限,是陣列,一個帳號可能有多個角色。
        而Ted的角色是general,能夠請求通過帶有@Roles('general')裝飾器的目標。
        */
        req.user = { "account": "Ted", "roles": ["general"] };
        const user = req.user;
        const hasRole = () => !!user.roles.find((role) => !!roles.find((item) => item === role));
        return user && user.roles && hasRole();
    }
}

說明:請看註解。

6.4 UsersController做些小變化。
程式碼如下:
src/modules/Users

@Controller()
@UseGuards(RolesGuard)
//@UseFilters(new HttpExceptionFilter())
export class UsersController {...省略...}
    @Get('users')
    @Roles('admin')
    //使用Express的參數
    async getAllUsers( @Request() req, @Response() res, @Next() next) {...省略...}
@Get('users/:id')
    @Roles('general')
    //使用Express的參數
    //@Param('id')可以直接抓id參數
    //使用ParseIntPipe
    async getUser( @Response() res, @Param('id', new ParseIntPipe()) id) {...省略...}

@UseGuards(RolesGuard)代表整個UsersController的方法都會去執行路由警衛機制,@Roles('xxx')代表只有該角色才能訪問,預期是http://localhost:3000/users 不能被訪問,但http://localhost:3000/users/1 可以被訪問。
6.5 打開Postman 對http://localhost:3000/users 發出GET請求,結果如下:

https://ithelp.ithome.com.tw/upload/images/20171211/20107195Jhd6S2HouQ.png

符合預期,因為getAllUsers方法帶有@Roles('admin')裝飾器,但Ted的role是general,所以被拒絕訪問。

6.6 打開Postman 對http://localhost:3000/users/1 發出GET請求,結果如下:
https://ithelp.ithome.com.tw/upload/images/20171211/20107195CwGFcEJMip.png

符合預期,因為getUser方法帶有@Roles('general')裝飾器,Ted的role是general,所以允許訪問。
以上大家已經知道怎麼實作Guard、Role,這可以讓我們更容易管理我們的route handler的訪問權限。

程式在github


上一篇
Nestjs framework 30天初探:Day07 Pipes
下一篇
Nestjs framework 30天初探:Day09 Interceptors
系列文
Nest.js framework 30天初探30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言