web-dev-qa-db-ja.com

Google APIのAndroidキーの使用を制限する

私の質問は、Android APIキーをアプリに使用することを制限するために、Google Developers Consoleでパッケージ名とSHA-1証明書フィンガープリントを適切に設定する方法についてです。

「Androidアプリ)の使用を制限する」セクションで何も設定していない場合、Google Translate 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."
 }
}

リクエストは次のようになります。リクエストにリファラーヘッダーがないことに注意してください。

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

「IPごとまたはリファラーごとの制限」についてのメッセージにもかかわらず、エラーメッセージがパッケージ名またはSHA-1フィンガープリントの問題を示していると想定しています。ブラウザーのキーではリファラーごとの制限を設定できますが、Androidキーを使用して、IPごとまたはリファラーごとの制限を設定する場所がありません。

Google Developers Consoleでパッケージ名を正しく入力したはずです。 Androidマニフェストファイルのpackageタグのmanifest属性からパッケージ名を読み取っています。

また、Google Developers ConsoleでSHA-1フィンガープリントが正しく設定されていると確信しています。コマンドkeytool -list -v -keystore /path/to/my/keystoreを使用して、キーストアからこの値を読み取っています。 keytool -list -printcert -jarfile myAppName.apkを使用してAPKファイルから読み取ると、同じ値が得られます。私はadbを使用して同じAPKファイルをインストールしています。

Developers Consoleに表示される内容は次のとおりです。

console screenshot

私はこれを、ストックAndroidを実行している複数のデバイスでテストしました。トラフィックをプロキシしているかどうかに関係なく、wifiとセルネットワークでエラー応答が表示されます。

Developers Consoleから制限を削除すると、アプリは再び正常に機能します。

ここで何が悪いのですか?

注: いくつか類似質問持っているbeen質問されたbeforebutwithnoadequateanswers 。ブラウザのキーを使用したり、制限を完全に解除したりしたくありません。使用制限を適切に機能させたい。

25
rmtheis

Googleが提供する中間SDKを経由するのではなく、コードから直接APIにアクセスするということは、アプリの証明書のフィンガープリントを安全に取得してAPIに渡すためのメカニズムが利用できないことを意味します。一方、APIを直接押すのではなく、提供されているAndroid SDKのいずれかを使用している場合、たとえばAndroid = Google Maps SDK-SDKはアプリの証明書フィンガープリントの取得を処理できるため、アプリの制限が意図したとおりに機能します。

Google Developers Consoleは、この点で誤解を招きます。APIの一部では、Androidアプリ証明書のフィンガープリントに基づいてキー制限を設定できますが、 AndroidのSDKは実行時にそのフィンガープリントをチェックできます。開発者に残されているのは、X-Android-CertとX-Android-Packageを送信するという、より安全性の低いオプションです。ここで他の回答で説明されているように、ヘッダーをリクエストと一緒に。

したがって、付随するAndroid SDKでアプリ証明書のフィンガープリントのチェックを処理するSDKが公開されていないAPIの場合、Google Play Servicesなどの簡単な方法を入手する方法はないことがわかりますアプリのキーの制限を適切に使用するためにアプリの証明書のフィンガープリントを取得することを処理するために、これを行う方法はありません。

1
rmtheis

AndroidアプリのAPIキーの使用を制限するためにGoogle Developer Consoleで行ったすべての操作は問題ありません。制限後、このAPIキーは、パッケージ名とSHA- 1つの証明書のフィンガープリントが指定されています。

したがって、Googleはどのようにしてリクエストが自分から送信されたかを確認しますAndroid APP?アプリのパッケージ名と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=証明書の署名をリクエストヘッダーに追加します。

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'

この助けを願っています:)

23
Duy Pham

TranslateなどのGoogle REST専用APIを使用する場合は、特定のユーザーとパッケージ/フィンガープリントのトークンを生成する GoogleAuthUtil を使用する必要があります。ただし、そのためには_GET_ACCOUNTS_権限が必要であり、スマートユーザーはこれに注意する必要があります。

AccountManagergetAuthToken()メソッドを使用することもできますが、これには_GET_ACCOUNTS_権限だけでなく_USE_CREDENTIALS_も必要です。

APIキーを使用して、少しあいまいにするのが最善の方法です。

8
323go

パッケージ制限とURL署名

逆ジオエンコーディングと静的マップAPIへのアクセス制限に苦労してこの投稿に出くわしたので、私の発見を共有したいと思います。

すべてのGoogleサービスが同じ制限を許可するわけではないことに注意してください。

URL署名とAndroid/iosパッケージ制限を使用します。 Googleドキュメントへのリンク

apkフィンガープリントを取得

Android apk。から指紋を取得する方法は複数あります。

with keystore

keytool -list -v keystore mystore.keystore

with apk

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

C#開始するコードの例(Xamarin)

私の生産的なコードには、Headerinfoの基本クラスがあり、Geoproviderクラスにインスタンスを提供します。このアプローチでは、Googleサービスのコードはウィンドウ間で100%共有されます。Androidおよびios => nugetパッケージ。

Androidヘッダー

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

IOSヘッダー

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
}

googleが提供するURL署名のサンプルコード

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;
    }
}
2
eugstman