Flutter 安全注入 Google Maps API Key (iOS & Android)
2024-12-25 05:28:37
Flutter 应用中安全注入 Google Maps API Key (iOS & Android)
在 Flutter 应用中集成 Google 地图服务时,将 API Key 硬编码到代码中存在安全风险,泄漏风险极大。为了提升安全性,有效方法是使用环境变量。此做法能够将敏感信息与代码分离,便于管理并减少暴露机会。 本文将讨论在 iOS 和 Android 平台中,如何使用环境变量安全注入 Google Maps API Key。
问题分析
硬编码 API Key 的风险显著:代码公开时,API Key 也会泄露,从而导致恶意使用、滥用,或者其他不安全问题,威胁应用。
许多教程都建议在构建时通过 DART_DEFINES
传递 API Key。虽然这种做法看似合理,但是过程通常繁琐易错。直接将环境变量作为 Dart 定义并进行解析并非始终直接奏效,需要额外的处理步骤和技巧,确保配置文件的正确生成。 提供的示例中, shell 脚本无法正常解析DART_DEFINES
环境变量并注入到 iOS 工程配置,说明需要在构建过程,以及文件生成时深入进行问题排查。
iOS 解决方案
以下是在 iOS 应用中安全注入 Google Maps API Key 的步骤和原理。
-
使用
.xcconfig
文件xcconfig
文件能够配置构建过程的环境变量,方便注入敏感数据。该文件可以指定Build Settings
。创建一个名为Dart-Defines.xcconfig
文件在ios/Flutter
路径下, 用来存放key, 如下例:
GOOGLE_MAPS_API=your_google_maps_api_key
将key放到配置文件可以避免在编译脚本硬编码
- 构建时处理脚本
- 需要脚本读取
.xcconfig
文件里的内容并导出为 iOS 工程Info.plist
和 swift 代码中可用变量。可以使用以下extract-defines.sh
脚本读取.xcconfig
文件的内容
#!/bin/sh
OUTPUT_FILE="${SRCROOT}/Flutter/Dart-Defines.xcconfig"
if [ ! -f "$OUTPUT_FILE" ]; then
echo "Error: $OUTPUT_FILE not found."
exit 1
fi
# Get the GOOGLE_MAPS_API key
GOOGLE_MAPS_API_KEY=$(grep "GOOGLE_MAPS_API=" "$OUTPUT_FILE" | sed 's/GOOGLE_MAPS_API=//')
if [ -z "$GOOGLE_MAPS_API_KEY" ]; then
echo "Error: GOOGLE_MAPS_API not defined in $OUTPUT_FILE."
exit 1
fi
# Export the value to the plist as GOOGLE_MAPS_API
/usr/libexec/PlistBuddy -c "Add :GOOGLE_MAPS_API string \"$GOOGLE_MAPS_API_KEY\"" "${SRCROOT}/Info.plist"
echo "Google Map Api Key successfully imported."
说明:
-
检查
.xcconfig
文件是否存在 -
使用
grep
命令查找 GOOGLE_MAPS_API 行, 并用sed
命令截取值 -
PlistBuddy
添加或者更改 plist 中的内容 -
然后添加构建过程的配置。 打开
ios/Runner.xcodeproj/project.pbxproj
找到PBXShellScriptBuildPhase
,然后将这个构建脚本添加到编译前的操作中。 可以修改build_phase
下的脚本:
shellScript = "\"${SRCROOT}/Flutter/extract-defines.sh\"";
**说明:**
- 使用该脚本解析
.xcconfig
文件 并导出到Info.plist
-
Info.plist
配置打开
ios/Runner/Info.plist
,添加GOOGLE_MAPS_API
键值。<key>GOOGLE_MAPS_API</key> <string>$(GOOGLE_MAPS_API)</string>
-
AppDelegate.swift
中初始化在
AppDelegate.swift
文件中,通过Bundle
读取配置:import Flutter import UIKit import GoogleMaps @main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { if let googleMapsApiKey = Bundle.main.object(forInfoDictionaryKey: "GOOGLE_MAPS_API") as? String { GMSServices.provideAPIKey(googleMapsApiKey) } return super.application(application, didFinishLaunchingWithOptions: launchOptions) } }
Android 解决方案
以下是在 Android 应用中安全注入 Google Maps API Key 的步骤和原理。
- 在
gradle.properties
配置环境变量
在 android/gradle.properties
中,添加 GOOGLE_MAPS_API
变量:
```gradle
GOOGLE_MAPS_API=your_google_maps_api_key
2. **在 `local.properties` 文件中定义变量**
此文件用于声明本地环境变量, 并在 `android/local.properties` 添加下面的变量:
```
GOOGLE_MAPS_API_KEY=your_google_maps_api_key
```
**说明:**
* 可以直接使用上面的文件而不用再去配置变量
* 请不要将此文件加入git版本控制
3. **`build.gradle` 配置**
在 `android/app/build.gradle` 中配置 `buildConfigField`, 访问这些变量。 在 `android/app/build.gradle` `android{}` 下面添加 `buildConfigField` 方法
```gradle
android {
...
def googleMapsApiKey = project.properties["GOOGLE_MAPS_API_KEY"] ?: 'DefaultKey'
buildConfigField("String", "GOOGLE_MAPS_API_KEY", "\"${googleMapsApiKey}\"")
...
}
**说明:**
- 通过
project.properties["GOOGLE_MAPS_API_KEY"]
读取配置文件中配置好的key
。 使用buildConfigField
函数, 将环境变量的值转化为 Java 常量供 App 调用。
-
使用配置
在你的 Android 代码 (MainActivity.kt
或MainActivity.java
) 初始化 google maps:import com.google.android.gms.maps.MapsInitializer override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) MapsInitializer.initialize(applicationContext, MapsInitializer.Renderer.LATEST) MapsInitializer.initialize(this, MapsInitializer.Renderer.LEGACY) if(BuildConfig.GOOGLE_MAPS_API_KEY.isNotBlank()){ com.google.android.gms.maps.MapsInitializer.initialize(this) GMSServices.setApiKey(BuildConfig.GOOGLE_MAPS_API_KEY) } }
说明:
- 从
BuildConfig
读取在build.gradle
中配置好的变量, 并将其作为API key, 注入给 google maps 服务
- 从
额外的安全建议
- 使用版本控制忽略敏感文件 :
gradle.properties
和local.properties
避免提交,确保.gitignore
包含相关文件。 - 限制 API Key 的使用范围 :通过 Google Cloud Console 为你的 API Key 设置应用程序限制、IP 地址限制和 HTTP 引用来源限制等。
- 使用安全的 API key 管理工具 :可以使用 HashiCorp Vault 之类的工具来进行更加复杂的 key 管理和轮换。
此解决方案在确保API密钥安全性的同时,可以避免构建问题。 通过利用xcconfig
和 gradle
配置文件,可以实现 API 密钥的动态注入,从而避免在源码中硬编码密钥,并提升应用程序的整体安全性和可维护性。