公司新项目后端框架选用 Nest,在前端请求 API 时需要验证身份信息。选用全局守卫做 token 认证。
1. 守卫
nest 守卫的用法可以参考官方手册:nest 手册
2. Jwt
Jwt 是 REST 框架常用的认证手段之一,使用方法参考手册:JWT TOKEN
3.全局守卫验证 token
- 在 src 下创建 common/guard/auth.guard.ts,非白名单的请求需要调用 jwtService.verify 验证 token; 白名单内的请求不用验证 token
import { Injectable, CanActivate, HttpException, HttpStatus, ExecutionContext } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { jwtConstants } from '../../auth/constants';
@Injectable()
export class AuthGuard implements CanActivate {
// 全局守卫
async canActivate(context: ExecutionContext): Promise<boolean> {
// context 请求的(Response/Request)的引用
console.log('进入全局权限守卫...');
// 获取请求头部数据
const request = context.switchToHttp().getRequest();
// 白名单验证
if (this.hasUrl(this.urlList, request.url)) {
return true;
}
// 获取请求头中的 authorization 字段
var token = context.switchToRpc().getData().headers.authorization;
// console.log(token);
token = token.replace('Bearer ', "");
// 验证token的合理性以及根据token做响应的操作
if (token) {
try {
// 校验 token
const jwtService = new JwtService();
const res = jwtService.verify(token, jwtConstants);
// 管理员校验,是否是管理员,管理员才能访问主管权限的路由
if(res.isMaster == false && this.hasUrl(this.masterUrlList, request.url)){
return false;
}
// 主管校验,是否是主管,主管才能访问主管权限的路由
if(res.isAdmin == false && this.hasUrl(this.adminUrlList, request.url)){
return false;
}
return res;
} catch (e) {
throw new HttpException(
'没有授权访问,请先登陆',
HttpStatus.UNAUTHORIZED,
);
}
} else {
throw new HttpException(
'没有授权访问,请先登陆',
HttpStatus.UNAUTHORIZED,
);
}
}
// 白名单
private urlList: string[] = [
'/api/v1/**/login',
'/api/v1/**/pwd',
'/api/v1/**/list',
];
// 主管路由名单
private masterUrlList: string[] = [
'api/v1/manager/**/list',
'api/v1/manager/**/search',
'api/v1/manager/**/export',
];
// 管理员路由名单
private adminUrlList: string[] = [
'api/v1/**/customer'
];
// 验证请求是否为白名单的路由
private hasUrl(urlList: string[], url: string): boolean {
let flag: boolean = false;
if (urlList.indexOf(url.split('?')[0]) >= 0) {
flag = true;
}
return flag;
}
}
2.在 mait.ts 调用 useGlobalGuards(new AuthGuard()),全局守卫就生效
import { Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from './modules/common/pipe/validation.pipe';
import { AuthGuard } from './modules/common/guard/auth.guard';
import { ResponseInterceptor } from './modules/common/interceptor/response.interceptor';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 全局注册通用验证管道 ValidationPipe
app.useGlobalPipes(new ValidationPipe());
// 全局注册权限验证守卫
app.useGlobalGuards(new AuthGuard());
// 全局注册拦截器
app.useGlobalInterceptors(new ResponseInterceptor())
const config = app.get(ConfigService)
app.setGlobalPrefix('api/v1'); // 设置全局路由前缀
await app.listen(config.get('port'));
Logger.log('http://localhost:' + config.get('port') + '/api/v1');
}
bootstrap();
- 使用全局守卫,有一个问题,即不能插入依赖项,因为它不属于任何一个模块,这意味着在 guard 中不能使用其他 module 提供的方法。使用 反射,获取权限元数据,通过数据来判断登录用户的权限。
- 创建 role.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { jwtConstants } from '../../auth/constants';
import { Observable } from 'rxjs';
import { Reflector } from '@nestjs/core';
import { UserService } from '../../user/user.service';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(
private reflector: Reflector,
private userService: UserService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
// 获取传进来的权限数组
const roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) {
return true;
}
// 获取请求头中的 authorization 字段
var token = context.switchToRpc().getData().headers.authorization;
// console.log(token);
token = token.replace('Bearer ', "");
// 校验 token
const jwtService = new JwtService();
const res = jwtService.verify(token, jwtConstants);
if(!res) {
return res;
}
// 获取用户信息
let userInfo = await this.userService.findUserForEmployeeId(res.email);
let adminString = 'admin'
let isAdmin = roles.some(role => {
return role == adminString})
let masterString = 'master'
let isMaster = roles.some(role => { return role == masterString})
// 管理员校验,是否是管理员,
if(isAdmin && isAdmin == userInfo.isAdmin) {
return true;
}
// 主管校验,是否是主管
if(isMaster && isMaster == userInfo.isMaster) {
return true;
}
return false;
}
}
- 通过 SetMetadata(),自定义装饰器。创建 roles.decorator.ts文件
import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
- 权限路由,在app.module.ts 引入
providers: [
AppService,
{
provide: APP_GUARD,
useClass: RolesGuard
}
]
- 在路由请求入口引入装饰器
@Get()
// 使用自定义的装饰器,校验接口权限
@Roles('admin')
findAll() {
return this.adminService.findAll();
}
基本能满足用户身份验证,权限验证。具体的代码逻辑可以按照业务逻辑再做调整。
参考: www.jianshu.com/p/ad13f4009… www.jianshu.com/p/8ba34787a…