9、Nest.js 中的看守器

作者: RoyLin1996 | 来源:发表于2018-08-25 14:20 被阅读530次

    什么是看守器(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 中的全局模块和动态模块

    相关文章

      网友评论

        本文标题:9、Nest.js 中的看守器

        本文链接:https://www.haomeiwen.com/subject/tefgiftx.html