rmt*_*eis 23 android google-api google-translate google-cloud-platform
我的问题是如何在Google Developers Console中正确设置软件包名称和SHA-1证书指纹,以限制我的Android API密钥在我的应用中的使用.
如果我在"限制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."
}
}
Run Code Online (Sandbox Code Playgroud)
请求如下所示.请注意,请求中没有referer标头:
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
Run Code Online (Sandbox Code Playgroud)
我假设错误消息指示包名称或SHA-1指纹问题,尽管它有关于"每IP或每个Referer限制"的消息.虽然浏览器密钥允许设置每个引用限制,但我使用Android密钥无处可设置每IP或每个Referer限制.
我确定我已在Google Developers Console中正确输入了软件包名称.我正在从我的Android清单文件中package的manifest标签上的属性中读取包名称.
我也确定我在Google Developers Console中正确设置了SHA-1指纹.我正在使用该命令从我的密钥库中读取此值keytool -list -v -keystore /path/to/my/keystore.当我从APK文件中读取它时,我得到相同的值keytool -list -printcert -jarfile myAppName.apk.我正在使用adb安装相同的APK文件.
这是我在开发者控制台中看到的内容:
我已经在运行Android的多台设备上测试了这个.我在wifi和蜂窝网络上得到错误响应,无论我是否代理流量.
当我从开发者控制台中删除限制时,该应用程序再次正常工作.
我在这做错了什么?
注:有几个 类似的 问题 已经 被 问 之前,但 有 没有 足够的 答案.我不想使用浏览器密钥或完全取消限制.我想让使用限制正常工作.
Duy*_*ham 21
您在Google Developer Console上为限制使用Android应用的api密钥所做的一切都可以.限制后,此API密钥将仅接受来自您的应用程序的请求,其中包含指定的包名称和SHA-1证书指纹.
那么Google如何知道您的ANDROID APP发送的请求?您必须在每个请求的标题中添加您的应用程序包名称和SHA-1(显然).而且你不需要GoogleAuthUtil和GET_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;
}
}
Run Code Online (Sandbox Code Playgroud)
然后,将包名称和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();
}
Run Code Online (Sandbox Code Playgroud)
其他方式,如果您使用的是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());
}
Run Code Online (Sandbox Code Playgroud)
将以下依赖项添加到您的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'
Run Code Online (Sandbox Code Playgroud)
希望这有帮助:)
使用Google REST专用API(例如翻译)时,您需要使用GoogleAuthUtil,这将为特定用户和包/指纹生成令牌.但是,这需要GET_ACCOUNTS许可,智能用户需要谨慎.
你也可以使用AccountManager的getAuthToken()方法,但这需要不仅GET_ACCOUNTS允许,而且还USE_CREDENTIALS.
您可能最好使用API密钥并稍微模糊一下.
直接从代码访问 API,而不是通过 Google 提供的中间 SDK,意味着没有可用的机制来安全地获取应用程序的证书指纹并将该指纹传递给 API。另一方面,当您使用提供的 Android SDK 之一而不是直接访问 API\xe2\x80\x94 时,例如,当您使用 Android Google Maps SDK\xe2\x80\x94 发送请求时,SDK 可以处理获取您的应用程序的证书指纹,以便应用程序限制按预期工作。
\n\nGoogle Developers Console 在这方面具有误导性,因为对于其某些 API,它允许开发人员根据 Android 应用程序证书指纹设置关键限制,但不会提供能够检查该指纹的 Android SDK在运行时。那么,开发人员剩下的就是更糟糕、更不安全的选择,即随请求一起发送 X-Android-Cert 和 X-Android-Package 标头,如此处另一个答案中所述。
\n\n因此,对于没有发布相应的 Android SDK 来处理应用程序证书指纹检查的 API,事实证明,没有隐藏的简单方法可以让 Google Play Services 之类的东西来处理按顺序获取应用程序的证书指纹要正确使用应用程序密钥限制\xe2\x80\x94,只是没有办法做到这一点。
\n包限制和 URL 签名
当我在努力限制反向地理编码和静态地图 api 的访问时遇到这篇文章时,我也想分享我的发现。
请注意,并非所有谷歌服务都允许相同的限制。
我们使用 url 签名和 android/ios 包限制。 链接至 Google 文档
获取apk指纹
有多种方法可以从 Android apk 中获取指纹。
带密钥库
keytool -list -v keystore mystore.keystore
Run Code Online (Sandbox Code Playgroud)
带APK
extract *.apk
navigate to folder META-INF
keytool.exe" -printcert -file *.RSA
Run Code Online (Sandbox Code Playgroud)
C# 入门示例代码 (Xamarin)
在我的生产代码中,我有一个 Headerinfo 的基类,并为 Geoprovider 类提供一个实例。通过这种方法,Google 服务的代码在 Windows、Android 和 ios => nuget 包之间 100% 共享。
Android 标头
httpWebRequest.Headers["x-android-package"] = "packageName";
httpWebRequest.Headers["x-android-package"] = "signature";
Run Code Online (Sandbox Code Playgroud)
iOS 标头
httpWebRequest.Headers["x-ios-bundle-identifier"] = "bundleIdentifier";
Run Code Online (Sandbox Code Playgroud)
获取静态地图的示例代码
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
}
Run Code Online (Sandbox Code Playgroud)
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;
}
}
Run Code Online (Sandbox Code Playgroud)
如果您使用 appBundle 而不是普通的 apk 文件,您还需要从以下位置获取 SHA-1 play.google.com/console/:
然后将其与您的包一起添加到console.developers.google.com/apis/credentials
希望它能为某人减轻紧张......
| 归档时间: |
|
| 查看次数: |
8927 次 |
| 最近记录: |