NestJS权限守卫
1. 简要介绍
权限认证作为大多数项目的常见且重要的部分,在NestJS
中也不例外。使用NestJS
构建服务,可以选择通过多种方式构建权限系统
- 自定义全局中间件实现
- 通过重构
NestJS
的canActivate
函数实现
今天,就会通过第二种方式来实现在NestJS
中快速构建鉴权系统(基于JWT
的)
2. 项目结构与Auth模块
2.1 创建Auth模块与对应Service
# nest g module auth
# nest g controller auth
# nest g service auth
# 再创建一个auth.guard.ts来自定义保卫器
nest开发的基本操作了
2.2 项目如下所示:
Auth模块
根据项目需求可以放在server
平级,也可以放在server
里面
3 构建登录模块
本质就是实现校验用户密码并返回
token
的过程
3.1 auth.module.ts配置
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard } from './auth.guard';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from 'src/entity/user.entity';
@Module({
imports: [
JwtModule.register({
global: true,
secret: 'rankmobile',
signOptions: { expiresIn: '7d' },
}),
TypeOrmModule.forFeature([User]),
],
controllers: [AuthController],
providers: [
AuthService,
{
provide: APP_GUARD,
useClass: AuthGuard,
},
],
})
export class AuthModule {}
3.2 auth.service.ts配置
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { InjectRepository } from '@nestjs/typeorm';
import { UserLoginDto } from 'src/dto/user.dto';
import { User } from 'src/entity/user.entity';
import { Repository } from 'typeorm';
@Injectable()
export class AuthService {
constructor(
private jwtService: JwtService,
@InjectRepository(User)
private usersRepository: Repository<User>,
) {}
async signIn(payload: UserLoginDto): Promise<any> {
let token;
const res = await this.usersRepository.findOne({
where: { username: payload.username, password: payload.password },
});
if (res != null) {
// 信息正确,返回正确的token
token = await this.jwtService.signAsync(payload);
console.log('Token生成');
} else {
// 信息正确,返回空的token,控制层再判断
token = '';
}
return token;
}
}
3.3 auth.controller.ts配置
import { Controller, Post, Body } from '@nestjs/common';
import { AuthService } from './auth.service';
import {
ApiBody,
ApiExtraModels,
ApiOperation,
ApiTags,
} from '@nestjs/swagger';
import { White_Router } from 'src/util/commonDecoration';
import { UserLoginDto } from 'src/dto/user.dto';
import { ResultData } from 'src/util/commonResponse';
import { ApiResult } from 'src/util/commonResult';
@Controller('auth')
@ApiTags('权限认证模块')
@ApiExtraModels(ResultData)
export class AuthController {
constructor(private authService: AuthService) {}
@White_Router()
@ApiOperation({ summary: '登录接口(获取token)' })
@ApiBody({ type: UserLoginDto })
@ApiResult(String, false, false)
@Post('login')
async signIn(@Body() payload: UserLoginDto) {
const token: string = await this.authService.signIn(payload);
if (token != '') {
return ResultData.successResponse('ok', token);//将token返回,实现登录并返回token的过程
} else {
return ResultData.errorResponse(401, '无权限', '无权限');
}
}
}
到此为止,即实现了鉴权模块的
登陆端口
部分,使得前端可以获取到token
信息,最后在完成其余接口的鉴权保护(token校验)
即可完成
4.构建自定义及安全保护
4.1 构建白名单装饰器
白名单问题上,使用
反射机制
来创建一个白名单装饰器
,配置在无需校验的接口上
//utils文件夹下commonDecoration.ts文件的代码
//之后的白名单跨越鉴权会引用到这里的
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const White_Router = () => SetMetadata(IS_PUBLIC_KEY, true);
4.2 Auth.guard.ts配置
整体上流程分为四步走:
- 反射实现白名单跨越鉴权
- 获取请求对象,拿出
token
token
鉴权- 收尾处理(成功后,请求对象挂载用户信息,失败后,抛出异常再统一处理)
下面的代码每一步都有相应的解释,大概可以看出来了
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { JwtService } from '@nestjs/jwt';
import { InjectRepository } from '@nestjs/typeorm';
import { Request } from 'express';
import { User } from 'src/entity/user.entity';
import { IS_PUBLIC_KEY } from 'src/util/commonDecoration';
import { Repository } from 'typeorm';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(
private jwtService: JwtService,
private reflector: Reflector,
@InjectRepository(User)
private usersRepository: Repository<User>,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
// 第一步:反射实现白名单跨越鉴权
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) {
//遇到白名单接口,直接允许,进入下一步
return true;
}
// 第二步:获取请求头对象,拿出token
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) {
throw new UnauthorizedException();
}
try {
// 第三步:token鉴权
const payload = await this.jwtService.verifyAsync(token, {
secret: 'rankmobile',
});
const username = payload.username;
const res = await this.usersRepository.findOne({
where: { username: username },
});
// 第四步:收尾处理(成功后,请求对象挂载用户信息,失败后,抛出异常再统一处理)
if (res) {
request['userInfo'] = res;
} else {
throw new Error('无权限');
}
} catch {
throw new UnauthorizedException();
}
return true;
}
private extractTokenFromHeader(request: Request): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
}
5. 接口绑定鉴权配置
5.1 鉴权接口绑定
@UseGuards(AuthGuard)//配置鉴权组件
@Get('profile')
getUserInfo(@Request() req) {
// --------------
//----------------
// 业务代码
}
5.2 全局鉴权绑定+白名单配置
在Auth.module.ts文件书写如下代码即可完成全局配置:
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard } from './auth.guard';
providers: [
AuthService,
{
provide: APP_GUARD,
useClass: AuthGuard,
},
],
整个项目总会有几个接口不需要鉴权,加上之前配置的白名单装饰器即可了。
@White_Router() //配置上白名单装饰器,即可实现该接口无需鉴权
@ApiOperation({ summary: '登录接口(获取token)' })
@ApiBody({ type: UserLoginDto })
@ApiResult(String, false, false)
@Post('login')
async signIn(@Body() payload: UserLoginDto) {
const token: string = await this.authService.signIn(payload);
if (token != '') {
return ResultData.successResponse('ok', token);
} else {
return ResultData.errorResponse(401, '无权限', '无权限');
}
}
}
本实践没有过多的文本描述,多在代码中的注释。但通过此个实践了解学习之后,应该可以较好的掌握在的React Hooks项目中应用Router V6封装整个项目的路由系统,能够真正实现一次封装,多处收益
相关的配套实践Demo会上传Github开源