Mysql+Prisma+Nest 实现JWT登录注册

963 阅读5分钟

Mysql+Prisma+Nest 实现JWT登录注册

一 prisma准备

创建项目

nest new login_project

创建一个database

image.png 使用prisma连接数据库

npm install prisma --save-dev  //安装prisma
​
npx prisma init --datasource-provider mysql   //初始化prisma

修改.env文件

DATABASE_URL="mysql://root:root123@localhost:3306/login_project_db"

创建一个user表

  • 主键 用户id
  • phone 用户账号
  • password用户密码
  • 创建时间
  • 更新时间
// prisma\schema.prisma
model User {
  id         Int      @id @default(autoincrement())
  phone      String   @unique
  password   String
  createTime DateTime @default(now())
  updateTime DateTime @updatedAt()
}

生成表结构

npx prisma migrate dev --name creat_user

image.png

看一下数据库

image.png

可以了,接下来把prisma注册成全局模块,这样别的模块想使用直接注入service里面就可以了

创建一个prisma模块和prisma服务

nest g mo prisma --no-spec     //创建模块
​
nest g s prisma --no-spec  //创建服务

在prisma服务里面继承PrismaClient

import { Injectable } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
​
@Injectable()
export class PrismaService extends PrismaClient { } // 继承PrismaClient

然后把prisma定义成全局模块,记得一定要暴露出prisma服务

import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
​
​
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService] // 导出 PrismaService 以便在其他模块中使用
})
export class PrismaModule { }

欧克差不多了,接下来别的service要使用直接注入就好,就可以直接使用了

二 Nest登录注册准备

初始化配置

nest g mo auth --no-spec     //创建一个登录注册的模块
​
nest g s auth --no-spec  //创建服务
​
nest g co auth --no-spec  //创建控制器

--no-spec 就是不要测试文件

//  src\auth\auth.controller.ts
import { Body, Controller, Post } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LoginDto } from './dto/login.dto';
import { RegisterDto } from './dto/register.dto';
​
@Controller('auth')
export class AuthController {
​
  constructor(
    private readonly authService: AuthService
  ) { }
​
  // 登录
  @Post('login')
  login(@Body() loginDto: LoginDto) {
    return this.authService.login(loginDto);
  }
​
​
  // 注册
  @Post('register')
  register(@Body() registerDto: RegisterDto) {
    return this.authService.register(registerDto);
  }
​
}
//  src\auth\auth.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from 'src/prisma/prisma.service';
import { LoginDto } from './dto/login.dto';
import { RegisterDto } from './dto/register.dto';
​
@Injectable()
export class AuthService {
  constructor(
    private readonly prisma: PrismaService
  ) { }
​
  login(loginDto: LoginDto) {
    return loginDto
  }
​
  register(registerDto: RegisterDto) {
    return registerDto
  }
​
}

dto文件是验证前端传递过来的参数的,这里不验证

//  src\auth\dto\login.dto.ts
export class LoginDto {
  phone: string;
  password: string;
}
​
//  src\auth\dto\register.dto.ts
export class RegisterDto {
  phone: string;
  password: string;
}

使用Apifox试一下

入口文件监听的是3000端口

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
​
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

image.png

试一下接口

image.png

接口完善

  • 要给用户密码加密
npm i argon2  //加密

使用方法

import { hash, verify } from "argon2"
  • hash 加密
  • verify 验证

接口完善

  • register 是把用户信息存到数据库里
  • login 是根据 phone和 password 取匹配是否有这个 user
完善注册
  • 拿到前端数据
  • 判断数据库手机号有没有一样的,有返回错误 没有继续
  • 存储数据到数据库
 async register(registerDto: RegisterDto) {
    // 验证用户是否存在
    const user = await this.prisma.user.findFirst({
      where: {
        phone: registerDto.phone
      }
    })
    // 存在则抛出异常
    if (user) {
      throw new HttpException('用户已存在', 200)
    }
​
    // 不存在则创建用户
    const newUser = await this.prisma.user.create({
      data: {
        phone: registerDto.phone,
        password: await hash(registerDto.password),        
     }
    })
​
    return newUser
  }

第一次注册的情况

image.png

注册相同的手机号肯定就不行了

image.png

完善登录
  • 登录拿到前端传递过来的数据
  • 查询数据库有没有对应手机号 有继续 没有失败
  • 对比传递过来的密码是否验证通过 通过给成功 对比失败 密码输入错误
 async login(loginDto: LoginDto) {
    const user = await this.prisma.user.findFirst({
      where: {
        phone: loginDto.phone,
      }
    })
    if (user) {
      const isPasswordCorrect = await verify(user.password, loginDto.password)
      if (isPasswordCorrect) {
        return user
      } else {
        throw new HttpException('密码错误', 200)
      }
    } else {
      throw new HttpException('用户不存在', 200)
    }
    return loginDto
  }

登录没有数据库对应手机号的

image.png

密码不对的

image.png

通过的

image.png

JWT配置

  • 登录或者注册成功,我们都应该返回token

  • 前端存一份,以后请求别的接口的时候,带上,

    • 验证通过就可以请求接口数据
    • 验证失败就抛出错误,不返回数据
npm install @nestjs/jwt

AppModule 里引入 JwtModule

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { PrismaModule } from './prisma/prisma.module';
import { AuthModule } from './auth/auth.module';
import { JwtModule } from '@nestjs/jwt';
​
@Module({
  imports: [PrismaModule, AuthModule,
    JwtModule.register({  // 注册jwt模块
      global: true,  // 全局使用
      secret: 'dehua',  // 秘钥
      signOptions: { // 签名配置
        expiresIn: '7d', // 过期时间
      },
    })],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule { }

global:true 声明为全局模块,这样就不用每个模块都引入它了,指定加密密钥,token 过期时间

在我们的auth.service 注入 JwtService

import { BadRequestException, HttpException, Injectable } from '@nestjs/common';
import { PrismaService } from 'src/prisma/prisma.service';
import { LoginDto } from './dto/login.dto';
import { RegisterDto } from './dto/register.dto';
import { hash, verify } from "argon2"
import { JwtService } from '@nestjs/jwt';
​
@Injectable()
export class AuthService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly jwtService: JwtService,
  ) { }
​
  async login(loginDto: LoginDto) {
    const user = await this.prisma.user.findFirst({
      where: {
        phone: loginDto.phone,
      }
    })
    if (user) {
      const isPasswordCorrect = await verify(user.password, loginDto.password)
      if (isPasswordCorrect) {
        const token = await this.jwtService.signAsync({
          id: user.id,
          phone: user.phone
        })
        return {
          code: 200,
          data: token,
          mag: "登录成功"
        }
      } else {
        throw new HttpException('密码错误', 200)
      }
    } else {
      throw new HttpException('用户不存在', 200)
    }
    return loginDto
  }
​
  async register(registerDto: RegisterDto) {
    // 验证用户是否存在
    const user = await this.prisma.user.findFirst({
      where: {
        phone: registerDto.phone
      }
    })
    // 存在则抛出异常
    if (user) {
      throw new HttpException('用户已存在', 200)
    }
​
    // 不存在则创建用户
    const newUser = await this.prisma.user.create({
      data: {
        phone: registerDto.phone,
        password: await hash(registerDto.password),
      }
    })
    const token = await this.jwtService.signAsync({
      id: newUser.id,
      phone: newUser.phone
    })
​
    return {
      code: 200,
      data: token,
      mag: "登录成功"
    }
  }
​
}
​

这样返回的就是token了

image.png

接下来前端存储一份,请求头上携带就可以了

接口要token验证通过之后,才可以返回数据

我们写测试接口试一下

  • 创建一个完整模块资源 rest api 增删改查都有
nest g res article

image.png

现在都可以查,不管有没有token,不管验证通过没有

我们现在创建一个Guard(守卫)来限制,满足才可以访问接口

nest g guard login --no-spec --flat

实现 jwt 校验的逻辑

import { JwtService } from '@nestjs/jwt';
import { CanActivate, ExecutionContext, Inject, Injectable, UnauthorizedException } from '@nestjs/common';
import { Request } from 'express';
import { Observable } from 'rxjs';
​
​
interface JwtUserData {
  userId: number;
  username: string;
}
​
declare module 'express' {
  interface Request {
    user: JwtUserData
  }
}
​
@Injectable()
export class LoginGuard implements CanActivate {
  @Inject(JwtService)
  private jwtService: JwtService;
​
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request: Request = context.switchToHttp().getRequest();
​
    const authorization = request.header('authorization') || '';
​
    const bearer = authorization.split(' ');
​
    if (!bearer || bearer.length < 2) {
      throw new UnauthorizedException('登录 token 错误');
    }
​
    const token = bearer[1];
        
    try {
      const info = this.jwtService.verify(token);
      (request as any).user = info
      return true;
    } catch (e) {
      throw new UnauthorizedException('登录 token 失效,请重新登录');
    }
  }
}
​
​

取出 authorization 的 header,验证 token 是否有效,token 有效返回 true,无效的话就返回 UnauthorizedException

应用一下需要验证的接口

 //article.controller.ts@Post()
  @UseGuards(LoginGuard)
  create(@Body() createArticleDto: CreateArticleDto) {
    return this.articleService.create(createArticleDto);
  }

没有带token的情况

image.png

带token,随便输入的token

image.png

正确的token

image.png

怎么获得当前token用户的信息呢

image.png

image.png

以上就是本次文章的全部内容,创作不易,如有帮助,记得点赞收藏,感谢您的观看。