9、Nest.js 中的看守器

什么是看守器(Guard)?

看守器就是使用 @Injectable 修饰并且实现了 CanActivate 接口的类。
一般使用看守器来做接口权限的验证,比如验证请求是否包含 token 或者 token 是否过期。

首先需要创建一个基本的看守器 roles.guard.ts

src/users/guards/roles.guard.ts

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

@Injectable()
export class RolesGuard implements CanActivate {

    constructor() { }

    canActivate(
        context: ExecutionContext,
    ): boolean | Promise<boolean> | Observable<boolean> {

        return true;
    }
}

这个看守器没有任何逻辑,只是简单的返回 true,表示认证通过。
我们预期的效果是像下面这样,在 action 方法上附加一个 装饰器 表示当前 action 需要认证才可以访问:

    @Get('info')
    @Roles('user')
    async info() {

    }

自定义一个装饰器:

src/users/decorators/common.decorator.ts

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

export const Roles = (...roles: string[]) => ReflectMetadata('roles', roles);

这个装饰器接收一个字符串数组, 作为需要被认证的角色列表,并且将其附加到元数据上,以便在看守器中可以通过反射元数据获取到角色列表然后一一验证。

src/users/guards/roles.guard.ts

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

@Injectable()
export class RolesGuard implements CanActivate {

    constructor(
        private readonly reflector: Reflector
    ) { }

    canActivate(
        context: ExecutionContext,
    ): boolean | Promise<boolean> | Observable<boolean> {

        const request = context.switchToHttp().getRequest();       

        const roles = this.reflector.get<string[]>('roles', context.getHandler());

        if (roles && roles.length > 0) {

            // 需要校验用户权限
            if(roles.some(item =>  'user' == item)) {
                
                return request.query.token || request.body.token;
                
            }

        }

        return true;
    }
}

修改看守器的逻辑,通过反射我们可以获取到在装饰器中定义的 roles 数组,然后判断是否有 user 这个角色需要被验证,我们将采用当前比较流行的 JWT 验证方式,所以校验此次请求必须包含 token 字段, 否则验证失败。
如果我们在看守器中返回 false , Nest 会抛出一个 HttpException 异常, 我们也可以抛出自定义的异常,然后用过滤器捕获它。

我们已经准备好了看守器,现在需要一套完整的用户体系,这里推荐大家使用 国内领先的身份认证云服务
Authing ,只需要花 5 分钟就可以拥有一个完整的用户系统。(注:不是打广告,现在不都讲究 CloudNative 云原生吗? 我们的宗旨就是,能用云服务搞定的,绝不自己瞎折腾)
安装 Authing 的 SDK:

$ npm install authing-js-sdk --save

安装 官方的 Express 中间件:

$ npm install express-authing --save

目前好像没有对 TypeScript 的类型支持,没有类型支持也没有关系, Authing的API非常简单易用,对人类友好的代码,才是优秀的代码。

$ npm install @types/express-authing --save-dev
npm ERR! code E404
npm ERR! 404 Not Found: @types/express-authing@latest

npm ERR! A complete log of this run can be found in:
npm ERR!     /home/lin/.npm/_logs/2018-08-25T05_05_10_335Z-debug.log

在 main.ts 中使用 Authing 和 RolesGuard:

src/main.ts

import { NestFactory, Reflector } from '@nestjs/core';
import { AppModule } from 'app.module';
import { HttpExceptionFilter } from 'common/filters/http-exception.filter';
import { ApiParamsValidationPipe } from 'common/pipes/api-params-validation.pipe';
import * as Authing from 'express-authing';
import { RolesGuard } from 'users/guards/roles.guard';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  app.useGlobalPipes(new ApiParamsValidationPipe());
  app.useGlobalGuards(new RolesGuard(new Reflector()));

  app.use(Authing({
    clientId: 'xxxxxx',
    secret: 'xxxxx'
  }));

  await app.listen(3000);
}
bootstrap();

这里的 clientId 和 secret 需要去 Authing 官网注册。 为了简单这里直接硬编码了,后面会介绍如何创建一个 Config 模块,将要配置的信息都存放到配置文件中。

我们预期的效果是下面这样的,从 token 中解析用户的id 然后调用 Authing 的API 获取用户的详细信息。
还是为了简单,这里用户的邮箱和密码都直接硬编码了,真实项目中应该从 LoginDto 中获取,并且用类验证器验证 LoginDto 中的 email 和 password 字段。


import { Controller, Get, Post } from '@nestjs/common';
import { Authing, Roles, AuthUser } from './decorators/common.decorator';

@Controller('users')
export class UsersController {

    @Post('login')
    async login(@Authing() authing) {

        try {
            const result  = await authing.login({
                email: 'xxxxxx',
                password: 'xxxxx'
            });

            return result;

        } catch (err) {
            console.log(err);
        }
        
    }

    @Get('info')
    @Roles('user')
    async info(@AuthUser() user, @Authing() authing) {

        try {

            return await authing.user({
                id: user.data.id
            });

        } catch(err) {
            console.log(err);
        }
    }
}

关键点就在于两个自定义的路由参数装饰器 @AuthUser 和 @Authing :

src/users/decorators/common.decorator.ts

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

export const Roles = (...roles: string[]) => ReflectMetadata('roles', roles);

export const Authing = createParamDecorator((data, req) => {
    return req.authing;
})

export const AuthUser = createParamDecorator((data, req) => {
    let token = req.query.token || null;
    
    !token && (token = req.body.token);

    return req.authing.decodeToken(token);
})

到此为止我们花了不到5分钟就在 Nest.js 中集成了一整套用户体系, Authing的功能远远不止于此,感兴趣可以去它的官网了解,这里只是抛砖引玉。

上一篇:8、Nest.js 中的拦截器
下一篇:10、Nest.js 中的全局模块和动态模块

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容