一、守卫
目标
部分接口需要用户登录后才可以访问,用户登录后可以颁发 jwt_token 给前端,前端在调用需要鉴权的接口,需要在请求头添加 jwt_token,后端校验通过才能继续访问,否则返回403无权访问
创建守卫 anth
安装依赖
npm i @nestjs/jwt -S
配置 jwt 常量参数 constants/index.ts
// jwt秘钥,固定随机字符串
export const JWT_SECRET = "NODE_TEST_SECRET";// jwt有效期 Eg: "60s", "3h", "2d"
export const JWT_EXPIRES = "2h"; // 2小时
创建 auth 模块 auth/auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { AuthService } from './auth.service';
import { JWT_SECRET, JWT_EXPIRES } from '../constants';@Module({imports: [JwtModule.register({secret: JWT_SECRET, // jwt秘钥signOptions: {expiresIn: JWT_EXPIRES, // jwt有效期}})],providers: [AuthService],exports: [AuthService]
})
export class AuthModule {}
创建 auth 服务 auth/auth.service.ts
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';@Injectable()
export class AuthService {constructor(private readonly jwtService: JwtService) {}// 创建jwt_tokenasync createToken(userInfo) {// 将用户信息(userId、name)存放到 jwt 中return {access_token: this.jwtService.sign(userInfo)};}// 校验登录状态validateToken(token) {if (!token) {return false;}try {return this.jwtService.verify(token);} catch {return false;}}
}
创建 auth 守卫 auth/auth.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { AuthService } from './auth.service';@Injectable()
export class AuthGuard implements CanActivate {constructor(private authService: AuthService) {}canActivate(context: ExecutionContext) {const request = context.switchToHttp().getRequest();const token = request.headers['authorization']; // 从请求头中获取 tokenif (!token) {return false;} else {return this.authService.validateToken(token); // 如果 token 有效,返回 true,允许访问资源}}
}
使用守卫 auth
需要鉴权的模块添加 auth 模块
api.module.ts
...
import { AuthModule } from '../auth/auth.module';@Module({imports: [AuthModule],...
})
export class ApiModule {}
需要鉴权的接口引入守卫
api.controller.ts
...
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from '../auth/auth.guard';@Controller('api')
export class ApiController {constructor(private readonly apiService: ApiService) {}@Get('getUserInfo')@UseGuards(AuthGuard) // 需要鉴权的接口添加UseGuardsgetUserInfo(): any {return this.apiService.getUserInfo();}
}
登录
调用 auth/auth.service.ts 中的 createToken 生成 jwt_token 返回给前端
前端在请求头添加 authorization
如果请求头没有 authorization,或者 authorization 失效,则返回 403 Forbidden
二、全局守卫
目标
由于单个守卫需要引入守卫模块、守卫方法,并且针对每一个接口加 @UseGuards 装饰器,使用起来比较繁琐。项目中经常遇到非常多的接口都需要鉴权,所以就需要使用全局守卫来校验需要鉴权的接口。
注意:获取与解析 jwt_token 的方法上面守卫的方法一致,auth/auth.service,此处不再赘述。
创建全局守卫
配置需要鉴权的接口常量 constants/index.ts
// 需要校验 jwt 的 url 列表
export const ValidUrlList = ['/api/getUserInfo','/api/product/buy','/api/queryUserWallet'
];
新建全局守卫 global.guard.ts
import { Injectable, NestInterceptor, ExecutionContext, HttpException, HttpStatus } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { AuthService } from './auth/auth.service';
import { ValidUrlList } from './constants';@Injectable()
export class GlobalGuard implements NestInterceptor {constructor(private authService: AuthService) {}intercept(context: ExecutionContext, next): Observable<any> {// 在这里执行全局守卫逻辑const request = context.switchToHttp().getRequest();// 从请求头中获取 tokenconst token = request.headers['authorization'];// 请求的url,用于判断是否需要校验jwtconst url = request.url;if (ValidUrlList.includes(url)) {// 需要鉴权才能访问的接口const validRes = this.authService.validateToken(token);if (!validRes) {return next.handle().pipe(map(() => {// 如果没有提供令牌,返回错误响应或执行其他逻辑return new HttpException('Forbidden', HttpStatus.FORBIDDEN);}));}}// 不需要校验的接口、校验通过的接口直接放行return next.handle();}
}
在 app.module.ts 配置全局守卫
// ...
import { APP_INTERCEPTOR } from '@nestjs/core';
import { JwtModule } from '@nestjs/jwt';
import { GlobalGuard } from './global.guard';
import { JWT_SECRET, JWT_EXPIRES } from './constants';
import { AuthService } from './auth/auth.service';@Module({imports: [JwtModule.register({secret: JWT_SECRET, // jwt秘钥signOptions: {expiresIn: JWT_EXPIRES, // jwt有效期}}),],providers: [{provide: APP_INTERCEPTOR,useClass: GlobalGuard,},AuthService],
})
export class AppModule {}
三、swagger文档调用需要鉴权的接口
目标
接口添加 jwt 鉴权后,接口文档调用接口请求头没有添加 authorization,请求会返回403。为此,需要给文档需要鉴权的接口请求头也添加 authorization
步骤
main.ts 配置 BearerAuth 校验
const config = new DocumentBuilder().setTitle('接口文档').setDescription('接口文档描述').setVersion('1.0').addBearerAuth() // 注意此处:文档添加BearerAuth.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api-docs', app, document);
在需要鉴权的接口添加 @ApiBearerAuth() 装饰器
import { ApiBearerAuth } from '@nestjs/swagger';@Controller('api')
@ApiBearerAuth() // 在此处添加,表示/api/的接口请求头全都需要添加authorization
export class ApiController {@Get('getUserInfo')@UseGuards(AuthGuard)@ApiBearerAuth() // 在此处添加,表示当前接口请求头需要添加authorizationgetUserInfo(): any {return this.apiService.getUserInfo();}
}
使用
先调用登录接口获取到 jwt_token
点击文档顶部Authorize按钮
输入获取到的 jwt_token,并点击Authorize,然后关闭弹窗
再调用需要鉴权的接口,就可以鉴权通过了
注意:请求头的 Authorization 参数会在最前面添加 Bearer 字符,可以在守卫中将此字符移除
this.jwtService.verify(token.replace('Bearer ', ''))