Nest 框架全局守卫验证 Jwt

1,188 阅读3分钟

公司新项目后端框架选用 Nest,在前端请求 API 时需要验证身份信息。选用全局守卫做 token 认证。

1. 守卫

nest 守卫的用法可以参考官方手册:nest 手册

2. Jwt

Jwt 是 REST 框架常用的认证手段之一,使用方法参考手册:JWT TOKEN

3.全局守卫验证 token

  1. 在 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();

  1. 使用全局守卫,有一个问题,即不能插入依赖项,因为它不属于任何一个模块,这意味着在 guard 中不能使用其他 module 提供的方法。使用 反射,获取权限元数据,通过数据来判断登录用户的权限。
  2. 创建 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;
  }
}
  1. 通过 SetMetadata(),自定义装饰器。创建 roles.decorator.ts文件
import { SetMetadata } from '@nestjs/common';

export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
  1. 权限路由,在app.module.ts 引入
providers: [
    AppService,
    {
      provide: APP_GUARD,
      useClass: RolesGuard
    }
  ]
  1. 在路由请求入口引入装饰器
  @Get()
  // 使用自定义的装饰器,校验接口权限
  @Roles('admin')
  findAll() {
    return this.adminService.findAll();
  }

基本能满足用户身份验证,权限验证。具体的代码逻辑可以按照业务逻辑再做调整。

参考: www.jianshu.com/p/ad13f4009… www.jianshu.com/p/8ba34787a…