--- paths: - "**/*.kt" - "**/*.kts" --- # Kotlin 安全 > 本文档基于 [common/security.md](../common/security.md),补充了 Kotlin 和 Android/KMP 相关的内容。 ## 密钥管理 * 切勿在源代码中硬编码 API 密钥、令牌或凭据 * 本地开发时,使用 `local.properties`(已通过 git 忽略)来管理密钥 * 发布版本中,使用由 CI 密钥生成的 `BuildConfig` 字段 * 运行时密钥存储使用 `EncryptedSharedPreferences`(Android)或 Keychain(iOS) ```kotlin // BAD val apiKey = "sk-abc123..." // GOOD — from BuildConfig (generated at build time) val apiKey = BuildConfig.API_KEY // GOOD — from secure storage at runtime val token = secureStorage.get("auth_token") ``` ## 网络安全 * 仅使用 HTTPS —— 配置 `network_security_config.xml` 以阻止明文传输 * 使用 OkHttp 的 `CertificatePinner` 或 Ktor 的等效功能为敏感端点固定证书 * 为所有 HTTP 客户端设置超时 —— 切勿使用默认值(可能为无限长) * 在使用所有服务器响应前,先进行验证和清理 ```xml ``` ## 输入验证 * 在处理或将用户输入发送到 API 之前,验证所有用户输入 * 对 Room/SQLDelight 使用参数化查询 —— 切勿将用户输入拼接到 SQL 语句中 * 清理用户输入中的文件路径,以防止路径遍历攻击 ```kotlin // BAD — SQL injection @Query("SELECT * FROM items WHERE name = '$input'") // GOOD — parameterized @Query("SELECT * FROM items WHERE name = :input") fun findByName(input: String): List ``` ## 数据保护 * 在 Android 上,使用 `EncryptedSharedPreferences` 存储敏感键值数据 * 使用 `@Serializable` 并明确指定字段名 —— 不要泄露内部属性名 * 敏感数据不再需要时,从内存中清除 * 对序列化类使用 `@Keep` 或 ProGuard 规则,以防止名称混淆 ## 身份验证 * 将令牌存储在安全存储中,而非普通的 SharedPreferences * 实现令牌刷新机制,并正确处理 401/403 状态码 * 退出登录时清除所有身份验证状态(令牌、缓存的用户数据、Cookie) * 对敏感操作使用生物特征认证(`BiometricPrompt`) ## ProGuard / R8 * 为所有序列化模型(`@Serializable`、Gson、Moshi)保留规则 * 为基于反射的库(Koin、Retrofit)保留规则 * 测试发布版本 —— 混淆可能会静默地破坏序列化 ## WebView 安全 * 除非明确需要,否则禁用 JavaScript:`settings.javaScriptEnabled = false` * 在 WebView 中加载 URL 前,先进行验证 * 切勿暴露访问敏感数据的 `@JavascriptInterface` 方法 * 使用 `WebViewClient.shouldOverrideUrlLoading()` 来控制导航