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 | Observable {

        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 | Observable {

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

        const roles = this.reflector.get('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 中的全局模块和动态模块

你可能感兴趣的:(9、Nest.js 中的看守器)