Appearance
安全编码规范 - React应用安全最佳实践
本文档详细介绍React应用开发中的安全编码规范和最佳实践,涵盖XSS、CSRF、数据加密等方面。
1. XSS (跨站脚本攻击) 防护
1.1 React自动转义
tsx
// ✅ React自动转义用户输入
function Component({ userInput }) {
// userInput中的HTML标签会被转义
return <div>{userInput}</div>;
}
// ❌ 危险: 使用dangerouslySetInnerHTML
function BadComponent({ htmlContent }) {
return <div dangerouslySetInnerHTML={{ __html: htmlContent }} />;
}
// ✅ 如果必须使用,先消毒
import DOMPurify from 'dompurify';
function SafeComponent({ htmlContent }) {
const clean = DOMPurify.sanitize(htmlContent);
return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}1.2 URL注入防护
tsx
// ❌ 危险: javascript: URL
function BadLink({ url }) {
return <a href={url}>Link</a>; // url可能是 "javascript:alert('XSS')"
}
// ✅ 验证URL
function SafeLink({ url }) {
const isValidUrl = (url: string) => {
try {
const parsed = new URL(url);
return ['http:', 'https:'].includes(parsed.protocol);
} catch {
return false;
}
};
if (!isValidUrl(url)) {
return <span>Invalid URL</span>;
}
return <a href={url} rel="noopener noreferrer">Link</a>;
}1.3 事件处理器注入
tsx
// ❌ 危险: 动态执行代码
function BadButton({ onClick }) {
return <button onClick={() => eval(onClick)}>Click</button>;
}
// ✅ 使用函数引用
function SafeButton({ onClick }) {
return <button onClick={onClick}>Click</button>;
}2. CSRF (跨站请求伪造) 防护
2.1 CSRF Token
tsx
// ✅ 在请求中包含CSRF Token
function useCSRFToken() {
const [token, setToken] = useState<string | null>(null);
useEffect(() => {
// 从Cookie或meta标签获取
const csrfToken = document.querySelector<HTMLMetaElement>(
'meta[name="csrf-token"]'
)?.content;
setToken(csrfToken || null);
}, []);
return token;
}
function Form() {
const csrfToken = useCSRFToken();
const handleSubmit = async (data: FormData) => {
await fetch('/api/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken || ''
},
body: JSON.stringify(data)
});
};
return <form onSubmit={handleSubmit}>...</form>;
}2.2 SameSite Cookie
typescript
// 服务端设置
const cookieOptions = {
httpOnly: true,
secure: true,
sameSite: 'Strict' // 或 'Lax'
};3. 认证和授权
3.1 JWT Token安全
tsx
// ✅ 安全存储Token
// 使用httpOnly Cookie (推荐)
const authService = {
login: async (credentials) => {
const response = await fetch('/api/login', {
method: 'POST',
credentials: 'include', // 包含Cookie
body: JSON.stringify(credentials)
});
return response.json();
},
logout: async () => {
await fetch('/api/logout', {
method: 'POST',
credentials: 'include'
});
}
};
// ❌ 避免: localStorage存储敏感Token
// localStorage.setItem('token', token); // 容易被XSS窃取3.2 权限检查
tsx
// ✅ 前端和后端双重验证
function ProtectedComponent() {
const { user, hasPermission } = useAuth();
// 前端检查
if (!hasPermission('admin')) {
return <AccessDenied />;
}
// 仍需后端验证
return <AdminPanel />;
}
// ✅ 路由级别保护
function PrivateRoute({ children, requiredRole }) {
const { user, loading } = useAuth();
if (loading) {
return <Spinner />;
}
if (!user || user.role !== requiredRole) {
return <Navigate to="/login" />;
}
return children;
}4. 数据验证
4.1 输入验证
tsx
// ✅ 使用验证库
import { z } from 'zod';
const userSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
age: z.number().min(18).max(120)
});
function RegisterForm() {
const [errors, setErrors] = useState({});
const handleSubmit = (data) => {
try {
const validated = userSchema.parse(data);
// 提交验证后的数据
submitForm(validated);
} catch (error) {
if (error instanceof z.ZodError) {
setErrors(error.flatten().fieldErrors);
}
}
};
return <form onSubmit={handleSubmit}>...</form>;
}4.2 服务端验证
tsx
// ✅ 前后端双重验证
// 前端验证提升用户体验
// 后端验证确保安全性
// 后端验证示例 (Node.js/Express)
app.post('/api/users', async (req, res) => {
try {
const validated = userSchema.parse(req.body);
const user = await createUser(validated);
res.json(user);
} catch (error) {
res.status(400).json({ error: error.message });
}
});5. 敏感数据处理
5.1 数据加密
tsx
// ✅ 敏感数据加密存储
import CryptoJS from 'crypto-js';
const SECRET_KEY = process.env.REACT_APP_SECRET_KEY;
function encrypt(data: string): string {
return CryptoJS.AES.encrypt(data, SECRET_KEY).toString();
}
function decrypt(ciphertext: string): string {
const bytes = CryptoJS.AES.decrypt(ciphertext, SECRET_KEY);
return bytes.toString(CryptoJS.enc.Utf8);
}
// 使用
function useSecureStorage(key: string) {
const setItem = (value: string) => {
const encrypted = encrypt(value);
localStorage.setItem(key, encrypted);
};
const getItem = (): string | null => {
const encrypted = localStorage.getItem(key);
if (!encrypted) return null;
return decrypt(encrypted);
};
return { setItem, getItem };
}5.2 敏感信息脱敏
tsx
// ✅ 脱敏显示
function maskEmail(email: string): string {
const [name, domain] = email.split('@');
const masked = name.substring(0, 2) + '***' + name.substring(name.length - 1);
return `${masked}@${domain}`;
}
function maskPhone(phone: string): string {
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
}
function maskCreditCard(card: string): string {
return card.replace(/\d(?=\d{4})/g, '*');
}
// 使用
function UserProfile({ user }) {
return (
<div>
<p>Email: {maskEmail(user.email)}</p>
<p>Phone: {maskPhone(user.phone)}</p>
</div>
);
}6. API安全
6.1 请求签名
tsx
// ✅ 对API请求签名
import { sha256 } from 'js-sha256';
function createSignature(data: object, secret: string): string {
const sorted = Object.keys(data)
.sort()
.map(key => `${key}=${data[key]}`)
.join('&');
return sha256(sorted + secret);
}
async function secureRequest(endpoint: string, data: object) {
const timestamp = Date.now();
const signature = createSignature(
{ ...data, timestamp },
process.env.REACT_APP_API_SECRET
);
return fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Signature': signature,
'X-Timestamp': timestamp.toString()
},
body: JSON.stringify(data)
});
}6.2 请求限流
tsx
// ✅ 客户端限流
function useThrottle<T extends (...args: any[]) => any>(
fn: T,
delay: number
): T {
const lastRun = useRef(Date.now());
return useCallback((...args: Parameters<T>) => {
const now = Date.now();
if (now - lastRun.current >= delay) {
lastRun.current = now;
return fn(...args);
}
}, [fn, delay]) as T;
}
function SearchComponent() {
const throttledSearch = useThrottle(performSearch, 1000);
return (
<input onChange={e => throttledSearch(e.target.value)} />
);
}7. 第三方依赖安全
7.1 依赖审计
bash
# 检查已知漏洞
npm audit
# 修复漏洞
npm audit fix
# 强制修复 (可能破坏兼容性)
npm audit fix --force7.2 依赖锁定
json
// package.json
{
"dependencies": {
"react": "18.2.0", // 固定版本
"lodash": "^4.17.21" // 允许小版本更新
}
}7.3 Subresource Integrity
html
<!-- ✅ 使用SRI验证CDN资源 -->
<script
src="https://cdn.example.com/script.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous"
></script>8. Content Security Policy
8.1 CSP配置
html
<!-- ✅ 在HTML中设置CSP -->
<meta
http-equiv="Content-Security-Policy"
content="
default-src 'self';
script-src 'self' 'unsafe-inline' https://trusted-cdn.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' data:;
connect-src 'self' https://api.example.com;
"
/>8.2 Nonce使用
tsx
// ✅ 使用nonce for inline scripts
function InlineScript({ nonce, code }) {
return (
<script nonce={nonce}>
{code}
</script>
);
}9. HTTPS和安全头
9.1 强制HTTPS
typescript
// ✅ 检测并重定向到HTTPS
useEffect(() => {
if (window.location.protocol !== 'https:' && process.env.NODE_ENV === 'production') {
window.location.href = window.location.href.replace('http:', 'https:');
}
}, []);9.2 安全响应头
javascript
// 服务端设置 (Express示例)
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '1; mode=block');
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
res.setHeader('Referrer-Policy', 'no-referrer');
next();
});10. 日志和监控
10.1 安全日志
tsx
// ✅ 记录安全相关事件
const securityLogger = {
logLogin: (userId: string, success: boolean) => {
fetch('/api/security-log', {
method: 'POST',
body: JSON.stringify({
event: 'login',
userId,
success,
timestamp: new Date().toISOString(),
ip: getUserIP()
})
});
},
logAccessDenied: (userId: string, resource: string) => {
fetch('/api/security-log', {
method: 'POST',
body: JSON.stringify({
event: 'access_denied',
userId,
resource,
timestamp: new Date().toISOString()
})
});
}
};10.2 异常监控
tsx
// ✅ 捕获并上报异常
import * as Sentry from '@sentry/react';
Sentry.init({
dsn: process.env.REACT_APP_SENTRY_DSN,
environment: process.env.NODE_ENV,
beforeSend(event, hint) {
// 过滤敏感信息
if (event.request) {
delete event.request.cookies;
delete event.request.headers;
}
return event;
}
});11. 安全检查清单
typescript
const securityChecklist = {
XSS防护: [
'✅ 依赖React自动转义',
'✅ 谨慎使用dangerouslySetInnerHTML',
'✅ 使用DOMPurify消毒HTML',
'✅ 验证URL',
'✅ 避免eval和动态代码执行'
],
认证授权: [
'✅ 使用httpOnly Cookie存储Token',
'✅ 实现CSRF防护',
'✅ 前后端双重权限验证',
'✅ 使用安全的会话管理'
],
数据安全: [
'✅ 前后端双重验证',
'✅ 敏感数据加密',
'✅ 敏感信息脱敏',
'✅ 使用HTTPS传输'
],
API安全: [
'✅ 实现请求签名',
'✅ 请求限流',
'✅ 输入验证',
'✅ 错误处理不泄露信息'
],
依赖安全: [
'✅ 定期审计依赖',
'✅ 使用SRI',
'✅ 锁定依赖版本',
'✅ 及时更新补丁'
],
配置安全: [
'✅ 设置CSP',
'✅ 配置安全响应头',
'✅ 强制HTTPS',
'✅ 安全Cookie设置'
],
监控: [
'✅ 记录安全事件',
'✅ 异常监控',
'✅ 定期安全审计',
'✅ 渗透测试'
]
};12. 总结
React应用安全需要多层防护:
- XSS防护: 利用React转义、验证输入、消毒HTML
- CSRF防护: 使用Token、SameSite Cookie
- 认证授权: 安全存储Token、双重验证
- 数据安全: 加密敏感数据、HTTPS传输
- API安全: 签名、限流、验证
- 依赖安全: 审计、更新、SRI
- 配置安全: CSP、安全头、HTTPS
- 持续监控: 日志、异常追踪、定期审计
安全是一个持续的过程,需要开发团队的持续关注和改进。