如何在TypeORM与NestJS应用中实现用户密码自动哈希

来源:AI教程网作者:上海SEO公司头衔:草根站长
导读:本期聚焦于小伙伴创作的《如何在TypeORM与NestJS应用中实现用户密码自动哈希》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《如何在TypeORM与NestJS应用中实现用户密码自动哈希》有用,将其分享出去将是对创作者最好的鼓励。

在NestJS项目中使用TypeORM作为ORM工具时,用户密码的安全存储是核心需求之一。如果直接将明文密码存入数据库,一旦数据库泄露,用户账号将面临极大风险。通过自动哈希处理,可以在数据持久化前自动将明文密码转换为不可逆的哈希值,既减少手动处理的重复代码,也能避免遗漏哈希步骤导致的安全问题。

如何在TypeORM与NestJS应用中实现用户密码自动哈希

环境准备与依赖安装

首先需要确保项目已经初始化了NestJS和TypeORM,然后安装密码哈希所需的bcrypt库以及TypeScript类型定义:

npm install bcrypt
npm install -D @types/bcrypt

封装bcrypt哈希工具

为了统一哈希逻辑,避免重复代码,我们可以封装一个密码处理工具类,提供哈希和校验两个核心方法:

import * as bcrypt from 'bcrypt';

export class PasswordUtil {
  // 盐轮数,数值越高哈希越安全但性能消耗越大,通常取10-12
  private static readonly SALT_ROUNDS = 10;

  /**
   * 对明文密码进行哈希处理
   * @param password 明文密码
   * @returns 哈希后的密码字符串
   */
  static async hashPassword(password: string): Promise<string> {
    return bcrypt.hash(password, this.SALT_ROUNDS);
  }

  /**
   * 校验明文密码和哈希密码是否匹配
   * @param password 明文密码
   * @param hashedPassword 哈希后的密码
   * @returns 是否匹配
   */
  static async comparePassword(password: string, hashedPassword: string): Promise<boolean> {
    return bcrypt.compare(password, hashedPassword);
  }
}

定义用户实体

创建用户实体类,使用TypeORM的装饰器定义表结构,这里需要添加密码字段,同时排除密码字段在默认查询中的返回,避免敏感信息泄露:

import { Entity, PrimaryGeneratedColumn, Column, BeforeInsert, BeforeUpdate } from 'typeorm';

@Entity('users')
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ unique: true })
  username: string;

  @Column()
  password: string;

  @Column({ default: true })
  isActive: boolean;

  @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
  createdAt: Date;

  // 临时存储明文密码,用于哈希处理后赋值给password字段
  transientPassword: string;

  @BeforeInsert()
  @BeforeUpdate()
  async hashPasswordBeforeSave() {
    // 如果传入了明文密码才进行哈希处理
    if (this.transientPassword) {
      this.password = await PasswordUtil.hashPassword(this.transientPassword);
      // 处理完成后清空临时字段
      this.transientPassword = undefined;
    }
  }
}

使用TypeORM实体订阅者实现自动哈希

除了在实体内部使用生命周期钩子,TypeORM还提供了实体订阅者机制,可以在实体插入、更新等事件触发时执行统一逻辑,更适合全局通用的处理逻辑:

创建密码哈希订阅者

import { EntitySubscriberInterface, EventSubscriber, InsertEvent, UpdateEvent } from 'typeorm';
import { User } from '../entities/user.entity';
import { PasswordUtil } from '../utils/password.util';

@EventSubscriber()
export class UserPasswordSubscriber implements EntitySubscriberInterface<User> {
  /**
   * 指定该订阅者监听的实体
   */
  listenTo() {
    return User;
  }

  /**
   * 插入前触发,处理密码哈希
   */
  async beforeInsert(event: InsertEvent<User>) {
    if (event.entity.password) {
      event.entity.password = await PasswordUtil.hashPassword(event.entity.password);
    }
  }

  /**
   * 更新前触发,处理密码哈希
   */
  async beforeUpdate(event: UpdateEvent<User>) {
    const updatedPassword = event.entity?.password;
    // 仅当密码字段被修改时才进行哈希
    if (updatedPassword && event.databaseEntity.password !== updatedPassword) {
      event.entity.password = await PasswordUtil.hashPassword(updatedPassword);
    }
  }
}

注册订阅者

在TypeORM的配置中注册该订阅者,确保订阅者能正常生效:

import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import { UserPasswordSubscriber } from './subscribers/user-password.subscriber';

export const typeOrmConfig: TypeOrmModuleOptions = {
  type: 'mysql', // 根据使用的数据库调整
  host: '127.0.0.1',
  port: 3306,
  username: 'root',
  password: '123456',
  database: 'test_db',
  entities: [__dirname + '/../**/*.entity{.ts,.js}'],
  subscribers: [UserPasswordSubscriber], // 注册订阅者
  synchronize: true, // 开发环境可开启,生产环境建议关闭
};

用户服务与控制器实现

创建用户相关的服务和控制器,测试自动哈希功能是否生效:

用户服务

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
  ) {}

  /**
   * 创建用户
   */
  async createUser(username: string, password: string): Promise<User> {
    const user = this.userRepository.create({ username, password });
    return this.userRepository.save(user);
  }

  /**
   * 根据用户名查询用户
   */
  async findByUsername(username: string): Promise<User> {
    return this.userRepository.findOne({ where: { username } });
  }
}

用户控制器

import { Controller, Post, Body, Get, Param } from '@nestjs/common';
import { UserService } from './user.service';

@Controller('users')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Post('register')
  async register(
    @Body('username') username: string,
    @Body('password') password: string,
  ) {
    return this.userService.createUser(username, password);
  }

  @Get(':username')
  async getUser(@Param('username') username: string) {
    const user = await this.userService.findByUsername(username);
    if (!user) {
      return { message: '用户不存在' };
    }
    // 返回时排除密码字段
    const { password, ...result } = user;
    return result;
  }
}

功能测试

启动项目后,通过接口测试工具发送注册请求:

curl -X POST http://127.0.0.1:3000/users/register 
  -H "Content-Type: application/json" 
  -d '{"username":"testuser","password":"123456"}'

查看数据库中的用户表,会发现password字段存储的是以$2b$开头的bcrypt哈希字符串,而不是明文123456。再通过查询接口获取用户信息,返回的结果中不会包含密码字段,说明自动哈希和敏感字段排除都生效了。

注意事项

  • bcrypt的盐轮数不要设置过高,否则会影响性能,通常10-12是兼顾安全和性能的取值
  • 更新用户密码时,需要确保传入的是明文密码,订阅者才会自动进行哈希处理,如果直接传入哈希后的字符串,会导致二次哈希
  • 生产环境中不要开启TypeORM的synchronize选项,避免表结构自动变更导致数据风险
  • 除了密码哈希,还需要配合HTTPS传输、登录失败限制等措施,全面提升应用安全性

TypeORMNestJS密码哈希bcrypt实体订阅者修改时间:2026-06-15 10:45:20

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。