Skip to content

安全编码规范 - 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>;
}
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 --force

7.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应用安全需要多层防护:

  1. XSS防护: 利用React转义、验证输入、消毒HTML
  2. CSRF防护: 使用Token、SameSite Cookie
  3. 认证授权: 安全存储Token、双重验证
  4. 数据安全: 加密敏感数据、HTTPS传输
  5. API安全: 签名、限流、验证
  6. 依赖安全: 审计、更新、SRI
  7. 配置安全: CSP、安全头、HTTPS
  8. 持续监控: 日志、异常追踪、定期审计

安全是一个持续的过程,需要开发团队的持续关注和改进。