前端安全
为什么你需要前端安全
最近看到一个项目,登录表单直接把密码发送到服务器,没有任何加密。这存在严重的安全隐患。
反面教材
// 反面教材:不安全的登录
// components/LoginForm.jsx
export default function LoginForm() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
// 直接发送明文密码
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }) // 明文密码
});
if (response.ok) {
// 登录成功
}
};
return (
<form onSubmit={handleSubmit}>
<input type="text" value={username} onChange={(e) => setUsername(e.target.value)} placeholder="用户名" />
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="密码" />
<button type="submit">登录</button>
</form>
);
}
*注意:密码在网络传输中是明文,本地存储也是明文,极易被截获。
前端安全的正确姿势
1. 密码加密
使用哈希算法对密码进行加密存储,而非明文。
// 正确姿势:密码加密
// utils/auth.js
import bcrypt from 'bcryptjs';
export async function hashPassword(password) {
const salt = await bcrypt.genSalt(10);
return await bcrypt.hash(password, salt);
}
export async function comparePassword(password, hashedPassword) {
return await bcrypt.compare(password, hashedPassword);
}
// 服务端登录处理
// api/login.js
export default async function handler(req, res) {
const { username, password } = req.body;
const user = await User.findOne({ username });
if (!user) {
return res.status(401).json({ error: '用户不存在' });
}
const isMatch = await comparePassword(password, user.password);
if (!isMatch) {
return res.status(401).json({ error: '密码错误' });
}
// 生成 JWT token
const token = generateToken(user.id);
res.json({ token });
}
2. XSS 防护
使用库净化输入内容并设置内容安全策略(CSP)。
// 正确姿势:防止 XSS
// components/Comment.jsx
import DOMPurify from 'dompurify';
export default function Comment({ content }) {
// 净化 HTML 内容
const sanitizedContent = DOMPurify.sanitize(content);
return (
<div dangerouslySetInnerHTML={{ __html: sanitizedContent }} />
);
}
// 服务器端设置 CSP 头
// next.config.js
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self' 'unsafe-inline' https://trusted-cdn.com"
}
]
}
];
}
};
3. CSRF 防护
在请求中携带并验证 CSRF Token。
// 正确姿势:防止 CSRF
// pages/api/protected.js
import csrf from 'csurf';
const csrfProtection = csrf({ cookie: true });
export default function handler(req, res) {
csrfProtection(req, res, () => {
// 受保护的 API 逻辑
});
}
// 客户端
// components/Form.jsx
export default function Form() {
const [csrfToken, setCsrfToken] = useState('');
useEffect(() => {
// 获取 CSRF token
fetch('/api/csrf-token')
.then(res => res.json())
.then(data => setCsrfToken(data.token));
}, []);
const handleSubmit = async (e) => {
e.preventDefault();
await fetch('/api/protected', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({ data: 'test' })
});
};
return (
<form onSubmit={handleSubmit}>
<input type="hidden" name="_csrf" value={csrfToken} />
{/* 表单内容 */}
</form>
);
}
