返回

Flutter 安全注入 Google Maps API Key (iOS & Android)

IOS

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 的步骤和原理。

  1. 使用 .xcconfig 文件

    xcconfig 文件能够配置构建过程的环境变量,方便注入敏感数据。该文件可以指定Build Settings。创建一个名为Dart-Defines.xcconfig文件在ios/Flutter路径下, 用来存放key, 如下例:

  GOOGLE_MAPS_API=your_google_maps_api_key

将key放到配置文件可以避免在编译脚本硬编码

  1. 构建时处理脚本
  • 需要脚本读取.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
  1. Info.plist配置

    打开 ios/Runner/Info.plist,添加 GOOGLE_MAPS_API 键值。

      <key>GOOGLE_MAPS_API</key>
      <string>$(GOOGLE_MAPS_API)</string>
    
  2. 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 的步骤和原理。

  1. 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 调用。
  1. 使用配置
    在你的 Android 代码 (MainActivity.ktMainActivity.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.propertieslocal.properties 避免提交,确保 .gitignore 包含相关文件。
  • 限制 API Key 的使用范围 :通过 Google Cloud Console 为你的 API Key 设置应用程序限制、IP 地址限制和 HTTP 引用来源限制等。
  • 使用安全的 API key 管理工具 :可以使用 HashiCorp Vault 之类的工具来进行更加复杂的 key 管理和轮换。

此解决方案在确保API密钥安全性的同时,可以避免构建问题。 通过利用xcconfiggradle 配置文件,可以实现 API 密钥的动态注入,从而避免在源码中硬编码密钥,并提升应用程序的整体安全性和可维护性。