返回博客
安全2025年9月18日安安科技团队阅读时间 15 分钟

FLUTTER 应用安全
最佳实践

全面的 Flutter 应用安全指南。学习数据加密、安全存储、API 安全、身份验证及漏洞防范技术。

Flutter 应用安全最佳实践
// 简报

在移动应用开发中,安全绝不应是事后才考虑的问题。随着网络威胁日益增加,加上 GDPR 和 CCPA 等严格的隐私法规,保护用户数据既是法律要求,也是建立信任的关键。

本指南全面涵盖 Flutter 应用的必备安全实践,从基础数据保护到高级威胁缓解。无论你正在开发简单的工具应用,还是复杂的金融科技方案,这些实践都能帮助你打造安全、值得信赖的应用。

数据加密与安全存储

无论是静态存储还是传输中的敏感数据,都必须妥善保护。切勿以明文存储敏感信息,传输前务必先加密。

secure_storage.dart
// Secure Storage Implementation
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:crypto/crypto.dart';
import 'dart:convert';

class SecureStorageService {
  static const _storage = FlutterSecureStorage(
    aOptions: AndroidOptions(
      encryptedSharedPreferences: true,
      sharedPreferencesName: 'secure_prefs',
      preferencesKeyPrefix: 'app_',
    ),
    iOptions: IOSOptions(
      groupId: 'com.yourapp.security',
      accessibility: IOSAccessibility.first_unlock_this_device,
      synchronizable: false,
    ),
  );
  
  // Store encrypted sensitive data
  static Future<void> storeSecureData(String key, String value) async {
    try {
      final encryptedValue = _encryptData(value);
      await _storage.write(key: key, value: encryptedValue);
    } catch (e) {
      throw SecurityException('Failed to store secure data: $e');
    }
  }
  
  // Retrieve and decrypt sensitive data
  static Future<String?> getSecureData(String key) async {
    try {
      final encryptedValue = await _storage.read(key: key);
      if (encryptedValue == null) return null;
      return _decryptData(encryptedValue);
    } catch (e) {
      throw SecurityException('Failed to retrieve secure data: $e');
    }
  }
  
  // Store authentication tokens securely
  static Future<void> storeAuthToken(String token) async {
    await storeSecureData('auth_token', token);
    await storeSecureData('token_timestamp', DateTime.now().toIso8601String());
  }
  
  // Check if token is valid and not expired
  static Future<bool> isTokenValid() async {
    final token = await getSecureData('auth_token');
    final timestampStr = await getSecureData('token_timestamp');
    
    if (token == null || timestampStr == null) return false;
    
    final timestamp = DateTime.parse(timestampStr);
    final now = DateTime.now();
    const tokenLifetime = Duration(hours: 24);
    
    return now.difference(timestamp) < tokenLifetime;
  }
  
  // Clear all sensitive data
  static Future<void> clearAllSecureData() async {
    await _storage.deleteAll();
  }
  
  // Basic encryption for additional security layer
  static String _encryptData(String data) {
    final bytes = utf8.encode(data);
    final digest = sha256.convert(bytes);
    return base64.encode(bytes); // In production, use proper encryption
  }
  
  static String _decryptData(String encryptedData) {
    final bytes = base64.decode(encryptedData);
    return utf8.decode(bytes);
  }
}

// Advanced Encryption Service
import 'package:pointycastle/export.dart';

class AdvancedEncryptionService {
  static final _key = _generateKey();
  static final _iv = _generateIV();
  
  static Uint8List encrypt(String plainText) {
    final plainBytes = Uint8List.fromList(utf8.encode(plainText));
    final cipher = AESFastEngine();
    final cbcCipher = CBCBlockCipher(cipher);
    final paddedCipher = PaddedBlockCipher('AES/CBC/PKCS7');
    
    paddedCipher.init(true, PaddedBlockCipherParameters(
      ParametersWithIV(KeyParameter(_key), _iv),
      null,
    ));
    
    return paddedCipher.process(plainBytes);
  }
  
  static String decrypt(Uint8List cipherBytes) {
    final cipher = AESFastEngine();
    final cbcCipher = CBCBlockCipher(cipher);
    final paddedCipher = PaddedBlockCipher('AES/CBC/PKCS7');
    
    paddedCipher.init(false, PaddedBlockCipherParameters(
      ParametersWithIV(KeyParameter(_key), _iv),
      null,
    ));
    
    final decrypted = paddedCipher.process(cipherBytes);
    return utf8.decode(decrypted);
  }
  
  static Uint8List _generateKey() {
    // In production, derive from secure random or user password
    final keyBytes = Uint8List(32);
    final secureRandom = SecureRandom('Fortuna');
    secureRandom.seed(KeyParameter(Uint8List.fromList('your-secret-key'.codeUnits)));
    
    for (int i = 0; i < keyBytes.length; i++) {
      keyBytes[i] = secureRandom.nextUint8();
    }
    return keyBytes;
  }
  
  static Uint8List _generateIV() {
    final ivBytes = Uint8List(16);
    final secureRandom = SecureRandom('Fortuna');
    
    for (int i = 0; i < ivBytes.length; i++) {
      ivBytes[i] = secureRandom.nextUint8();
    }
    return ivBytes;
  }
}

存储应做事项

  • 使用 Flutter Secure Storage 存储 token
  • 敏感数据先加密后存储
  • 实施密钥轮换策略
  • 为数据设置过期时间戳

存储禁忌

  • 切勿以明文存储密码
  • 不要用 SharedPreferences 存储机密数据
  • 避免硬编码加密密钥
  • 个人身份信息(PII)必须加密存储

API 安全与身份验证

安全的 API 通信对保护传输中的数据至关重要。实施妥善的身份验证、授权及请求签名,防止未经授权的访问。

secure_api_client.dart
// Secure API Client Implementation
import 'package:dio/dio.dart';
import 'package:dio_certificate_pinning/dio_certificate_pinning.dart';
import 'package:crypto/crypto.dart';

class SecureApiClient {
  late final Dio _dio;
  final String _baseUrl;
  final String _apiKey;
  
  SecureApiClient({
    required String baseUrl,
    required String apiKey,
  }) : _baseUrl = baseUrl, _apiKey = apiKey {
    _setupDio();
  }
  
  void _setupDio() {
    _dio = Dio();
    
    // Certificate pinning for enhanced security
    _dio.interceptors.add(
      CertificatePinningInterceptor(
        allowedSHAFingerprints: [
          'YOUR_SERVER_CERTIFICATE_SHA256_FINGERPRINT',
        ],
      ),
    );
    
    // Request/Response interceptor for security
    _dio.interceptors.add(
      InterceptorsWrapper(
        onRequest: (options, handler) {
          // Add security headers
          options.headers['Content-Type'] = 'application/json';
          options.headers['User-Agent'] = 'YourApp/1.0.0';
          options.headers['X-Requested-With'] = 'XMLHttpRequest';
          
          // Add timestamp and nonce for replay attack prevention
          final timestamp = DateTime.now().millisecondsSinceEpoch.toString();
          final nonce = _generateNonce();
          
          options.headers['X-Timestamp'] = timestamp;
          options.headers['X-Nonce'] = nonce;
          
          // Add request signature
          final signature = _signRequest(
            method: options.method,
            path: options.path,
            timestamp: timestamp,
            nonce: nonce,
            body: options.data?.toString() ?? '',
          );
          options.headers['X-Signature'] = signature;
          
          handler.next(options);
        },
        onResponse: (response, handler) {
          // Verify response signature if provided
          final responseSignature = response.headers['x-response-signature']?.first;
          if (responseSignature != null) {
            if (!_verifyResponseSignature(response.data, responseSignature)) {
              throw DioError(
                requestOptions: response.requestOptions,
                error: 'Invalid response signature',
                type: DioErrorType.other,
              );
            }
          }
          handler.next(response);
        },
        onError: (error, handler) {
          _handleApiError(error);
          handler.next(error);
        },
      ),
    );
  }
  
  // Authenticated GET request
  Future<Map<String, dynamic>> get(String endpoint) async {
    try {
      final token = await SecureStorageService.getSecureData('auth_token');
      if (token == null) throw UnauthorizedException('No auth token found');
      
      final response = await _dio.get(
        '$_baseUrl$endpoint',
        options: Options(
          headers: {'Authorization': 'Bearer $token'},
        ),
      );
      
      return response.data;
    } catch (e) {
      throw _handleApiException(e);
    }
  }
  
  // Authenticated POST request with body encryption
  Future<Map<String, dynamic>> post(String endpoint, Map<String, dynamic> data) async {
    try {
      final token = await SecureStorageService.getSecureData('auth_token');
      if (token == null) throw UnauthorizedException('No auth token found');
      
      // Encrypt sensitive data in request body
      final encryptedData = _encryptRequestBody(data);
      
      final response = await _dio.post(
        '$_baseUrl$endpoint',
        data: encryptedData,
        options: Options(
          headers: {
            'Authorization': 'Bearer $token',
            'Content-Encoding': 'encrypted',
          },
        ),
      );
      
      return response.data;
    } catch (e) {
      throw _handleApiException(e);
    }
  }
  
  // OAuth 2.0 / JWT Authentication
  Future<void> authenticate(String username, String password) async {
    try {
      // Never send passwords in plain text
      final hashedPassword = _hashPassword(password);
      
      final response = await _dio.post(
        '$_baseUrl/auth/login',
        data: {
          'username': username,
          'password': hashedPassword,
          'client_id': 'your_client_id',
          'grant_type': 'password',
        },
      );
      
      final accessToken = response.data['access_token'];
      final refreshToken = response.data['refresh_token'];
      
      await SecureStorageService.storeAuthToken(accessToken);
      await SecureStorageService.storeSecureData('refresh_token', refreshToken);
      
    } catch (e) {
      throw AuthenticationException('Authentication failed: $e');
    }
  }
  
  // Token refresh mechanism
  Future<void> refreshToken() async {
    final refreshToken = await SecureStorageService.getSecureData('refresh_token');
    if (refreshToken == null) throw UnauthorizedException('No refresh token');
    
    try {
      final response = await _dio.post(
        '$_baseUrl/auth/refresh',
        data: {
          'refresh_token': refreshToken,
          'grant_type': 'refresh_token',
        },
      );
      
      final newAccessToken = response.data['access_token'];
      await SecureStorageService.storeAuthToken(newAccessToken);
      
    } catch (e) {
      // Clear tokens if refresh fails
      await SecureStorageService.clearAllSecureData();
      throw AuthenticationException('Token refresh failed: $e');
    }
  }
  
  String _signRequest({
    required String method,
    required String path,
    required String timestamp,
    required String nonce,
    required String body,
  }) {
    final message = '$method$path$timestamp$nonce$body';
    final key = utf8.encode(_apiKey);
    final messageBytes = utf8.encode(message);
    
    final hmac = Hmac(sha256, key);
    final digest = hmac.convert(messageBytes);
    return digest.toString();
  }
  
  String _generateNonce() {
    final random = Random.secure();
    final values = List<int>.generate(16, (i) => random.nextInt(256));
    return base64.encode(values);
  }
  
  Map<String, dynamic> _encryptRequestBody(Map<String, dynamic> data) {
    // Encrypt sensitive fields
    final sensitiveFields = ['password', 'ssn', 'credit_card', 'personal_data'];
    final encryptedData = Map<String, dynamic>.from(data);
    
    for (final field in sensitiveFields) {
      if (encryptedData.containsKey(field)) {
        final plainText = encryptedData[field].toString();
        encryptedData[field] = base64.encode(
          AdvancedEncryptionService.encrypt(plainText),
        );
      }
    }
    
    return encryptedData;
  }
  
  String _hashPassword(String password) {
    // Use bcrypt or Argon2 in production
    final salt = 'your_app_salt'; // Use proper salt generation
    final combined = password + salt;
    final digest = sha256.convert(utf8.encode(combined));
    return digest.toString();
  }
}

API 安全清单:

  • 实施证书绑定(certificate pinning)
  • 使用 HMAC 签名验证请求
  • 加入时间戳及 nonce 防止重放攻击
  • 加密请求及响应中的敏感数据
  • 实施妥善的 token 刷新机制

生物识别验证与设备安全

实施生物识别验证及设备安全检查,确保应用只在可信任的设备上运行,并提供安全的用户验证。

biometric_auth_service.dart
// Biometric Authentication Service
import 'package:local_auth/local_auth.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/services.dart';

class BiometricAuthService {
  static final LocalAuthentication _localAuth = LocalAuthentication();
  
  // Check if biometric authentication is available
  static Future<bool> isBiometricAvailable() async {
    try {
      final isAvailable = await _localAuth.canCheckBiometrics;
      final isDeviceSupported = await _localAuth.isDeviceSupported();
      return isAvailable && isDeviceSupported;
    } catch (e) {
      return false;
    }
  }
  
  // Get available biometric types
  static Future<List<BiometricType>> getAvailableBiometrics() async {
    try {
      return await _localAuth.getAvailableBiometrics();
    } catch (e) {
      return [];
    }
  }
  
  // Authenticate with biometrics
  static Future<bool> authenticateWithBiometrics({
    required String reason,
    bool stickyAuth = true,
  }) async {
    try {
      final isAvailable = await isBiometricAvailable();
      if (!isAvailable) return false;
      
      return await _localAuth.authenticate(
        localizedReason: reason,
        authMessages: const [
          AndroidAuthMessages(
            signInTitle: 'Biometric Authentication',
            biometricHint: 'Verify your identity',
            biometricNotRecognized: 'Biometric not recognized',
            biometricRequiredTitle: 'Biometric Required',
            biometricSuccess: 'Authentication successful',
            cancelButton: 'Cancel',
            deviceCredentialsRequiredTitle: 'Device credentials required',
            deviceCredentialsSetupDescription: 'Please set up device credentials',
            goToSettingsButton: 'Go to Settings',
            goToSettingsDescription: 'Please set up biometric authentication',
          ),
          IOSAuthMessages(
            lockOut: 'Biometric authentication is disabled',
            goToSettingsButton: 'Go to Settings',
            goToSettingsDescription: 'Please set up biometric authentication',
            cancelButton: 'Cancel',
          ),
        ],
        options: AuthenticationOptions(
          stickyAuth: stickyAuth,
          biometricOnly: false, // Allow fallback to device credentials
        ),
      );
    } catch (e) {
      return false;
    }
  }
  
  // Enhanced authentication with retry logic
  static Future<AuthResult> authenticateWithRetry({
    required String reason,
    int maxRetries = 3,
  }) async {
    int attempts = 0;
    
    while (attempts < maxRetries) {
      try {
        final success = await authenticateWithBiometrics(reason: reason);
        if (success) {
          return AuthResult.success();
        }
        attempts++;
      } catch (e) {
        if (e is PlatformException) {
          switch (e.code) {
            case 'UserCancel':
              return AuthResult.cancelled();
            case 'BiometricOnlyNotSupported':
              return AuthResult.fallbackRequired();
            case 'DeviceNotSupported':
              return AuthResult.notSupported();
            default:
              attempts++;
          }
        }
      }
    }
    
    return AuthResult.failed('Maximum authentication attempts exceeded');
  }
}

// Device Security Checks
class DeviceSecurityService {
  static final DeviceInfoPlugin _deviceInfo = DeviceInfoPlugin();
  
  // Check if device is rooted/jailbroken
  static Future<bool> isDeviceCompromised() async {
    try {
      if (Platform.isAndroid) {
        return await _checkAndroidRootedDevice();
      } else if (Platform.isIOS) {
        return await _checkIOSJailbrokenDevice();
      }
      return false;
    } catch (e) {
      return true; // Assume compromised if check fails
    }
  }
  
  static Future<bool> _checkAndroidRootedDevice() async {
    final androidInfo = await _deviceInfo.androidInfo;
    
    // Check for common root indicators
    final rootIndicators = [
      '/system/app/Superuser.apk',
      '/sbin/su',
      '/system/bin/su',
      '/system/xbin/su',
      '/data/local/xbin/su',
      '/data/local/bin/su',
      '/system/sd/xbin/su',
      '/system/bin/failsafe/su',
      '/data/local/su',
    ];
    
    for (final path in rootIndicators) {
      if (await File(path).exists()) {
        return true;
      }
    }
    
    // Check for root apps
    try {
      await Process.run('which', ['su']);
      return true;
    } catch (e) {
      // su command not found, likely not rooted
    }
    
    return false;
  }
  
  static Future<bool> _checkIOSJailbrokenDevice() async {
    final iosInfo = await _deviceInfo.iosInfo;
    
    // Check for jailbreak indicators
    final jailbreakPaths = [
      '/Applications/Cydia.app',
      '/Library/MobileSubstrate/MobileSubstrate.dylib',
      '/bin/bash',
      '/usr/sbin/sshd',
      '/etc/apt',
      '/private/var/lib/apt/',
    ];
    
    for (final path in jailbreakPaths) {
      if (await File(path).exists()) {
        return true;
      }
    }
    
    return false;
  }
  
  // Check if app is running in debug mode
  static bool isDebugMode() {
    return kDebugMode;
  }
  
  // Verify app integrity
  static Future<bool> verifyAppIntegrity() async {
    try {
      // Check app signature (platform-specific implementation required)
      // This is a simplified check
      final packageInfo = await PackageInfo.fromPlatform();
      
      // Verify package name and version
      const expectedPackageName = 'com.yourapp.package';
      if (packageInfo.packageName != expectedPackageName) {
        return false;
      }
      
      return true;
    } catch (e) {
      return false;
    }
  }
  
  // Complete security check
  static Future<SecurityCheckResult> performSecurityCheck() async {
    final results = await Future.wait([
      isDeviceCompromised(),
      BiometricAuthService.isBiometricAvailable(),
      verifyAppIntegrity(),
    ]);
    
    final isCompromised = results[0] as bool;
    final biometricAvailable = results[1] as bool;
    final integrityValid = results[2] as bool;
    
    return SecurityCheckResult(
      isDeviceCompromised: isCompromised,
      isBiometricAvailable: biometricAvailable,
      isAppIntegrityValid: integrityValid,
      isDebugMode: isDebugMode(),
    );
  }
}

// Result classes
class AuthResult {
  final bool isSuccess;
  final String? error;
  final AuthResultType type;
  
  AuthResult._(this.isSuccess, this.error, this.type);
  
  factory AuthResult.success() => AuthResult._(true, null, AuthResultType.success);
  factory AuthResult.failed(String error) => AuthResult._(false, error, AuthResultType.failed);
  factory AuthResult.cancelled() => AuthResult._(false, 'User cancelled', AuthResultType.cancelled);
  factory AuthResult.notSupported() => AuthResult._(false, 'Not supported', AuthResultType.notSupported);
  factory AuthResult.fallbackRequired() => AuthResult._(false, 'Fallback required', AuthResultType.fallbackRequired);
}

enum AuthResultType { success, failed, cancelled, notSupported, fallbackRequired }

class SecurityCheckResult {
  final bool isDeviceCompromised;
  final bool isBiometricAvailable;
  final bool isAppIntegrityValid;
  final bool isDebugMode;
  
  SecurityCheckResult({
    required this.isDeviceCompromised,
    required this.isBiometricAvailable,
    required this.isAppIntegrityValid,
    required this.isDebugMode,
  });
  
  bool get isSecure => !isDeviceCompromised && isAppIntegrityValid && !isDebugMode;
}

设备安全功能:

  • 支持后备方案的生物识别验证
  • Root/越狱检测
  • 应用完整性验证
  • 调试模式检测

常见漏洞与防范

了解常见安全漏洞,有助你主动保护应用。以下是最关键的威胁及相应的防范方法。

常见威胁

  • 中间人攻击
  • 通过 API 的 SQL 注入
  • 逆向工程
  • 日志泄露数据
  • 不安全的数据传输
  • 代码篡改

防范措施

  • 证书绑定(certificate pinning)
  • 输入验证与清理
  • 代码混淆(obfuscation)
  • 安全的日志记录实践
  • 端到端加密
  • 应用完整性检查
security_utils.dart
// Security Utilities and Best Practices
class SecurityUtils {
  // Secure logging that prevents sensitive data leakage
  static void secureLog(String message, {LogLevel level = LogLevel.info}) {
    if (kReleaseMode) {
      // In production, only log non-sensitive information
      final sanitizedMessage = _sanitizeLogMessage(message);
      _logToSecureEndpoint(sanitizedMessage, level);
    } else {
      // In debug mode, allow detailed logging
      developer.log(message, level: level.value);
    }
  }
  
  static String _sanitizeLogMessage(String message) {
    // Remove sensitive patterns
    final sensitivePatterns = [
      RegExp(r'password[=:]s*S+', caseSensitive: false),
      RegExp(r'token[=:]s*S+', caseSensitive: false),
      RegExp(r'api[_-]?key[=:]s*S+', caseSensitive: false),
      RegExp(r'd{4}[s-]?d{4}[s-]?d{4}[s-]?d{4}'), // Credit card numbers
      RegExp(r'd{3}-d{2}-d{4}'), // SSN
    ];
    
    String sanitized = message;
    for (final pattern in sensitivePatterns) {
      sanitized = sanitized.replaceAll(pattern, '[REDACTED]');
    }
    return sanitized;
  }
  
  // Input validation and sanitization
  static String sanitizeInput(String input) {
    // Remove potentially dangerous characters
    return input
        .replaceAll(RegExp(r'[<>"']'), '')
        .replaceAll(RegExp(r'script', caseSensitive: false), '')
        .trim();
  }
  
  // Email validation with security considerations
  static bool isValidEmail(String email) {
    final emailRegex = RegExp(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$');
    return emailRegex.hasMatch(email) && email.length <= 254;
  }
  
  // Password strength validation
  static PasswordStrength checkPasswordStrength(String password) {
    int score = 0;
    final feedback = <String>[];
    
    if (password.length >= 8) score += 1;
    else feedback.add('Use at least 8 characters');
    
    if (RegExp(r'[a-z]').hasMatch(password)) score += 1;
    else feedback.add('Include lowercase letters');
    
    if (RegExp(r'[A-Z]').hasMatch(password)) score += 1;
    else feedback.add('Include uppercase letters');
    
    if (RegExp(r'd').hasMatch(password)) score += 1;
    else feedback.add('Include numbers');
    
    if (RegExp(r'[!@#$&*~]').hasMatch(password)) score += 1;
    else feedback.add('Include special characters');
    
    // Check against common passwords
    if (_isCommonPassword(password)) {
      score = 0;
      feedback.add('Password is too common');
    }
    
    return PasswordStrength(score: score, feedback: feedback);
  }
  
  static bool _isCommonPassword(String password) {
    final commonPasswords = [
      'password', '123456', 'password123', 'admin', 'qwerty',
      'letmein', 'welcome', 'monkey', '1234567890', 'dragon'
    ];
    return commonPasswords.contains(password.toLowerCase());
  }
  
  // Generate secure random strings
  static String generateSecureToken(int length) {
    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    final random = Random.secure();
    return List.generate(length, (index) => chars[random.nextInt(chars.length)]).join();
  }
  
  // Rate limiting for API calls
  static final Map<String, List<DateTime>> _requestHistory = {};
  
  static bool isRateLimited(String identifier, {int maxRequests = 10, Duration window = const Duration(minutes: 1)}) {
    final now = DateTime.now();
    final windowStart = now.subtract(window);
    
    _requestHistory[identifier] ??= [];
    final requests = _requestHistory[identifier]!;
    
    // Remove old requests outside the window
    requests.removeWhere((time) => time.isBefore(windowStart));
    
    if (requests.length >= maxRequests) {
      return true; // Rate limited
    }
    
    requests.add(now);
    return false;
  }
}

// Security exceptions
class SecurityException implements Exception {
  final String message;
  SecurityException(this.message);
  
  @override
  String toString() => 'SecurityException: $message';
}

class AuthenticationException extends SecurityException {
  AuthenticationException(String message) : super(message);
}

class UnauthorizedException extends SecurityException {
  UnauthorizedException(String message) : super(message);
}

// Password strength result
class PasswordStrength {
  final int score;
  final List<String> feedback;
  
  PasswordStrength({required this.score, required this.feedback});
  
  PasswordStrengthLevel get level {
    if (score < 2) return PasswordStrengthLevel.weak;
    if (score < 4) return PasswordStrengthLevel.medium;
    return PasswordStrengthLevel.strong;
  }
}

enum PasswordStrengthLevel { weak, medium, strong }
enum LogLevel { debug, info, warning, error }
CHK.01审计

FLUTTER 安全 清单

客户端安全

使用 Flutter Secure Storage 存储 token
实施生物识别验证
加入 Root/越狱检测
混淆发布版本
验证所有用户输入

网络安全

实施证书绑定
所有通信使用 HTTPS
以 HMAC 签名 API 请求
实施速率限制
加密敏感请求数据
// 加固防线

立即保护你的 FLUTTER 应用

安全绝不应是事后补救。我们的团队可以帮助你实施全面的安全措施,保护你的用户和业务。

联系我们
// 信号放大

分享本指南