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:
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.
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 :)
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.
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;
}
}