You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NestJS结合MySQL使用bcrypt实现登录密码比对问题求助

NestJS登录接口bcrypt密码比对解决方案

问题说明

在NestJS登录接口中,已通过邮箱查询到对应用户,但不知道如何获取数据库中的哈希密码完成bcrypt比对,且未使用TypeORM。

修正后的AuthService代码

import { ConflictException, Injectable, UnauthorizedException } from '@nestjs/common';
import { SignInDto, SignUpDto } from '../dto';
import { execute } from '../mysql';
import * as bcrypt from 'bcrypt';

@Injectable()
export class AuthService {

    // ------SignUp-------
  public async CREATE(Dto: SignUpDto): Promise<any> {
    const [account]: any = await execute(
      `
        SELECT 
            * 
        FROM  
            account 
        WHERE 
            email = ? AND 
            is_active = ? AND  
            is_deleted = ?  
            `,
      [Dto.email.toLowerCase(), 1, 0],
    );

    if (account) {
      throw new ConflictException('Account already exists on this email id.');
    }

    // 先验证两次密码输入一致,无需哈希confirmPassword
    if (Dto.password !== Dto.confirmPassword) {
      throw new ConflictException('Password and confirm password do not match.');
    }

    Dto.email = Dto.email.toLowerCase();
    // 仅哈希需要存储的password,存储到数据库
    Dto.password = await bcrypt.hash(Dto.password, 12);
    // 移除confirmPassword,避免存入数据库
    const { confirmPassword, ...data } = Dto;

    return await execute(`INSERT INTO account SET ?`, [data]);
}

// -------SignIn---------
public async GET(Dto: SignInDto): Promise<any> {
    const [isExist]: any = await execute(
      `
        SELECT 
            * 
        FROM 
            account 
        WHERE 
            email = ? AND 
            is_active = ? AND 
            is_deleted = ?
        `,
      [Dto.email.toLowerCase(), 1, 0],
    );

    if (!isExist) {
      throw new ConflictException('Account does not exist.');
    }

    // 比对用户输入的明文密码和数据库中的哈希密码
    const isPasswordValid = await bcrypt.compare(Dto.password, isExist.password);
    if (!isPasswordValid) {
      throw new UnauthorizedException('Invalid password.');
    }

    return {
      id: isExist.id,
      email: isExist.email
    };
  }
}

关键修改点

  • 注册逻辑优化:
    • 先验证password和confirmPassword是否一致,不匹配直接抛出异常
    • 仅哈希需要存储的password,无需哈希confirmPassword,且移除该字段避免存入数据库
  • 登录密码比对逻辑:
    • 查询到用户后,调用bcrypt.compare(明文密码, 数据库哈希密码)验证密码有效性
    • 密码不匹配时抛出UnauthorizedException(更符合HTTP语义的异常类型)
    • 调整异常抛出的位置,确保逻辑顺序正确

修正后的AuthController代码(补充异常处理)

import { Controller, Post, Body, HttpCode, HttpStatus, Res } from '@nestjs/common';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { SignUpDto, SignInDto } from '../dto';
import { Response } from 'express';
import { AuthService } from './auth.service';

@Controller('auth')
@ApiTags('Auth')
export class AuthController {
    constructor(private readonly _authService: AuthService) { }

    @Post('/sign-up')
    @HttpCode(HttpStatus.OK)
    @ApiResponse({ status: HttpStatus.OK, description: 'Registration completed successfully.' })
    @ApiResponse({ status: HttpStatus.CONFLICT, description: 'Account exists or password mismatch.' })
    @ApiOperation({ summary: 'User SignUp' })
    public async SIGNUP(@Res() res: Response, @Body() Dto: SignUpDto): Promise<any> {
        try {
            await this._authService.CREATE(Dto);
            return res.status(HttpStatus.OK).json({ 
                status: HttpStatus.OK, 
                message: `Registration completed successfully.` 
            });
        } catch (error) {
            return res.status(error.getStatus()).json({ 
                status: error.getStatus(), 
                message: error.message 
            });
        }
    }

    @Post('/sign-in')
    @HttpCode(HttpStatus.OK)
    @ApiResponse({ status: HttpStatus.OK, description: 'Login successful.' })
    @ApiResponse({ status: HttpStatus.CONFLICT, description: 'Account does not exist.' })
    @ApiResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Invalid password.' })
    @ApiOperation({ summary: 'User SignIn' })
    public async SIGNIN(@Res() res: Response, @Body() Dto: SignInDto): Promise<any> {
        try {
            const result = await this._authService.GET(Dto);
            return res.status(HttpStatus.OK).json({ 
                status: HttpStatus.OK, 
                data: result, 
                message: `Login successful` 
            });
        } catch (error) {
            return res.status(error.getStatus()).json({ 
                status: error.getStatus(), 
                message: error.message 
            });
        }
    }
}

补充说明

  • 使用UnauthorizedException替代ConflictException处理密码错误更符合RESTful规范,对应HTTP 401状态码
  • 注册时不要存储confirmPassword,仅用它验证两次输入一致即可
  • bcrypt.compare会自动处理哈希的盐值验证,无需手动提取盐值

内容的提问来源于stack exchange,提问作者demi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 14:10:28