最近也是在学习nestjs这一块的内容,也是看到nestjs安全这一块内容,顺便就使用jwt去实现了一下注册、登录、用户密码加密和对后端接口做一个统一的权限处理,注册登录功能大家都很熟悉,而接口权限统一处理,也就是咱们平时开发时,除了登录注册等一些公共接口以外的接口,需要对token进行一个验证,验证不通过则返回401,通过才去返回正确的状态码和数据。下面是整体的一个流程,可能有点长,我会一步一步带大家去完成这些功能,并附上源码,希望大家耐心看完。
首先,咱们去创建一个nestjs项目,使用
nest new xxx
,我这里简单取名为auth,包管理工具我这里就用pnpm了,大家可以根据自己电脑上安装的包管理工具去选择,选择好回车一下去安装,安装时间可能有点长,大家耐心等待一下
安装完成后,使用nest g res auth
去生成restful风格的auth模块,下面是具体操作
然后我们找到auth.controllert.ts
和auth.service.ts
文件,把这些方法先去除掉,移除完成后长下面这样
由于咱们要去注册用户,肯定是需要一张用户的表,接着咱们先去连接数据库,我这里使用mysql数据库,Nest提供了与现成的 TypeORM与@nestjs/typeorm的紧密集成,我这里也是使用TypeORM,对于数据库的安装,网上也是有很多教程,我这里就不具体去讲了。
咱们先去安装这三个包
pnpm install --save @nestjs/typeorm typeorm mysql2
然后在app.module.ts
文件去配置连接mysql
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AuthModule } from './auth/auth.module';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [AuthModule, TypeOrmModule.forRoot({
type: 'mysql', // 数据库类型
host: 'localhost', // 主机名
port: 3306, // 端口
username: 'root', // 用户名
password: '123456', // 密码
database: 'nestjs', // 数据库名称
synchronize: true,
retryDelay: 500, //重试连接数据库间隔
retryAttempts: 10,//重试连接数据库的次数
autoLoadEntities: true, //如果为true,将自动加载实体 forFeature()方法注册的每个实体都将自动添加到配置对象的实体数组中
}),],
controllers: [AppController],
providers: [AppService],
})
export class AppModule { }
接着在auth.entity.ts
文件中去写关于用户表的配置,我这里就做三个字段id,username,password
import { Column, PrimaryGeneratedColumn } from "typeorm";
@Entity()
export class NV_Users {
// id为主键并且自动递增
@PrimaryGeneratedColumn()
id:number
@Column()
username:string
@Column()
password:string
}
然后使用TypeOrmModule.forFeature()
方法注册该表,这样我们可以使用@InjectRepository()
装饰器将NV_UsersRepository
注入到auth.service.ts
中。
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { NV_Users } from './entities/auth.entity';
@Module({
imports: [TypeOrmModule.forFeature([NV_Users])],
controllers: [AuthController],
providers: [AuthService]
})
export class AuthModule { }
注入NV_UsersRepository
到auth.service.ts
中。
import { Injectable } from '@nestjs/common';
import { CreateAuthDto } from './dto/create-auth.dto';
import { UpdateAuthDto } from './dto/update-auth.dto';
import { NV_Users } from './entities/auth.entity';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
@Injectable()
export class AuthService {
constructor(@InjectRepository(NV_Users) private readonly user:Repository<NV_Users>){}
}
接着在auth.controller.ts
去写对应的方法,去调用auth.service.ts
中对应的方法。
下面是auth.controller.ts
文件代码
import { Controller, Post, Body } from '@nestjs/common';
import { AuthService } from './auth.service';
import { CreateAuthDto } from './dto/create-auth.dto';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) { }
// 注册
@Post("/signup")
signup(@Body() signupData: CreateAuthDto) {
return this.authService.signup(signupData)
}
// 登录
@Post("/login")
login(@Body() loginData: CreateAuthDto) {
return this.authService.login(signupData)
}
}
写了这么多,咱们先跑起来(pnpm run start:dev
)看看nv_users
这张表有没有创建,我这里推荐大家去装一个vscode插件
装了之后vscode左边会多一个这个东西,如果没有,重启vscode即可
如果有Navicat
这个软件也是可以的,下面我们看下我们创建的表:
这里也是创建好了 Navicat中长这样,也是非常的像。
然后我们先简单写下auth.service.ts
中的代码
import { Injectable } from '@nestjs/common';
import { CreateAuthDto } from './dto/create-auth.dto';
import { NV_Users } from './entities/auth.entity';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
@Injectable()
export class AuthService {
constructor(@InjectRepository(NV_Users) private readonly user: Repository<NV_Users>) { }
// 注册
signup(signupData: CreateAuthDto) {
console.log(signupData);
return "注册成功"
}
// 登录
login(loginData: CreateAuthDto) {
console.log(loginData);
return "登录成功"
}
}
接着就是来测试下接口有没有问题,这里我使用apifox
这个软件,当然其他接口测试软件也是可以的,比如postman等等。nestjs默认端口是3000,可以在main.ts去修改,我这里就使用默认的了,然后接口路径就是auth.controller.ts
中拼接起来,比如我这里注册接口就是/auth/signup
,其他同理。
接口测试没问题,接着就是写业务代码了,写业务代码前需要安装几个包:
pnpm i bcryptjs // 这个是对用户密码进行加密的
pnpm i @nestjs/jwt // 用于生成token
其中也是用到了几个方法:
bcryptjs.hashSync()
方法是对用户密码进行加密用的,hashSync()
方法第一个参数为用户密码,第二个为密码盐(简单理解为加密的程度)。
bcryptjs.compareSync()
方法是对用户的密码和加密后的密码进行比较的,如果正确返回true,错误则返回fasle,第一个参数为登录时用户输入的密码,第二个为查出来的加密后的密码。这里提一下,加密密码的方法是不可逆的,也就是没有解密的功能,这样做也是保证用户的安全。
JwtService.sign()
方法即生成token的方法,参数可传入我们想传入给前端的用户对象,这样前端可以把token拿去解密,拿到用户对象,切记不能将密码传入。
我们先新建一个关于token相关的配置文件constants.ts
export const jwtConstants = {
secret: "leeKey", // 密钥
expiresIn: "60s" // token有效时间
}
接着在auth.module.ts
中配置
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { NV_Users } from './entities/auth.entity';
import { JwtModule } from '@nestjs/jwt';
import { jwtConstants } from "./constants"
@Module({
imports: [TypeOrmModule.forFeature([NV_Users]), JwtModule.register({
secret: jwtConstants.secret,
signOptions: { expiresIn: jwtConstants.expiresIn }
})],
controllers: [AuthController],
providers: [AuthService]
})
export class AuthModule { }
登录注册我这里就一次性写完了,代码看下面👇
import { BadRequestException, Injectable } from '@nestjs/common';
import { CreateAuthDto } from './dto/create-auth.dto';
import { NV_Users } from './entities/auth.entity';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import * as bcryptjs from "bcryptjs"
import { JwtService } from "@nestjs/jwt"
@Injectable()
export class AuthService {
constructor(
@InjectRepository(NV_Users) private readonly user: Repository<NV_Users>,
private readonly JwtService: JwtService
) { }
// 注册
async signup(signupData: CreateAuthDto) {
const findUser = await this.user.findOne({
where: { username: signupData.username }
})
if (findUser && findUser.username === signupData.username) return "用户已存在"
// 对密码进行加密处理
signupData.password = bcryptjs.hashSync(signupData.password, 10)
await this.user.save(signupData)
return "注册成功"
}
// 登录
async login(loginData: CreateAuthDto) {
const findUser = await this.user.findOne({
where: { username: loginData.username }
})
// 没有找到
if (!findUser) return new BadRequestException("用户不存在")
// 找到了对比密码
const compareRes: boolean = bcryptjs.compareSync(loginData.password, findUser.password)
// 密码不正确
if (!compareRes) return new BadRequestException("密码不正确")
const payload = { username: findUser.username }
return {
access_token: this.JwtService.sign(payload),
msg: "登录成功"
}
}
}
接着我们注册两个用户试一试
也是没有问题,接着测试登录,也是正确返回token
以上是关于登录注册功能,下面我们来看看身份验证这一块的功能,如何完成。
首先我们新建文件/common/public.decorator.ts
,创建自定义装饰器
import { SetMetadata } from "@nestjs/common";
export const IS_PUBLIC_KEY = 'isPublic'
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
接着我们安装这几个包
pnpm install --save @nestjs/passport passport-jwt
pnpm install @types/passport-jwt --save-dev
新建jwt-auth.grard.ts
文件,用于全局守卫,将未携带token的接口进行拦截,代码👇
import { ExecutionContext, Injectable } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport"
import { Reflector } from "@nestjs/core";
import { Observable } from "rxjs"
import { IS_PUBLIC_KEY } from "src/common/public.decorator";
@Injectable()
export class jwtAuth extends AuthGuard("jwt") {
constructor(private reflector: Reflector) {
super()
}
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass()
])
console.log(isPublic, "isPublic");
if (isPublic) return true
return super.canActivate(context)
}
}
新建jwt-auth.strategy.ts
,该文件为验证策略,也就是验证前端请求头中携带的token,代码👇
import { Injectable } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { ExtractJwt, Strategy } from "passport-jwt";
import { jwtConstants } from "./constants";
export interface JwtPayload {
username: string
}
@Injectable()
// 验证请求头中的token
export default class JwtAuthStrategy extends PassportStrategy(Strategy, "jwt") {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: jwtConstants.secret
})
}
async validate(payload: JwtPayload) {
console.log(payload.username);
const { username } = payload
return {
username
}
}
}
接着在auth.module.ts
的providers
中配置JwtAuthStrategy
,代码👇
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { NV_Users } from './entities/auth.entity';
import { JwtModule } from '@nestjs/jwt';
import { jwtConstants } from "./constants"
import { JwtAuthStrategy } from "./jwt-auth.strategy"
@Module({
imports: [TypeOrmModule.forFeature([NV_Users]), JwtModule.register({
secret: jwtConstants.secret,
signOptions: { expiresIn: jwtConstants.expiresIn }
})],
controllers: [AuthController],
providers: [AuthService, JwtAuthStrategy]
})
export class AuthModule { }
最后在app.module.ts
将其注册为全局守卫
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AuthModule } from './auth/auth.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { APP_GUARD } from '@nestjs/core';
import { JwtAuthGuard } from './auth/jwt-auth.grard';
@Module({
imports: [AuthModule, TypeOrmModule.forRoot({
type: 'mysql', // 数据库类型
host: 'localhost', // 主机名
port: 3306, // 端口
username: 'root', // 用户名
password: '123456', // 密码
database: 'nestjs', // 数据库名称
synchronize: true,
retryDelay: 500, //重试连接数据库间隔
retryAttempts: 10,//重试连接数据库的次数
autoLoadEntities: true, //如果为true,将自动加载实体 forFeature()方法注册的每个实体都将自动添加到配置对象的实体数组中
}),],
controllers: [AppController],
// 注册为全局守卫
providers: [AppService, {
provide: APP_GUARD,
useClass: JwtAuthGuard
}],
})
export class AppModule { }
以上配置完成后,再次请求注册接口,返回401,表示我们没有权限
然后我们给通用接口(注册和登录接口)都加上@Public装饰器后
import { Controller, Post, Body } from '@nestjs/common';
import { AuthService } from './auth.service';
import { CreateAuthDto } from './dto/create-auth.dto';
import { Public } from 'src/common/public.decorator';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) { }
// 注册
@Public()
@Post("/signup")
// @Public()
signup(@Body() signupData: CreateAuthDto) {
return this.authService.signup(signupData)
}
// 登录
@Public()
@Post("/login")
login(@Body() loginData: CreateAuthDto) {
return this.authService.login(loginData)
}
}
再去请求,发现没有问题
以上就是关于全局身份验证的内容了,这样做之后,我们只需要给你通用接口加上@Pulic()装饰器即可,随后我们需要验证的接口就不用一个一个去加守卫了。
以上就是本次文章的全部内容,创作不易,如有帮助,记得点赞收藏,感谢您的观看。