限制 Android 键用于 Google API

Restricting usage for an Android key for a Google API

我的问题是关于如何在 Google Developers Console 中正确设置包名和 SHA-1 证书指纹以限制我的 Android API 密钥的使用我的应用程序。

当我没有在 "Restrict usage to your Android apps" 部分设置任何内容时,我对 Google 翻译 API 的请求可以正常工作。 API 正常响应状态代码 200 和我的预期结果。

但是,当我使用 Developers Console 为我的应用程序指定程序包名称和 SHA-1 证书指纹时,我始终会收到 403 Forbidden 响应,如下所示:

HTTP/1.1 403 Forbidden
Vary: Origin
Vary: X-Origin
Content-Type: application/json; charset=UTF-8
Date: Sun, 29 Nov 2015 21:01:39 GMT
Expires: Sun, 29 Nov 2015 21:01:39 GMT
Cache-Control: private, max-age=0
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Server: GSE
Alternate-Protocol: 443:quic,p=1
Alt-Svc: quic=":443"; ma=604800; v="30,29,28,27,26,25"
Content-Length: 729

{
 "error": {
  "errors": [
   {
    "domain": "usageLimits",
    "reason": "ipRefererBlocked",
    "message": "There is a per-IP or per-Referer restriction configured on your API key and the request does not match these restrictions. Please use the Google Developers Console to update your API key configuration if request from this IP or referer should be allowed.",
    "extendedHelp": "https://console.developers.google.com"
   }
  ],
  "code": 403,
  "message": "There is a per-IP or per-Referer restriction configured on your API key and the request does not match these restrictions. Please use the Google Developers Console to update your API key configuration if request from this IP or referer should be allowed."
 }
}

请求如下所示。请注意,请求中没有 referer header:

GET https://www.googleapis.com/language/translate/v2?key=XXXXXXXXXXXXXXXXXXXXXXXX-XXXXXXXXXXXXXX&source=en&target=es&q=test HTTP/1.1
User-Agent: Dalvik/2.1.0 (Linux; U; Android 5.1.1; Nexus 6 Build/LVY48H)
Host: www.googleapis.com
Connection: Keep-Alive
Accept-Encoding: gzip

我假设错误消息指示包名称或 SHA-1 指纹问题,尽管它的消息是关于 "per-IP or per-Referer restriction"。虽然浏览器键允许设置 per-referer 限制,但我使用的是 Android 键,无处可设置 per-IP 或 per-Referer 限制。

我确定我在 Google 开发者控制台中输入的包名是正确的。我正在从 Android 清单文件中 manifest 标签的 package 属性中读取包名称。

我还确定我在 Google 开发者控制台中正确设置了 SHA-1 指纹。我正在使用命令 keytool -list -v -keystore /path/to/my/keystore 从我的密钥库中读取此值。当我使用 keytool -list -printcert -jarfile myAppName.apk 从 APK 文件中读取它时,我得到了相同的值。我正在使用 adb 安装相同的 APK 文件。

这是我在 Developers Console 中看到的内容:

我已经在多台设备 运行 stock Android 上对此进行了测试。我在 wifi 和蜂窝网络上收到错误响应,无论我是否在代理流量。

当我从 Developers Console 中删除限制后,应用程序再次正常运行。

我做错了什么?

注:Several have , but with no adequate answers。我不想使用浏览器密钥或完全取消限制。我想让使用限制正常工作。

在使用 Google REST-only API 时,例如翻译,您需要使用 GoogleAuthUtil,这将为特定用户生成令牌,并且 package/fingerprint。但是,这需要 GET_ACCOUNTS 许可,聪明的用户对此很警惕。

您也可以使用 AccountManagergetAuthToken() 方法,但这不仅需要 GET_ACCOUNTS 许可,还需要 USE_CREDENTIALS 许可。

您最好使用 API 键并将其隐藏一点。

您在 Google 开发者控制台上为限制 Android 应用程序使用 api 密钥所做的一切都没有问题。限制后,此 API 密钥将只接受来自您的应用程序的请求,其中指定了程序包名称和 SHA-1 证书指纹。

那么 google 如何知道请求是从您的 ANDROID 应用程序发送的?您必须在每个请求的 header 中添加您的应用程序包名称和 SHA-1(很明显)。而且您不需要 GoogleAuthUtilGET_ACCOUNTS 许可。

首先,获取您的应用 SHA 签名(您将需要 Guava 库):

/**
 * Gets the SHA1 signature, hex encoded for inclusion with Google Cloud Platform API requests
 *
 * @param packageName Identifies the APK whose signature should be extracted.
 * @return a lowercase, hex-encoded
 */
public static String getSignature(@NonNull PackageManager pm, @NonNull String packageName) {
    try {
        PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
        if (packageInfo == null
                || packageInfo.signatures == null
                || packageInfo.signatures.length == 0
                || packageInfo.signatures[0] == null) {
            return null;
        }
        return signatureDigest(packageInfo.signatures[0]);
    } catch (PackageManager.NameNotFoundException e) {
        return null;
    }
}

private static String signatureDigest(Signature sig) {
    byte[] signature = sig.toByteArray();
    try {
        MessageDigest md = MessageDigest.getInstance("SHA1");
        byte[] digest = md.digest(signature);
        return BaseEncoding.base16().lowerCase().encode(digest);
    } catch (NoSuchAlgorithmException e) {
        return null;
    }
}

然后,添加包名和SHA证书签名请求header:

java.net.URL url = new URL(REQUEST_URL);
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
try {
    connection.setDoInput(true);
    connection.setDoOutput(true);

    connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
    connection.setRequestProperty("Accept", "application/json");

    // add package name to request header
    String packageName = mActivity.getPackageName();
    connection.setRequestProperty("X-Android-Package", packageName);
    // add SHA certificate to request header
    String sig = getSignature(mActivity.getPackageManager(), packageName);
    connection.setRequestProperty("X-Android-Cert", sig);
    connection.setRequestMethod("POST");

    // ADD YOUR REQUEST BODY HERE
    // ....................
} catch (Exception e) {
    e.printStackTrace();
} finally {
    connection.disconnect();
}

其他方式,如果您使用 Google Vision API,您可以使用 VisionRequestInitializer:

构建您的请求
try {
    HttpTransport httpTransport = AndroidHttp.newCompatibleTransport();
    JsonFactory jsonFactory = GsonFactory.getDefaultInstance();

    VisionRequestInitializer requestInitializer =
    new VisionRequestInitializer(CLOUD_VISION_API_KEY) {
    /**
         * We override this so we can inject important identifying fields into the HTTP
         * headers. This enables use of a restricted cloud platform API key.
         */
        @Override
        protected void initializeVisionRequest(VisionRequest<?> visionRequest)
            throws IOException {
            super.initializeVisionRequest(visionRequest);

            String packageName = mActivity.getPackageName();
            visionRequest.getRequestHeaders().set("X-Android-Package", packageName);

            String sig = getSignature(mActivity.getPackageManager(), packageName);
            visionRequest.getRequestHeaders().set("X-Android-Cert", sig);
        }
    };

    Vision.Builder builder = new Vision.Builder(httpTransport, jsonFactory, null);
    builder.setVisionRequestInitializer(requestInitializer);

    Vision vision = builder.build();

    BatchAnnotateImagesRequest batchAnnotateImagesRequest =
    new BatchAnnotateImagesRequest();
    batchAnnotateImagesRequest.setRequests(new ArrayList<AnnotateImageRequest>() {{
    AnnotateImageRequest annotateImageRequest = new AnnotateImageRequest();

    // Add the image
    Image base64EncodedImage = new Image();
    // Convert the bitmap to a JPEG
    // Just in case it's a format that Android understands but Cloud Vision
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    requestImage.compress(Bitmap.CompressFormat.JPEG, IMAGE_JPEG_QUALITY, byteArrayOutputStream);
    byte[] imageBytes = byteArrayOutputStream.toByteArray();

    // Base64 encode the JPEG
    base64EncodedImage.encodeContent(imageBytes);
    annotateImageRequest.setImage(base64EncodedImage);

    // add the features we want
    annotateImageRequest.setFeatures(new ArrayList<Feature>() {{
    Feature labelDetection = new Feature();
    labelDetection.setType(TYPE_TEXT_DETECTION);
    add(labelDetection);
    }});

    // Add the list of one thing to the request
    add(annotateImageRequest);
    }});

    Vision.Images.Annotate annotateRequest =
    vision.images().annotate(batchAnnotateImagesRequest);
    // Due to a bug: requests to Vision API containing large images fail when GZipped.
    annotateRequest.setDisableGZipContent(true);
    Log.d("TAG_SERVER", "created Cloud Vision request object, sending request");

    BatchAnnotateImagesResponse response = annotateRequest.execute();
        return convertResponseToString(response);
    } catch (GoogleJsonResponseException e) {
        Log.d("TAG_SERVER", "failed to make API request because " + e.getContent());
    } catch (IOException e) {
        Log.d("TAG_SERVER", "failed to make API request because of other IOException " +
        e.getMessage());
}

将以下依赖项添加到您的 gradle:

compile 'com.google.apis:google-api-services-vision:v1-rev2-1.21.0'
compile 'com.google.api-client:google-api-client-android:1.20.0' exclude module: 'httpclient'
compile 'com.google.http-client:google-http-client-gson:1.20.0' exclude module: 'httpclient'

希望对您有所帮助:)

包裹限制和Url签名

当我在努力限制对反向地理编码和静态地图的访问时遇到这个 post api 我也想分享我的发现。

请注意,并非所有 google 服务都允许相同的限制。

我们使用 url 签名和 android / ios 包限制。 Link to the Google documentation

获取apk指纹

有多种方法可以从 android apk 中获取指纹。

带密钥库

keytool -list -v keystore mystore.keystore

有apk

extract *.apk
navigate to folder META-INF
keytool.exe" -printcert -file *.RSA

C# 示例代码 (Xamarin) 入门

在我的生产代码中,我有一个 Headerinfo 的基础 class,并为 Geoprovider class 提供了一个实例。 通过这种方法,google 服务的代码在 windows、android 和 ios 之间 100% 共享 => nuget 包。

Android Headers

httpWebRequest.Headers["x-android-package"] = "packageName";
httpWebRequest.Headers["x-android-package"] = "signature";

IOS Headers

httpWebRequest.Headers["x-ios-bundle-identifier"] = "bundleIdentifier";

获取静态地图的示例代码

public byte[] GenerateMap(double latitude, double longitude, int zoom, string size, string mapType)
{
    string lat = latitude.ToString(CultureInfo.InvariantCulture);
    string lng = longitude.ToString(CultureInfo.InvariantCulture);
    string url = $"https://maps.googleapis.com/maps/api/staticmap?center={lat},{lng}&zoom={zoom}&size={size}&maptype={mapType}&markers={lat},{lng}&key={_apiKey}";

    // get the secret from your firebase console don't create always an new instance in productive code
    string signedUrl = new GoogleUrlSigner("mysecret").Sign(url);

    HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(signedUrl);

    //Add your headers httpWebRequest.Headers...

    // get the response for the request
    HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();

    // do whatever you want to do with the response
}

url签名示例代码由google

提供

https://developers.google.com/maps/documentation/geocoding/get-api-key

internal class GoogleUrlSigner
{
    private readonly string _secret;

    public GoogleUrlSigner(string secret)
    {
        _secret = secret;
    }

    internal string Sign(string url)
    {
        ASCIIEncoding encoding = new ASCIIEncoding();

        // converting key to bytes will throw an exception, need to replace '-' and '_' characters first.
        string usablePrivateKey = _secret.Replace("-", "+").Replace("_", "/");
        byte[] privateKeyBytes = Convert.FromBase64String(usablePrivateKey);

        Uri uri = new Uri(url);
        byte[] encodedPathAndQueryBytes = encoding.GetBytes(uri.LocalPath + uri.Query);

        // compute the hash
        HMACSHA1 algorithm = new HMACSHA1(privateKeyBytes);
        byte[] hash = algorithm.ComputeHash(encodedPathAndQueryBytes);

        // convert the bytes to string and make url-safe by replacing '+' and '/' characters
        string signature = Convert.ToBase64String(hash).Replace("+", "-").Replace("/", "_");

        // Add the signature to the existing URI.
        return uri.Scheme + "://" + uri.Host + uri.LocalPath + uri.Query + "&signature=" + signature;
    }
}

直接从您的代码中点击 API 而不是通过 Google-provided 中间 SDK 意味着没有可用的机制来安全地获取您的应用程序的证书指纹并将该指纹传递给 API。另一方面,当您使用提供的 Android SDK 之一而不是直接点击 API 时——例如,当您使用 Android Google 发送请求时Maps SDK - SDK 可以处理获取应用程序的证书指纹,以便应用程序限制按预期工作。

Google 开发者控制台在这方面具有误导性,因为它的某些 API 允许开发者根据 Android 应用程序证书指纹设置密钥限制,但随后没有为 Android 提供能够在运行时检查该指纹的 SDK。那么,开发人员剩下的是更糟糕、更不安全的选择,即发送 X-Android-Cert 和 X-Android-Package headers 以及他们的请求,如此处其他答案所述。

因此,对于 APIs,没有随附的 Android SDK 来处理应用程序证书指纹的检查已经发布,事实证明没有隐藏的简单方法来获得类似的东西,比方说,Google Play Services 来处理获取应用程序的证书指纹,以便正确使用应用程序密钥限制——只是没有办法做到这一点。

如果您使用的是 appBundle 而不是普通的 apk 文件,您还需要从 play.google.com/console/:

获取 SHA-1

然后将其与您的包裹一起添加到 console.developers.google.com/apis/credentials

希望它能为某人省心...