什么是看守器(Guard)谅阿?
看守器就是使用 @Injectable 修飾并且實(shí)現(xiàn)了 CanActivate 接口的類。
一般使用看守器來(lái)做接口權(quán)限的驗(yàn)證溉苛,比如驗(yàn)證請(qǐng)求是否包含 token 或者 token 是否過(guò)期镜廉。
首先需要?jiǎng)?chuàng)建一個(gè)基本的看守器 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;
}
}
這個(gè)看守器沒有任何邏輯,只是簡(jiǎn)單的返回 true愚战,表示認(rèn)證通過(guò)娇唯。
我們預(yù)期的效果是像下面這樣,在 action 方法上附加一個(gè) 裝飾器 表示當(dāng)前 action 需要認(rèn)證才可以訪問:
@Get('info')
@Roles('user')
async info() {
}
自定義一個(gè)裝飾器:
src/users/decorators/common.decorator.ts
import { ReflectMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => ReflectMetadata('roles', roles);
這個(gè)裝飾器接收一個(gè)字符串?dāng)?shù)組寂玲, 作為需要被認(rèn)證的角色列表塔插,并且將其附加到元數(shù)據(jù)上,以便在看守器中可以通過(guò)反射元數(shù)據(jù)獲取到角色列表然后一一驗(yàn)證拓哟。
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) {
// 需要校驗(yàn)用戶權(quán)限
if(roles.some(item => 'user' == item)) {
return request.query.token || request.body.token;
}
}
return true;
}
}
修改看守器的邏輯想许,通過(guò)反射我們可以獲取到在裝飾器中定義的 roles 數(shù)組,然后判斷是否有 user 這個(gè)角色需要被驗(yàn)證断序,我們將采用當(dāng)前比較流行的 JWT 驗(yàn)證方式流纹,所以校驗(yàn)此次請(qǐng)求必須包含 token 字段, 否則驗(yàn)證失敗违诗。
如果我們?cè)诳词仄髦蟹祷?false 漱凝, Nest 會(huì)拋出一個(gè) HttpException 異常, 我們也可以拋出自定義的異常诸迟,然后用過(guò)濾器捕獲它茸炒。
我們已經(jīng)準(zhǔn)備好了看守器,現(xiàn)在需要一套完整的用戶體系阵苇,這里推薦大家使用 國(guó)內(nèi)領(lǐng)先的身份認(rèn)證云服務(wù)
Authing 壁公,只需要花 5 分鐘就可以擁有一個(gè)完整的用戶系統(tǒng)。(注:不是打廣告慎玖,現(xiàn)在不都講究 CloudNative 云原生嗎贮尖? 我們的宗旨就是笛粘,能用云服務(wù)搞定的趁怔,絕不自己瞎折騰)
安裝 Authing 的 SDK:
$ npm install authing-js-sdk --save
安裝 官方的 Express 中間件:
$ npm install express-authing --save
目前好像沒有對(duì) TypeScript 的類型支持,沒有類型支持也沒有關(guān)系薪前, Authing的API非常簡(jiǎn)單易用润努,對(duì)人類友好的代碼,才是優(yōu)秀的代碼示括。
$ 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 官網(wǎng)注冊(cè)铺浇。 為了簡(jiǎn)單這里直接硬編碼了,后面會(huì)介紹如何創(chuàng)建一個(gè) Config 模塊垛膝,將要配置的信息都存放到配置文件中鳍侣。
我們預(yù)期的效果是下面這樣的丁稀,從 token 中解析用戶的id 然后調(diào)用 Authing 的API 獲取用戶的詳細(xì)信息。
還是為了簡(jiǎn)單倚聚,這里用戶的郵箱和密碼都直接硬編碼了线衫,真實(shí)項(xiàng)目中應(yīng)該從 LoginDto 中獲取,并且用類驗(yàn)證器驗(yàn)證 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);
}
}
}
關(guān)鍵點(diǎn)就在于兩個(gè)自定義的路由參數(shù)裝飾器 @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的功能遠(yuǎn)遠(yuǎn)不止于此,感興趣可以去它的官網(wǎng)了解惨驶,這里只是拋磚引玉白热。