注:该文档目前只对jwt做处理,缺少登录其它校验、以及redis部分,后续更新
1、安装
npm install --save @nestjs/jwt passport-jwt
npm install --save @nestjs/passport passport
npm install --save-dev @types/passport-jwt
2、注册jwt 文档
全局模块中引入jwtModule,提供密钥及过期时间,并导出,为后面注入做准备
import { Global, Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm';
import configurationService from '../config/index';
import { JwtModule } from '@nestjs/jwt';
@Global()
@Module({
imports: [
ConfigModule.forRoot({
cache: true,
load: [configurationService],
isGlobal: true,
}),
TypeOrmModule.forRootAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => {
return ({
type: "mysql",
autoLoadEntities: true, //自动加载实体
timezone: '+08:00',
...config.get("db.mysql"),
} as TypeOrmModuleOptions)
}
}),
JwtModule.registerAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => {
return {
secret: config.get("jwt.secretkey"),
signOptions: {
expiresIn: config.get("jwt.expiresin"),
},
}
}
})
],
providers: [
],
exports: [
TypeOrmModule,
JwtModule
],
})
export class ShareModule { }
3、登录成功后使用JwtServie生成密钥 文档
调用jwtService.signAsync(信息)生成Token
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class UsersService {
constructor(
private jwtService: JwtService,
) { }
async login() {
const payload = {userId:"通过登录成功后拿到的用户id或者其他信息"}
return await this.jwtService.signAsync(payload);
}
}
4、定义jwt解析,如果payload.userId不为空,抛出异常,如果为真,可以通过@Req中的user进行获取 文档
import { Injectable, UnauthorizedException } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { ExtractJwt, Strategy } from 'passport-jwt';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class JwtAuthStrategy extends PassportStrategy(Strategy) {
constructor(private readonly config: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: config.get('jwt.secretkey')!,
});
}
validate(payload: any) {
if(!payload.userId){
throw new UnauthorizedException('当前登录已过期,请重新登录')
}else{
return payload;
}
}
}
5、路由守卫,用来做拦截 文档
如果调用的super.canActivate(context),则会走JwtAuthStrategy类的校验,反之则不会走
import { ExecutionContext, Inject, Injectable, UnauthorizedException } from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { AuthGuard } from "@nestjs/passport";
import { JwtAuthStrategy } from './jwt-auth.strategy';
import { ConfigService } from '@nestjs/config';
import { pathToRegexp } from 'path-to-regexp';
import { isIn } from "class-validator";
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
private globalWhiteList: { path: string, method: string }[] = [];
constructor(
private readonly reflector: Reflector,
private readonly configService: ConfigService,
private readonly jwtAuthStrategy: JwtAuthStrategy
) {
super();
this.globalWhiteList = this.configService.get("perm.router.whitelist") || [];
}
async canActivate(context: ExecutionContext): Promise<boolean> {
const isPublic = this.reflector.getAllAndOverride('isPublic', [
context.getHandler(),
context.getClass()
])
if (isPublic) {
return true
}
const isINWhiteList = this.checkWhiteList(context);
if(isINWhiteList){
return true
}
const req = context.switchToHttp().getRequest();
if (!req.headers.authorization) {
throw new UnauthorizedException('当前登录已过期,请重新登录')
}
return super.canActivate(context) as boolean
}
checkWhiteList(ctx: ExecutionContext): boolean {
const req = ctx.switchToHttp().getRequest();
const i = this.globalWhiteList.findIndex((route) => {
// 请求方法类型相同
if (!route.method || req.method.toUpperCase() === route.method.toUpperCase()) {
// 对比 url
const { regexp } = pathToRegexp(route.path);
return !!regexp.exec(req.route.path);
}
return false;
});
// 在白名单内 则 进行下一步, i === -1 ,则不在白名单,需要 比对是否有当前接口权限
return i > -1;
}
}
上述代码解读:因为接下来要做的是定义全局守卫,会对所有路由都进行一个过滤,那么我们路由中比如登录、注册、验证码等等这些是不需要进行验证的,就有了一个获取元数据中是否存在isPublic,如果存在,就直接返回true,不在进行拦截
isPublic添加方法:
1、控制器添加@SetMetadata('isPublic', true)@Get() @SetMetadata('isPublic', true) findAll() { return this.usersService.findAll(); }
2、自定义装饰器,添加isPublic属性:
import { SetMetadata } from "@nestjs/common"; export const SkipAuth = () => SetMetadata("isPublic", true)
@Get() @SkipAuth() findAll() { return this.usersService.findAll(); }
上述代码解读:
checkWhiteList:白名单,可以自己定义不需要拦截的路由,以下是白名单配置格式
perm: router: whitelist: [ { path: '/captchaImage', method: 'GET' }, { path: '/registerUser', method: 'GET' }, { path: '/register', method: 'POST' }, { path: '/login', method: 'POST' }, { path: '/logout', method: 'POST' }, { path: '/perm/{id}', method: 'GET' }, { path: '/upload', method: 'POST' }, ]
注:白名单和isPublic方式用一种就好啦
6、注册全局守卫
将JwtAuthStrategy和JwtAuthGuard进行引入,注意顺序,因为JwtAuthGuard中存在JwtAuthStrategy的注入,所有JwtAuthStrategy必须在JwtAuthGuard之前
import { Global, Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm';
import configurationService from '../config/index';
import { join } from 'path';
import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler';
import { APP_GUARD } from '@nestjs/core';
import { JwtModule } from '@nestjs/jwt';
import { JwtAuthGuard } from '../utils/auth/jwt-auth.guard';
import { JwtAuthStrategy } from '../utils/auth/jwt-auth.strategy';
@Global()
@Module({
imports: [
ConfigModule.forRoot({
cache: true,
load: [configurationService],
isGlobal: true,
}),
TypeOrmModule.forRootAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => {
return ({
type: "mysql",
autoLoadEntities: true, //自动加载实体
timezone: '+08:00',
...config.get("db.mysql"),
} as TypeOrmModuleOptions)
}
}),
JwtModule.registerAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => {
return {
secret: config.get("jwt.secretkey"),
signOptions: {
expiresIn: config.get("jwt.expiresin"),
},
}
}
}),
],
providers: [
JwtAuthStrategy,
{
provide: APP_GUARD,
useClass: JwtAuthGuard,
}
],
exports: [
TypeOrmModule,
JwtAuthStrategy,
JwtModule
],
})
export class ShareModule { }