工程化与框架系列(33)--前端安全实践指南

发布于:2025-03-15 ⋅ 阅读:(12) ⋅ 点赞:(0)

前端安全实践指南 🔒

引言

前端安全是Web应用开发中的关键环节。本文将深入探讨前端安全实践,包括XSS防护、CSRF防御、内容安全策略等,帮助开发者构建更安全的前端应用。

安全概述

前端安全主要包括以下方面:

  • XSS防护:跨站脚本攻击防御
  • CSRF防御:跨站请求伪造防护
  • CSP配置:内容安全策略
  • 数据加密:敏感信息保护
  • 访问控制:权限管理

安全工具实现

安全管理器

// 安全管理器类
class SecurityManager {
    private static instance: SecurityManager;
    private config: SecurityConfig;
    
    private constructor() {
        this.config = {
            xssFilter: true,
            csrfProtection: true,
            contentSecurityPolicy: true,
            httpsOnly: true,
            maxTokenAge: 3600
        };
        
        this.initialize();
    }
    
    // 获取单例实例
    static getInstance(): SecurityManager {
        if (!SecurityManager.instance) {
            SecurityManager.instance = new SecurityManager();
        }
        return SecurityManager.instance;
    }
    
    // 初始化安全管理器
    private initialize(): void {
        // 配置CSP
        if (this.config.contentSecurityPolicy) {
            this.setupCSP();
        }
        
        // 配置HTTPS
        if (this.config.httpsOnly) {
            this.enforceHTTPS();
        }
        
        // 初始化CSRF保护
        if (this.config.csrfProtection) {
            this.initializeCsrfProtection();
        }
    }
    
    // 设置CSP
    private setupCSP(): void {
        const csp = [
            "default-src 'self'",
            "script-src 'self' 'unsafe-inline' 'unsafe-eval'",
            "style-src 'self' 'unsafe-inline'",
            "img-src 'self' data: https:",
            "font-src 'self'",
            "connect-src 'self'",
            "media-src 'self'",
            "object-src 'none'",
            "frame-src 'self'",
            "worker-src 'self' blob:"
        ].join('; ');
        
        const meta = document.createElement('meta');
        meta.httpEquiv = 'Content-Security-Policy';
        meta.content = csp;
        document.head.appendChild(meta);
    }
    
    // 强制HTTPS
    private enforceHTTPS(): void {
        if (window.location.protocol === 'http:' && 
            window.location.hostname !== 'localhost') {
            window.location.href = window.location.href.replace(
                'http:', 'https:'
            );
        }
    }
    
    // 初始化CSRF保护
    private initializeCsrfProtection(): void {
        this.setupCsrfToken();
        this.interceptXHR();
        this.interceptFetch();
    }
    
    // 设置CSRF Token
    private setupCsrfToken(): void {
        const token = this.generateToken();
        document.cookie = `XSRF-TOKEN=${token}; path=/; secure; samesite=strict`;
        
        const meta = document.createElement('meta');
        meta.name = 'csrf-token';
        meta.content = token;
        document.head.appendChild(meta);
    }
    
    // 拦截XHR请求
    private interceptXHR(): void {
        const originalOpen = XMLHttpRequest.prototype.open;
        const originalSend = XMLHttpRequest.prototype.send;
        const manager = this;
        
        XMLHttpRequest.prototype.open = function(
            method: string,
            url: string,
            ...args: any[]
        ) {
            this._method = method;
            this._url = url;
            originalOpen.apply(this, [method, url, ...args]);
        };
        
        XMLHttpRequest.prototype.send = function(data?: any) {
            if (this._method?.toUpperCase() !== 'GET') {
                const token = manager.getCsrfToken();
                this.setRequestHeader('X-XSRF-TOKEN', token);
            }
            originalSend.call(this, data);
        };
    }
    
    // 拦截Fetch请求
    private interceptFetch(): void {
        const originalFetch = window.fetch;
        const manager = this;
        
        window.fetch = function(
            input: RequestInfo,
            init?: RequestInit
        ): Promise<Response> {
            if (init?.method && init.method.toUpperCase() !== 'GET') {
                const token = manager.getCsrfToken();
                init.headers = {
                    ...init.headers,
                    'X-XSRF-TOKEN': token
                };
            }
            return originalFetch.call(window, input, init);
        };
    }
    
    // 生成Token
    private generateToken(): string {
        const array = new Uint8Array(32);
        crypto.getRandomValues(array);
        return Array.from(array, byte => 
            byte.toString(16).padStart(2, '0')
        ).join('');
    }
    
    // 获取CSRF Token
    private getCsrfToken(): string {
        const meta = document.querySelector('meta[name="csrf-token"]');
        return meta?.getAttribute('content') || '';
    }
    
    // XSS过滤
    sanitizeHTML(html: string): string {
        const div = document.createElement('div');
        div.textContent = html;
        return div.innerHTML;
    }
    
    // 验证输入
    validateInput(
        input: string,
        pattern: RegExp | string
    ): boolean {
        const regex = typeof pattern === 'string' 
            ? new RegExp(pattern) 
            : pattern;
        return regex.test(input);
    }
    
    // 加密数据
    async encryptData(
        data: string,
        password: string
    ): Promise<string> {
        const encoder = new TextEncoder();
        const salt = crypto.getRandomValues(new Uint8Array(16));
        
        const keyMaterial = await crypto.subtle.importKey(
            'raw',
            encoder.encode(password),
            'PBKDF2',
            false,
            ['deriveBits', 'deriveKey']
        );
        
        const key = await crypto.subtle.deriveKey(
            {
                name: 'PBKDF2',
                salt,
                iterations: 100000,
                hash: 'SHA-256'
            },
            keyMaterial,
            {
                name: 'AES-GCM',
                length: 256
            },
            false,
            ['encrypt']
        );
        
        const iv = crypto.getRandomValues(new Uint8Array(12));
        const encryptedContent = await crypto.subtle.encrypt(
            {
                name: 'AES-GCM',
                iv
            },
            key,
            encoder.encode(data)
        );
        
        const encryptedArray = new Uint8Array(encryptedContent);
        const resultArray = new Uint8Array(
            salt.length + iv.length + encryptedArray.length
        );
        
        resultArray.set(salt, 0);
        resultArray.set(iv, salt.length);
        resultArray.set(encryptedArray, salt.length + iv.length);
        
        return btoa(String.fromCharCode(...resultArray));
    }
    
    // 解密数据
    async decryptData(
        encryptedData: string,
        password: string
    ): Promise<string> {
        const decoder = new TextDecoder();
        const encoder = new TextEncoder();
        const data = Uint8Array.from(
            atob(encryptedData),
            c => c.charCodeAt(0)
        );
        
        const salt = data.slice(0, 16);
        const iv = data.slice(16, 28);
        const content = data.slice(28);
        
        const keyMaterial = await crypto.subtle.importKey(
            'raw',
            encoder.encode(password),
            'PBKDF2',
            false,
            ['deriveBits', 'deriveKey']
        );
        
        const key = await crypto.subtle.deriveKey(
            {
                name: 'PBKDF2',
                salt,
                iterations: 100000,
                hash: 'SHA-256'
            },
            keyMaterial,
            {
                name: 'AES-GCM',
                length: 256
            },
            false,
            ['decrypt']
        );
        
        const decryptedContent = await crypto.subtle.decrypt(
            {
                name: 'AES-GCM',
                iv
            },
            key,
            content
        );
        
        return decoder.decode(decryptedContent);
    }
    
    // 生成安全的随机值
    generateSecureRandom(length: number): string {
        const array = new Uint8Array(length);
        crypto.getRandomValues(array);
        return Array.from(array, byte => 
            byte.toString(16).padStart(2, '0')
        ).join('');
    }
    
    // 哈希密码
    async hashPassword(
        password: string,
        salt?: string
    ): Promise<{ hash: string; salt: string }> {
        const encoder = new TextEncoder();
        salt = salt || this.generateSecureRandom(16);
        
        const keyMaterial = await crypto.subtle.importKey(
            'raw',
            encoder.encode(password),
            'PBKDF2',
            false,
            ['deriveBits']
        );
        
        const bits = await crypto.subtle.deriveBits(
            {
                name: 'PBKDF2',
                salt: encoder.encode(salt),
                iterations: 100000,
                hash: 'SHA-256'
            },
            keyMaterial,
            256
        );
        
        const hash = Array.from(new Uint8Array(bits), byte =>
            byte.toString(16).padStart(2, '0')
        ).join('');
        
        return { hash, salt };
    }
    
    // 验证密码
    async verifyPassword(
        password: string,
        hash: string,
        salt: string
    ): Promise<boolean> {
        const result = await this.hashPassword(password, salt);
        return result.hash === hash;
    }
}

// 接口定义
interface SecurityConfig {
    xssFilter: boolean;
    csrfProtection: boolean;
    contentSecurityPolicy: boolean;
    httpsOnly: boolean;
    maxTokenAge: number;
}

// 使用示例
const security = SecurityManager.getInstance();

// XSS防护
const userInput = '<script>alert("xss")</script>';
const safeHtml = security.sanitizeHTML(userInput);

// 输入验证
const email = 'user@example.com';
const isValidEmail = security.validateInput(
    email,
    /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
);

// 数据加密
const sensitiveData = 'secret information';
security.encryptData(sensitiveData, 'password123')
    .then(encrypted => {
        console.log('Encrypted:', encrypted);
        return security.decryptData(encrypted, 'password123');
    })
    .then(decrypted => {
        console.log('Decrypted:', decrypted);
    });

// 密码哈希
security.hashPassword('myPassword123')
    .then(({ hash, salt }) => {
        console.log('Hash:', hash);
        console.log('Salt:', salt);
        
        return security.verifyPassword('myPassword123', hash, salt);
    })
    .then(isValid => {
        console.log('Password valid:', isValid);
    });

安全HTTP客户端

// 安全HTTP客户端类
class SecureHttpClient {
    private baseUrl: string;
    private security: SecurityManager;
    
    constructor(config: SecureHttpConfig) {
        this.baseUrl = config.baseUrl || '';
        this.security = SecurityManager.getInstance();
    }
    
    // 发送GET请求
    async get<T>(
        url: string,
        config?: RequestConfig
    ): Promise<T> {
        return this.request<T>({
            method: 'GET',
            url,
            ...config
        });
    }
    
    // 发送POST请求
    async post<T>(
        url: string,
        data?: any,
        config?: RequestConfig
    ): Promise<T> {
        return this.request<T>({
            method: 'POST',
            url,
            data,
            ...config
        });
    }
    
    // 发送PUT请求
    async put<T>(
        url: string,
        data?: any,
        config?: RequestConfig
    ): Promise<T> {
        return this.request<T>({
            method: 'PUT',
            url,
            data,
            ...config
        });
    }
    
    // 发送DELETE请求
    async delete<T>(
        url: string,
        config?: RequestConfig
    ): Promise<T> {
        return this.request<T>({
            method: 'DELETE',
            url,
            ...config
        });
    }
    
    // 发送请求
    private async request<T>(config: RequestConfig): Promise<T> {
        const url = this.baseUrl + config.url;
        
        // 添加CSRF Token
        const headers = {
            'Content-Type': 'application/json',
            ...config.headers
        };
        
        if (config.method !== 'GET') {
            const token = document.querySelector(
                'meta[name="csrf-token"]'
            )?.getAttribute('content');
            
            if (token) {
                headers['X-XSRF-TOKEN'] = token;
            }
        }
        
        // 加密敏感数据
        let data = config.data;
        if (config.encrypt && data) {
            data = await this.security.encryptData(
                JSON.stringify(data),
                config.encryptKey || ''
            );
        }
        
        try {
            const response = await fetch(url, {
                method: config.method,
                headers,
                body: data ? JSON.stringify(data) : undefined,
                credentials: 'same-origin'
            });
            
            if (!response.ok) {
                throw new Error(response.statusText);
            }
            
            // 解密响应数据
            let result = await response.json();
            if (config.encrypt && result.encrypted) {
                result = JSON.parse(
                    await this.security.decryptData(
                        result.data,
                        config.encryptKey || ''
                    )
                );
            }
            
            return result;
            
        } catch (error) {
            console.error('Request failed:', error);
            throw error;
        }
    }
}

// 接口定义
interface SecureHttpConfig {
    baseUrl?: string;
}

interface RequestConfig {
    method: string;
    url: string;
    data?: any;
    headers?: Record<string, string>;
    encrypt?: boolean;
    encryptKey?: string;
}

// 使用示例
const http = new SecureHttpClient({
    baseUrl: 'https://api.example.com'
});

// 发送普通请求
http.get('/users')
    .then(users => {
        console.log('Users:', users);
    });

// 发送加密请求
http.post(
    '/sensitive-data',
    { secret: 'value' },
    {
        encrypt: true,
        encryptKey: 'secret-key-123'
    }
).then(response => {
    console.log('Response:', response);
});

最佳实践与建议

  1. 输入验证

    • 客户端验证
    • 服务端验证
    • 特殊字符过滤
    • 长度限制
  2. 数据加密

    • 使用HTTPS
    • 敏感数据加密
    • 安全密钥管理
    • 哈希算法选择
  3. 访问控制

    • 身份认证
    • 权限验证
    • 会话管理
    • 令牌验证
  4. 安全配置

    • CSP策略
    • Cookie设置
    • 错误处理
    • 日志记录

总结

前端安全需要考虑以下方面:

  1. XSS和CSRF防护
  2. 数据加密和传输
  3. 访问控制和认证
  4. 安全配置和监控
  5. 漏洞修复和更新

通过全面的安全实践,可以有效降低安全风险。

学习资源

  1. OWASP安全指南
  2. Web安全最佳实践
  3. 加密算法教程
  4. 安全测试工具
  5. 漏洞数据库

如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇

终身学习,共同成长。

咱们下一期见

💻