安全最佳实践 #

一、安全概述 #

1.1 安全威胁 #

威胁 说明
数据泄露 敏感数据被窃取
中间人攻击 网络通信被拦截
代码注入 恶意代码执行
反编译 应用被逆向工程
数据篡改 本地数据被修改

1.2 安全层级 #

text
应用层安全
    ├── 代码安全
    ├── 数据安全
    └── 通信安全

平台层安全
    ├── iOS沙盒
    └── Android沙盒

网络层安全
    ├── HTTPS
    └── 证书校验

二、数据加密 #

2.1 加密工具类 #

typescript
// src/utils/encryption.ts

// 使用Web Crypto API
export class Encryption {
    private static algorithm = 'AES-GCM';
    private static keyLength = 256;
    
    // 生成密钥
    static async generateKey(): Promise<CryptoKey> {
        return crypto.subtle.generateKey(
            {
                name: this.algorithm,
                length: this.keyLength
            },
            true,
            ['encrypt', 'decrypt']
        );
    }
    
    // 从密码派生密钥
    static async deriveKey(password: string, salt: Uint8Array): Promise<CryptoKey> {
        const encoder = new TextEncoder();
        const keyMaterial = await crypto.subtle.importKey(
            'raw',
            encoder.encode(password),
            'PBKDF2',
            false,
            ['deriveBits', 'deriveKey']
        );
        
        return crypto.subtle.deriveKey(
            {
                name: 'PBKDF2',
                salt,
                iterations: 100000,
                hash: 'SHA-256'
            },
            keyMaterial,
            { name: this.algorithm, length: this.keyLength },
            false,
            ['encrypt', 'decrypt']
        );
    }
    
    // 加密
    static async encrypt(data: string, key: CryptoKey): Promise<{ iv: Uint8Array; data: Uint8Array }> {
        const encoder = new TextEncoder();
        const iv = crypto.getRandomValues(new Uint8Array(12));
        
        const encrypted = await crypto.subtle.encrypt(
            { name: this.algorithm, iv },
            key,
            encoder.encode(data)
        );
        
        return {
            iv,
            data: new Uint8Array(encrypted)
        };
    }
    
    // 解密
    static async decrypt(encrypted: { iv: Uint8Array; data: Uint8Array }, key: CryptoKey): Promise<string> {
        const decrypted = await crypto.subtle.decrypt(
            { name: this.algorithm, iv: encrypted.iv },
            key,
            encrypted.data
        );
        
        const decoder = new TextDecoder();
        return decoder.decode(decrypted);
    }
    
    // 导出密钥
    static async exportKey(key: CryptoKey): Promise<string> {
        const exported = await crypto.subtle.exportKey('raw', key);
        return btoa(String.fromCharCode(...new Uint8Array(exported)));
    }
    
    // 导入密钥
    static async importKey(keyData: string): Promise<CryptoKey> {
        const binary = Uint8Array.from(atob(keyData), c => c.charCodeAt(0));
        
        return crypto.subtle.importKey(
            'raw',
            binary,
            { name: this.algorithm, length: this.keyLength },
            true,
            ['encrypt', 'decrypt']
        );
    }
}

2.2 安全存储服务 #

typescript
// src/services/secure-storage.service.ts
import { Preferences } from '@capacitor/preferences';
import { Encryption } from '../utils/encryption';

class SecureStorageService {
    private key: CryptoKey | null = null;
    private keyAlias = 'app_secure_key';
    
    async init(password?: string): Promise<void> {
        if (password) {
            // 使用密码派生密钥
            const salt = new TextEncoder().encode('app-salt-value');
            this.key = await Encryption.deriveKey(password, salt);
        } else {
            // 使用或生成随机密钥
            const storedKey = await this.getStoredKey();
            
            if (storedKey) {
                this.key = await Encryption.importKey(storedKey);
            } else {
                this.key = await Encryption.generateKey();
                await this.storeKey(await Encryption.exportKey(this.key));
            }
        }
    }
    
    private async getStoredKey(): Promise<string | null> {
        const { value } = await Preferences.get({ key: this.keyAlias });
        return value;
    }
    
    private async storeKey(keyData: string): Promise<void> {
        await Preferences.set({ key: this.keyAlias, value: keyData });
    }
    
    async set(key: string, value: any): Promise<void> {
        if (!this.key) {
            throw new Error('Secure storage not initialized');
        }
        
        const json = JSON.stringify(value);
        const encrypted = await Encryption.encrypt(json, this.key);
        
        // 存储加密数据
        await Preferences.set({
            key: `secure_${key}`,
            value: JSON.stringify({
                iv: Array.from(encrypted.iv),
                data: Array.from(encrypted.data)
            })
        });
    }
    
    async get<T>(key: string): Promise<T | null> {
        if (!this.key) {
            throw new Error('Secure storage not initialized');
        }
        
        const { value } = await Preferences.get({ key: `secure_${key}` });
        
        if (!value) return null;
        
        try {
            const parsed = JSON.parse(value);
            const decrypted = await Encryption.decrypt(
                {
                    iv: new Uint8Array(parsed.iv),
                    data: new Uint8Array(parsed.data)
                },
                this.key
            );
            
            return JSON.parse(decrypted) as T;
        } catch {
            return null;
        }
    }
    
    async remove(key: string): Promise<void> {
        await Preferences.remove({ key: `secure_${key}` });
    }
}

export const secureStorage = new SecureStorageService();

2.3 使用示例 #

typescript
// 初始化(使用密码)
await secureStorage.init('user-password');

// 或初始化(使用随机密钥)
await secureStorage.init();

// 存储敏感数据
await secureStorage.set('user_token', {
    access: 'xxx',
    refresh: 'yyy',
    expires: Date.now() + 3600000
});

// 读取敏感数据
const token = await secureStorage.get<{ access: string; refresh: string }>('user_token');

三、网络安全 #

3.1 HTTPS配置 #

typescript
// capacitor.config.json
{
    "server": {
        "iosScheme": "https",
        "androidScheme": "https"
    }
}

3.2 iOS网络安全 #

xml
<!-- ios/App/App/Info.plist -->
<key>NSAppTransportSecurity</key>
<dict>
    <!-- 推荐设置:禁用任意HTTP加载 -->
    <key>NSAllowsArbitraryLoads</key>
    <false/>
    
    <!-- 如果需要,为特定域名启用HTTP -->
    <key>NSExceptionDomains</key>
    <dict>
        <key>localhost</key>
        <dict>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
        </dict>
    </dict>
</dict>

3.3 Android网络安全 #

xml
<!-- android/app/src/main/res/xml/network_security_config.xml -->
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <!-- 生产环境:禁用明文HTTP -->
    <base-config cleartextTrafficPermitted="false">
        <trust-anchors>
            <certificates src="system" />
        </trust-anchors>
    </base-config>
    
    <!-- 开发环境:允许localhost -->
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">localhost</domain>
        <domain includeSubdomains="true">10.0.2.2</domain>
    </domain-config>
    
    <!-- 证书固定(可选) -->
    <domain-config>
        <domain includeSubdomains="true">api.example.com</domain>
        <pin-set>
            <pin digest="SHA-256">base64-encoded-pin</pin>
        </pin-set>
    </domain-config>
</network-security-config>

3.4 API请求封装 #

typescript
// src/services/api.service.ts
import { Preferences } from '@capacitor/preferences';

class ApiService {
    private baseUrl = 'https://api.example.com';
    
    private async getHeaders(): Promise<HeadersInit> {
        const { value: token } = await Preferences.get({ key: 'auth_token' });
        
        return {
            'Content-Type': 'application/json',
            'Authorization': token ? `Bearer ${token}` : ''
        };
    }
    
    async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
        const headers = await this.getHeaders();
        
        const response = await fetch(`${this.baseUrl}${endpoint}`, {
            ...options,
            headers: {
                ...headers,
                ...options.headers
            }
        });
        
        if (!response.ok) {
            throw new Error(`API Error: ${response.status}`);
        }
        
        return response.json();
    }
    
    get<T>(endpoint: string): Promise<T> {
        return this.request<T>(endpoint);
    }
    
    post<T>(endpoint: string, data: any): Promise<T> {
        return this.request<T>(endpoint, {
            method: 'POST',
            body: JSON.stringify(data)
        });
    }
}

export const apiService = new ApiService();

四、认证安全 #

4.1 Token管理 #

typescript
// src/services/auth.service.ts
import { Preferences } from '@capacitor/preferences';
import { secureStorage } from './secure-storage.service';

interface TokenData {
    accessToken: string;
    refreshToken: string;
    expiresAt: number;
}

class AuthService {
    private tokenKey = 'auth_tokens';
    
    async login(credentials: { email: string; password: string }): Promise<void> {
        // 调用登录API
        const response = await fetch('https://api.example.com/auth/login', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(credentials)
        });
        
        const data = await response.json();
        
        // 安全存储token
        await secureStorage.set(this.tokenKey, {
            accessToken: data.accessToken,
            refreshToken: data.refreshToken,
            expiresAt: Date.now() + data.expiresIn * 1000
        });
    }
    
    async getAccessToken(): Promise<string | null> {
        const tokens = await secureStorage.get<TokenData>(this.tokenKey);
        
        if (!tokens) return null;
        
        // 检查是否过期
        if (Date.now() >= tokens.expiresAt) {
            // 尝试刷新token
            const newTokens = await this.refreshTokens(tokens.refreshToken);
            return newTokens?.accessToken || null;
        }
        
        return tokens.accessToken;
    }
    
    private async refreshTokens(refreshToken: string): Promise<TokenData | null> {
        try {
            const response = await fetch('https://api.example.com/auth/refresh', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ refreshToken })
            });
            
            const data = await response.json();
            
            const tokens: TokenData = {
                accessToken: data.accessToken,
                refreshToken: data.refreshToken || refreshToken,
                expiresAt: Date.now() + data.expiresIn * 1000
            };
            
            await secureStorage.set(this.tokenKey, tokens);
            
            return tokens;
        } catch {
            // 刷新失败,清除token
            await this.logout();
            return null;
        }
    }
    
    async logout(): Promise<void> {
        await secureStorage.remove(this.tokenKey);
    }
    
    async isAuthenticated(): Promise<boolean> {
        const token = await this.getAccessToken();
        return token !== null;
    }
}

export const authService = new AuthService();

4.2 生物识别 #

typescript
// 使用 @capacitor-community/biometric
import { BiometricAuth } from '@capacitor-community/biometric';

async function authenticateWithBiometric(): Promise<boolean> {
    try {
        const result = await BiometricAuth.checkBiometry();
        
        if (!result.isAvailable) {
            console.log('Biometric not available');
            return false;
        }
        
        const authResult = await BiometricAuth.authenticate({
            reason: '请验证身份以继续',
            title: '身份验证',
            subtitle: '使用生物识别',
            cancelTitle: '取消'
        });
        
        return authResult.isAuthenticated;
    } catch (error) {
        console.error('Biometric auth error:', error);
        return false;
    }
}

五、代码安全 #

5.1 代码混淆 #

javascript
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
    plugins: [react()],
    build: {
        minify: 'terser',
        terserOptions: {
            compress: {
                drop_console: true,
                drop_debugger: true
            },
            mangle: {
                properties: {
                    regex: /^_/
                }
            }
        }
    }
});

5.2 环境变量安全 #

typescript
// .env(不提交到版本控制)
VITE_API_URL=https://api.example.com
VITE_API_KEY=your-api-key

// 使用
const apiUrl = import.meta.env.VITE_API_URL;

// 不要在前端存储敏感密钥
// 敏感操作应该在服务器端完成

5.3 输入验证 #

typescript
// src/utils/validation.ts
export function sanitizeInput(input: string): string {
    return input
        .replace(/[<>]/g, '')  // 移除可能的HTML标签
        .trim();
}

export function validateEmail(email: string): boolean {
    const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return regex.test(email);
}

export function validatePhone(phone: string): boolean {
    const regex = /^\+?[\d\s-]{10,}$/;
    return regex.test(phone);
}

六、安全检查清单 #

6.1 开发阶段 #

  • [ ] 所有API通信使用HTTPS
  • [ ] 敏感数据加密存储
  • [ ] 不在代码中硬编码密钥
  • [ ] 输入验证和清理
  • [ ] 错误信息不暴露敏感信息

6.2 构建阶段 #

  • [ ] 启用代码混淆
  • [ ] 移除console.log
  • [ ] 检查依赖漏洞
  • [ ] 环境变量正确配置

6.3 发布阶段 #

  • [ ] 生产环境禁用调试
  • [ ] 证书正确配置
  • [ ] 安全策略配置
  • [ ] 隐私政策完善

七、总结 #

7.1 安全要点 #

层级 措施
数据层 加密存储敏感数据
网络层 强制HTTPS
认证层 安全Token管理
代码层 混淆和验证

7.2 下一步 #

了解安全最佳实践后,让我们学习 性能优化

最后更新:2026-03-28