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的功能远远不止于此,感兴趣可以去它的官网了解,这里只是抛砖引玉。