it-swarm.com.de

Einschränkung der Verwendung eines Android-Schlüssels für eine Google-API

Meine Frage bezieht sich darauf, wie der Paketname und der Fingerabdruck des SHA-1-Zertifikats in der Google Developers Console richtig festgelegt werden, um die Verwendung meines Android-API-Schlüssels für meine App einzuschränken.

Wenn im Abschnitt "Verwendung auf Android-Apps beschränken" nichts eingestellt ist, funktionieren meine Anforderungen an die Google Translate-API ordnungsgemäß. Die API antwortet normalerweise mit dem Statuscode 200 und meinem erwarteten Ergebnis.

Wenn ich jedoch einen Paketnamen und einen Fingerabdruck für das SHA-1-Zertifikat für meine App mithilfe der Entwicklerkonsole angeben, erhalte ich durchgängig 403 verbotene Antworten wie die folgenden:

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."
 }
}

Die Anfrage sieht wie folgt aus. Beachten Sie, dass sich in der Anforderung kein Referer-Header befindet:

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

Ich gehe davon aus, dass die Fehlernachricht einen Paketnamen oder ein SHA-1-Fingerabdruckproblem angibt, trotz der Meldung über eine "Einschränkung pro IP oder pro Referer". Während Browser-Tasten die Einstellung einer Einschränkung pro Referer zulassen, verwende ich eine Android-Taste, an der nirgends eine Einschränkung für IP oder pro Referer festgelegt werden kann.

Ich bin sicher, dass ich den Paketnamen korrekt in der Google Developers Console eingegeben habe. Ich lese den Paketnamen aus dem package-Attribut des manifest-Tags in meiner Android-Manifestdatei.

Ich bin auch sicher, dass ich den SHA-1-Fingerabdruck in der Google Developers Console richtig eingestellt habe. Ich lese diesen Wert aus meinem Schlüsselspeicher mit dem Befehl keytool -list -v -keystore /path/to/my/keystore. Ich erhalte den gleichen Wert, wenn ich sie mit keytool -list -printcert -jarfile myAppName.apk aus der APK-Datei lese. Ich installiere dieselbe APK-Datei mit Adb.

Folgendes sehe ich in der Entwicklerkonsole:

 console screenshot

Ich habe dies auf mehreren Geräten getestet, auf denen Android läuft. Ich erhalte die Fehlerantwort auf WLAN und auf dem Zellennetzwerk, unabhängig davon, ob ich den Datenverkehr annehme oder nicht.

Wenn ich die Einschränkung aus der Entwicklerkonsole entferne, funktioniert die App wieder einwandfrei.

Was mache ich hier falsch?

Hinweis: MehrereähnlicheFragenwurdengefragtvor , abermitneinausreichendantworten . Ich möchte keinen Browserschlüssel verwenden oder die Einschränkung vollständig entfernen. Ich möchte, dass die Nutzungsbeschränkung ordnungsgemäß funktioniert.

17
rmtheis

Alles, was Sie in der Google Developer Console getan haben, um die Verwendung Ihres API-Schlüssels für die Android-App zu beschränken, ist in Ordnung. Nach der Einschränkung akzeptiert dieser API-Schlüssel nur Anforderungen von Ihrer App mit dem angegebenen Paketnamen und dem Fingerabdruck des SHA-1-Zertifikats.

Woher weiß Google, dass die Anfrage von Ihrer Android-App gesendet wurde? Sie MÜSSEN den Paketnamen Ihrer App und SHA-1 im Kopf jeder Anfrage hinzufügen (offensichtlich). Und Sie benötigen keine GoogleAuthUtil- und GET_ACCOUNTS-Berechtigung.

ERSTE, hol dir deine App SHA Signatur (du brauchst Guava library):

/**
 * 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;
    }
}

Fügen Sie dann den Paketnamen und SHA Zertifikatsignatur zum Anforderungsheader hinzu:

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();
}

Wenn Sie die Google Vision-API verwenden, können Sie Ihre Anfrage auch mit VisionRequestInitializer erstellen:

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());
}

Fügen Sie Ihrem Gradle folgende Abhängigkeiten hinzu:

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'

Ich hoffe das hilft :)

17
Duy Pham

Wenn Sie eine reine Google-REST-API wie Translate verwenden, müssen Sie GoogleAuthUtil verwenden, wodurch ein Token für einen bestimmten Nutzer und ein bestimmtes Paket/Fingerabdruck generiert wird. Dies erfordert jedoch eine GET_ACCOUNTS-Berechtigung, derer sich intelligente Benutzer scheuen. 

Sie können auch die getAuthToken()-Methode der Variable AccountManager verwenden. Dies würde jedoch nicht nur die GET_ACCOUNTS-Berechtigung, sondern auch USE_CREDENTIALS erfordern. 

Am besten können Sie einen API-Schlüssel verwenden und diesen etwas verdecken.

7
323go

Paketbeschränkung und URL-Signatur

Als ich auf diesen Beitrag stieß, als ich mit der Einschränkung des Zugriffs für inverse Geokodierung und statische Map-APIs zu kämpfen hatte, möchte ich auch meine Ergebnisse mitteilen.

Beachten Sie, dass nicht alle Google-Services dieselben Einschränkungen zulassen.

Wir verwenden die URL-Signatur und die Einschränkung für Android/iOS-Pakete. Link zur Google-Dokumentation

apk-Fingerabdruck abrufen

Es gibt mehrere Möglichkeiten, den Fingerabdruck von der Android-App zu erhalten.

Mit Schlüsselspeicher

keytool -list -v keystore mystore.keystore

Mit apk

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

C # Beispielcode (Xamarin) zum Einstieg

In meinem Produktivcode habe ich eine Basisklasse für Headerinfo und verleiht der Geoprovider-Klasse einen Einstieg. Bei diesem Ansatz wird der Code für die Google-Dienste zu 100% von Windows, Android und ios => Nuget-Paket geteilt.

Android-Header

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

IOS Header

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

Beispielcode zum Abrufen einer statischen Karte

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
}

Beispielcode für die von Google bereitgestellte URL-Signatur

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