登录注册密码加密:从踩坑经历聊核心实现

4 阅读5分钟

【TS 版】登录注册密码加密:从踩坑经历聊核心实现

做登录注册的密码加密时,我踩过不少坑 —— 比如明文传密码被测试怼、存 SHA256 值被安全同事批、接口不限流差点被暴力破解… 今天用 React + Node.js/Express(TS 版),把 “能落地的密码加密方案” 和 “避坑指南” 掰开揉碎讲清楚。

一、前端 TS 核心实现(React)

1. 加密 & 校验工具(src/utils/crypto.ts)

先明确:前端加密的核心目的是「不让密码以明文出现在网络传输 / 开发者工具里」,不是替代后端加密(这点千万别搞反!)。

typescript

运行

import CryptoJS from 'crypto-js';

// 前端SHA256辅助加密(踩坑血泪:第一次做项目直接传明文,打开Network面板密码看得一清二楚)
export const encryptPwd = (pwd: string): string => {
  // trim()是小细节:避免用户手滑输了前后空格,导致加密结果和预期不一致
  return CryptoJS.SHA256(pwd.trim()).toString(CryptoJS.enc.Hex);
};

// 密码参数的TS类型约束(踩坑:曾没写类型,传参时把password写成pwd,上线前才发现报错)
export interface AuthParams {
  username: string;
  password: string;
}

2. 提交核心逻辑

这里有个关键前提:全程必须用 HTTPS!如果是 HTTP,哪怕前端加密了,黑客也能抓包篡改数据,所有加密都白搭。

typescript

运行

import { encryptPwd, AuthParams } from '@/utils/crypto';

// 登录/注册提交(封装成通用方法,区分注册/登录场景)
const handleSubmit = async (isRegister = false) => {
  // 模拟用户输入(真实场景替换成表单的state值,比如usernameRef.current.value)
  const params: AuthParams = {
    username: 'test123',
    password: encryptPwd('Test123!'), // 传的是哈希值,不是明文"Test123!"
  };

  try {
    await fetch(`/api/${isRegister ? 'register' : 'login'}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' }, // 别漏这个,后端会解析不了参数
      body: JSON.stringify(params),
    });
    alert(isRegister ? '注册成功' : '登录成功');
  } catch (err) {
    alert('网络出错啦,稍后再试~');
  }
};

二、后端 TS 核心实现(Node.js/Express)

前端的 SHA256 只是 “第一道防护”,真正的安全核心在后端 —— 因为 SHA256 是「不可逆但固定」的(同一个密码加密结果永远一样),黑客有 “彩虹表” 能秒破解;而 bcrypt 的「加盐哈希」是给每个密码加 “专属随机盐”,就算密码一样,加密结果也不同,根本没法用彩虹表破解。

1. bcrypt 加盐哈希工具(src/utils/passwordUtil.ts)

typescript

运行

import bcrypt from 'bcryptjs';

// 后端核心加盐哈希(踩坑:我曾直接存前端的SHA256值,安全评审时被打回——彩虹表能秒破!)
export const hashPassword = async (pwd: string): Promise<string> => {
  // 生成随机盐值(10是行业常用复杂度,数值越高越安全但越耗性能,8-12最合适)
  const salt = await bcrypt.genSalt(10); 
  // 把前端传的SHA256值 + 随机盐,再次加密(双重防护)
  return bcrypt.hash(pwd, salt);
};

// 密码验证(不用自己写解密逻辑,bcrypt会自动对比)
export const verifyPassword = async (inputPwd: string, storedHash: string): Promise<boolean> => {
  // inputPwd:前端传的加密值;storedHash:数据库存的最终哈希值
  return bcrypt.compare(inputPwd, storedHash);
};

2. 接口核心逻辑

除了加密,还要防暴力破解、防信息泄露,这些细节比加密本身更易踩坑!

typescript

运行

import express from 'express';
import rateLimit from 'express-rate-limit';
import jwt from 'jsonwebtoken';
import { hashPassword, verifyPassword } from '../utils/passwordUtil';

const router = express.Router();

// 接口限流(踩坑:曾没加限流,测试用脚本1秒试100个密码,差点把数据库搞崩)
// 15分钟内最多试10次,超过就锁接口,防暴力破解
router.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 10 }));

// 注册接口
router.post('/register', async (req, res) => {
  try {
    // 核心:前端已经SHA256加密了,后端必须再做bcrypt加盐
    const finalHash = await hashPassword(req.body.password);
    // 数据库只存finalHash!明文、前端SHA256值都别存(就算数据库被拖库,黑客也解不开)
    // 真实项目里这里写:await UserModel.create({ username: req.body.username, password: finalHash })
    res.json({ code: 200, msg: '注册成功' });
  } catch (err) {
    res.json({ code: 500, msg: '注册失败' });
  }
});

// 登录接口
router.post('/login', async (req, res) => {
  try {
    // 真实场景:根据用户名查数据库,拿到存的finalHash
    const storedHash = await UserModel.findOne({ username: req.body.username }).password;
    
    // 对比密码是否匹配
    const isMatch = await verifyPassword(req.body.password, storedHash);
    
    // 模糊提示(踩坑:曾返回“密码错误”,黑客试出“用户名存在”,专门撞库!)
    if (!isMatch) return res.json({ code: 400, msg: '账号或密码错误' });
    
    // 登录成功生成JWT(只存用户ID,绝不存密码相关信息)
    const token = jwt.sign({ uid: '用户唯一ID' }, process.env.JWT_SECRET!, { expiresIn: '2h' });
    res.json({ code: 200, data: { token }, msg: '登录成功' });
  } catch (err) {
    res.json({ code: 500, msg: '登录失败' });
  }
});

三、那些年踩过的坑(90% 开发者都会犯)

用大白话讲清楚每个坑的 “坑点” 和 “怎么避”:

  1. 只做前端加密,后端直接存 SHA256 值坑点:SHA256 是固定哈希,黑客用彩虹表(提前算好的密码 - 哈希对照表)能秒破解;避坑:前端 SHA256 只是辅助,后端必须用 bcrypt 加盐二次加密。
  2. 接口不限流,裸奔接请求坑点:黑客用脚本批量试密码(暴力破解),不仅耗服务器资源,还可能破解弱密码;避坑:加限流(比如 15 分钟 10 次),超过次数直接拒绝请求。
  3. **错误提示太 “精准”**坑点:返回 “密码错误”→ 黑客知道 “这个用户名是存在的”,专门针对这个账号撞库;避坑:统一返回 “账号或密码错误”,让黑客无法判断是用户名错还是密码错。
  4. 不用 HTTPS 传输坑点:哪怕前端加密了,HTTP 传输时黑客能抓包、篡改数据(比如把加密后的密码换成自己的);避坑:全程 HTTPS,这是所有加密的基础,没有 HTTPS = 白忙活。

核心总结

用 3 句大白话总结核心逻辑,好记又好落地:

  1. 前端:用 SHA256 把密码变成 “乱码” 再传,避免明文暴露在开发者工具 / 传输过程中;
  2. 后端:给前端的 “乱码” 再加一层 bcrypt “专属盐” 存库,同时加限流、返模糊提示,防破解 / 信息泄露;
  3. 基础:全程 HTTPS,没有这个,前面所有加密都是纸糊的。

关键点回顾

  1. 前端加密是 “防明文暴露”,后端 bcrypt 加盐才是 “核心安全”,二者缺一不可;
  2. 接口限流、模糊错误提示是 “低成本高收益” 的安全细节,一定要加;
  3. HTTPS 是传输层的底线,所有加密方案都建立在 HTTPS 之上。