【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% 开发者都会犯)
用大白话讲清楚每个坑的 “坑点” 和 “怎么避”:
- 只做前端加密,后端直接存 SHA256 值坑点:SHA256 是固定哈希,黑客用彩虹表(提前算好的密码 - 哈希对照表)能秒破解;避坑:前端 SHA256 只是辅助,后端必须用 bcrypt 加盐二次加密。
- 接口不限流,裸奔接请求坑点:黑客用脚本批量试密码(暴力破解),不仅耗服务器资源,还可能破解弱密码;避坑:加限流(比如 15 分钟 10 次),超过次数直接拒绝请求。
- **错误提示太 “精准”**坑点:返回 “密码错误”→ 黑客知道 “这个用户名是存在的”,专门针对这个账号撞库;避坑:统一返回 “账号或密码错误”,让黑客无法判断是用户名错还是密码错。
- 不用 HTTPS 传输坑点:哪怕前端加密了,HTTP 传输时黑客能抓包、篡改数据(比如把加密后的密码换成自己的);避坑:全程 HTTPS,这是所有加密的基础,没有 HTTPS = 白忙活。
核心总结
用 3 句大白话总结核心逻辑,好记又好落地:
- 前端:用 SHA256 把密码变成 “乱码” 再传,避免明文暴露在开发者工具 / 传输过程中;
- 后端:给前端的 “乱码” 再加一层 bcrypt “专属盐” 存库,同时加限流、返模糊提示,防破解 / 信息泄露;
- 基础:全程 HTTPS,没有这个,前面所有加密都是纸糊的。
关键点回顾
- 前端加密是 “防明文暴露”,后端 bcrypt 加盐才是 “核心安全”,二者缺一不可;
- 接口限流、模糊错误提示是 “低成本高收益” 的安全细节,一定要加;
- HTTPS 是传输层的底线,所有加密方案都建立在 HTTPS 之上。