废话部分:
宝们,最近再搞一个秘密项目。
后端我打算是用nestjs来做的,刚好我也是变学边做,所以就整理了下这篇文章来记录下怕忘了。
废话部分
首先,咱们去创建一个nestjs
项目,使用nest new xxx
,我这里简单取名为auth
,包管理工具我这里就用npm
了,大家可以根据自己电脑上安装的包管理工具去选择。至于安装速度?dddd咳咳,无非就是换源爬墙嘛,大家都会,就不多说了。
安装完成后,使用nest g res auth
去生成restful
风格的auth模块。
然后我们找到auth.controllert.ts
和auth.service.ts
文件,把这俩里的方法都先删除,我们自己写。
由于咱们要去注册用户,肯定是需要一张用户的表,接着咱们先去连接数据库,我这里使用mysql数据库,Nest提供了与现成的TypeORM
与@nestjs/typeorm
的紧密集成,我这里也是使用TypeORM
。
数据库部分
npm 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)
}
}
写了这么多,咱们先跑起来npm run start:dev
看看nv_users
这张表有没有创建,正常跑起来后会自动创建。
然后我们先简单写下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 "登录成功"
}
}
着就是来测试下接口有没有问题,这里我使用apipost
这个软件,当然其他接口测试软件也是可以的,比如postman
等等。nestjs默认端口是3000,可以在main.ts去修改,我这里就使用默认的了,然后接口路径就是auth.controller.ts
中拼接起来,比如我这里注册接口就是/auth/signup
,其他同理。
接口测试没问题,接着就是写业务代码了,写业务代码前需要安装几个包:
npm i bcryptjs // 这个是对用户密码进行加密的
npm i @nestjs/jwt // 用于生成token
其中也是用到了几个方法:
bcryptjs.hashSync()
方法是对用户密码进行加密用的,hashSync()
方法第一个参数为用户密码,第二个为密码盐(简单理解为加密的程度)。
bcryptjs.compareSync()
方法是对用户的密码和加密后的密码进行比较的,如果正确返回true
,错误则返回false
,第一个参数为登录时用户输入的密码,第二个为查出来的加密后的密码。这里提一下,加密密码的方法是不可逆的,也就是没有解密的功能,这样做也是保证用户的安全。
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: "登录成功"
}
}
}
这时候我们可以再次使用apipost
等软件测试下接口,看看返回值是否正确。
身份验证部分
下面我们来看看身份验证
这一块的功能,如何完成。 首先我们新建文件/common/public.decorator.ts
,创建自定义装饰器
import { SetMetadata } from "@nestjs/common";
export const IS_PUBLIC_KEY = 'isPublic'
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
接着我们安装这几个包
npm install --save @nestjs/passport passport-jwt
npm 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
装饰器就可以请求了。
以上就是关于全局身份验证的内容了,这样做之后,我们只需要给你通用接口加上@Pulic()
装饰器即可,随后我们需要验证的接口就不用一个一个去加守卫了。
评论区