重要提示:Google Maps Platform 专业版方案不再开放注册,也不再提供给新客户。
数字签名
数字签名的运作方式
您可以使用 Google Cloud 控制台中的网址签名密钥(也称为加密密钥)来生成数字签名。该密钥本质上是一种私钥,仅在您与 Google 之间共享,并且归您的客户端 ID 独有。
签名流程使用一种加密算法将网址与您的共享密钥组合起来。借助生成的唯一签名,我们的服务器可验证任何使用您的客户端 ID 生成请求的网站是否获得了相应授权。
对请求进行签名
对请求进行签名包括以下步骤:
- 第 1 步:获取您的网址签名密钥
- 第 2 步:构建未签名的请求
- 第 3 步:生成已签名的请求
第 1 步:获取您的网址签名密钥
如需获取项目的网址签名密钥,请按以下步骤操作:
- 前往 Cloud 控制台中的“客户端 ID”页面。
- 密钥字段包含您当前的客户端 ID 网址签名密钥。
如果您需要重新生成客户端 ID 网址签名密钥,请与支持团队联系。
第 2 步:构建未签名的请求
必须对下表中未列出的字符进行网址编码:
字符集 | 字符 | 在网址中的用法 |
---|---|---|
字母数字 | a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 | 文本字符串、在 scheme 中使用 (http )、端口 (8080 ) 等 |
非预留字符 | - _ . ~ | 文本字符串 |
预留字符 | ! * ' ( ) ; : @ & = + $ , / ? % # [ ] | 控制字符和/或文本字符串 |
此要求也适用于预留字符集中的所有字符(如果相应字符是在文本字符串内传递的)。如需了解详情,请参阅特殊字符。
构建不带签名的未签名请求网址。
此外,还请务必在 client
参数中添加客户端 ID。例如:
https://maps.googleapis.com/maps/api/staticmap?center=Z%C3%BCrich&size=400x400&client=YOUR_CLIENT_ID
生成已签名请求
为便于排查问题,您可以使用可用的立即对网址进行签名 widget 自动生成数字签名。
对于动态生成的请求,您需要在服务器端进行签名,这需要执行一些额外的中间步骤。
无论采用上述哪种方式,最终都应在您的请求网址末尾附加一个 signature
参数。例如:
https://maps.googleapis.com/maps/api/staticmap?center=Z%C3%BCrich&size=400x400&client=YOUR_CLIENT_ID &signature=BASE64_SIGNATURE
-
去除网址的协议 scheme 和主机部分,只留下路径和查询:
-
显示的网址签名密钥采用改良版网址 Base64 进行编码。
由于大多数加密库都要求密钥采用原始字节格式,因此您可能需要先将网址签名密钥解码为其最初的原始格式,然后再进行签名。
- 使用 HMAC-SHA1 对上述执行了去除操作的请求进行签名。
-
由于大多数加密库都会生成采用原始字节格式的签名,因此您需要利用改良版网址 Base64,将生成的二进制签名转换成可在网址内传递的内容。
-
将 Base64 编码的签名附加到原始未签名请求网址的
signature
参数中。例如:https://maps.googleapis.com/maps/api/staticmap?center=Z%C3%BCrich&size=400x400&client=
YOUR_CLIENT_ID &signature=BASE64_SIGNATURE
/maps/api/staticmap?center=Z%C3%BCrich&size=400x400&client=YOUR_CLIENT_ID
如需查看示例,了解如何使用服务器端代码实现网址签名,请参阅下面的网址签名示例代码。
网址签名示例代码
以下各部分展示了使用服务器端代码实现网址签名的方法。应始终在服务器端对网址进行签名,以免将您的网址签名密钥暴露给用户。
下例使用标准 Python 库对网址进行签名(下载代码)。
#!/usr/bin/python
# -*- coding: utf-8 -*-
""" Signs a URL using a URL signing secret """
import hashlib
import hmac
import base64
import urllib.parse as urlparse
def sign_url(input_url=None, secret=None):
""" Sign a request URL with a URL signing secret.
Usage:
from urlsigner import sign_url
signed_url = sign_url(input_url=my_url, secret=SECRET)
Args:
input_url - The URL to sign
secret - Your URL signing secret
Returns:
The signed request URL
"""
if not input_url or not secret:
raise Exception("Both input_url and secret are required")
url = urlparse.urlparse(input_url)
# We only need to sign the path+query part of the string
url_to_sign = url.path + "?" + url.query
# Decode the private key into its binary format
# We need to decode the URL-encoded private key
decoded_key = base64.urlsafe_b64decode(secret)
# Create a signature using the private key and the URL-encoded
# string using HMAC SHA1. This signature will be binary.
signature = hmac.new(decoded_key, str.encode(url_to_sign), hashlib.sha1)
# Encode the binary signature into base64 for use within a URL
encoded_signature = base64.urlsafe_b64encode(signature.digest())
original_url = url.scheme + "://" + url.netloc + url.path + "?" + url.query
# Return signed URL
return original_url + "&signature=" + encoded_signature.decode()
if __name__ == "__main__":
input_url = input("URL to Sign: ")
secret = input("URL signing secret: ")
print("Signed URL: " + sign_url(input_url, secret))
下例使用从 JDK 1.8 开始提供的 java.util.Base64
类,旧版本可能需要使用 Apache Commons 或类似工具(下载代码)。
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64; // JDK 1.8 only - older versions may need to use Apache Commons or similar.
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class UrlSigner {
// Note: Generally, you should store your private key someplace safe
// and read them into your code
private static String keyString = "YOUR_PRIVATE_KEY";
// The URL shown in these examples is a static URL which should already
// be URL-encoded. In practice, you will likely have code
// which assembles your URL from user or web service input
// and plugs those values into its parameters.
private static String urlString = "YOUR_URL_TO_SIGN";
// This variable stores the binary key, which is computed from the string (Base64) key
private static byte[] key;
public static void main(String[] args) throws IOException,
InvalidKeyException, NoSuchAlgorithmException, URISyntaxException {
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
String inputUrl, inputKey = null;
// For testing purposes, allow user input for the URL.
// If no input is entered, use the static URL defined above.
System.out.println("Enter the URL (must be URL-encoded) to sign: ");
inputUrl = input.readLine();
if (inputUrl.equals("")) {
inputUrl = urlString;
}
// Convert the string to a URL so we can parse it
URL url = new URL(inputUrl);
// For testing purposes, allow user input for the private key.
// If no input is entered, use the static key defined above.
System.out.println("Enter the Private key to sign the URL: ");
inputKey = input.readLine();
if (inputKey.equals("")) {
inputKey = keyString;
}
UrlSigner signer = new UrlSigner(inputKey);
String request = signer.signRequest(url.getPath(),url.getQuery());
System.out.println("Signed URL :" + url.getProtocol() + "://" + url.getHost() + request);
}
public UrlSigner(String keyString) throws IOException {
// Convert the key from 'web safe' base 64 to binary
keyString = keyString.replace('-', '+');
keyString = keyString.replace('_', '/');
System.out.println("Key: " + keyString);
// Base64 is JDK 1.8 only - older versions may need to use Apache Commons or similar.
this.key = Base64.getDecoder().decode(keyString);
}
public String signRequest(String path, String query) throws NoSuchAlgorithmException,
InvalidKeyException, UnsupportedEncodingException, URISyntaxException {
// Retrieve the proper URL components to sign
String resource = path + '?' + query;
// Get an HMAC-SHA1 signing key from the raw key bytes
SecretKeySpec sha1Key = new SecretKeySpec(key, "HmacSHA1");
// Get an HMAC-SHA1 Mac instance and initialize it with the HMAC-SHA1 key
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(sha1Key);
// compute the binary signature for the request
byte[] sigBytes = mac.doFinal(resource.getBytes());
// base 64 encode the binary signature
// Base64 is JDK 1.8 only - older versions may need to use Apache Commons or similar.
String signature = Base64.getEncoder().encodeToString(sigBytes);
// convert the signature to 'web safe' base 64
signature = signature.replace('+', '-');
signature = signature.replace('/', '_');
return resource + "&signature=" + signature;
}
}
下例使用原生节点模块对网址进行签名(下载代码)。
'use strict'
const crypto = require('crypto');
const url = require('url');
/**
* Convert from 'web safe' base64 to true base64.
*
* @param {string} safeEncodedString The code you want to translate
* from a web safe form.
* @return {string}
*/
function removeWebSafe(safeEncodedString) {
return safeEncodedString.replace(/-/g, '+').replace(/_/g, '/');
}
/**
* Convert from true base64 to 'web safe' base64
*
* @param {string} encodedString The code you want to translate to a
* web safe form.
* @return {string}
*/
function makeWebSafe(encodedString) {
return encodedString.replace(/\+/g, '-').replace(/\//g, '_');
}
/**
* Takes a base64 code and decodes it.
*
* @param {string} code The encoded data.
* @return {string}
*/
function decodeBase64Hash(code) {
// "new Buffer(...)" is deprecated. Use Buffer.from if it exists.
return Buffer.from ? Buffer.from(code, 'base64') : new Buffer(code, 'base64');
}
/**
* Takes a key and signs the data with it.
*
* @param {string} key Your unique secret key.
* @param {string} data The url to sign.
* @return {string}
*/
function encodeBase64Hash(key, data) {
return crypto.createHmac('sha1', key).update(data).digest('base64');
}
/**
* Sign a URL using a secret key.
*
* @param {string} path The url you want to sign.
* @param {string} secret Your unique secret key.
* @return {string}
*/
function sign(path, secret) {
const uri = url.parse(path);
const safeSecret = decodeBase64Hash(removeWebSafe(secret));
const hashedSignature = makeWebSafe(encodeBase64Hash(safeSecret, uri.path));
return url.format(uri) + '&signature=' + hashedSignature;
}
下例使用默认 System.Security.Cryptography
库对网址请求进行签名。请注意,我们需要转换默认 Base64 编码,才能实现网址安全版本(下载代码)。
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
namespace SignUrl {
public struct GoogleSignedUrl {
public static string Sign(string url, string keyString) {
ASCIIEncoding encoding = new ASCIIEncoding();
// converting key to bytes will throw an exception, need to replace '-' and '_' characters first.
string usablePrivateKey = keyString.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;
}
}
class Program {
static void Main() {
// Note: Generally, you should store your private key someplace safe
// and read them into your code
const string keyString = "YOUR_PRIVATE_KEY";
// The URL shown in these examples is a static URL which should already
// be URL-encoded. In practice, you will likely have code
// which assembles your URL from user or web service input
// and plugs those values into its parameters.
const string urlString = "YOUR_URL_TO_SIGN";
string inputUrl = null;
string inputKey = null;
Console.WriteLine("Enter the URL (must be URL-encoded) to sign: ");
inputUrl = Console.ReadLine();
if (inputUrl.Length == 0) {
inputUrl = urlString;
}
Console.WriteLine("Enter the Private key to sign the URL: ");
inputKey = Console.ReadLine();
if (inputKey.Length == 0) {
inputKey = keyString;
}
Console.WriteLine(GoogleSignedUrl.Sign(inputUrl,inputKey));
}
}
}
其他语言的示例
您可以在网址签名项目中查看涵盖更多语言的示例。
问题排查
如果请求包含的签名无效,则 API 会返回 HTTP 403 (Forbidden)
错误。如果使用的签名密钥未与传递的客户端 ID 相关联,或者非 ASCII 输入未在签名之前进行网址编码,则很有可能会发生此错误。
如需排查问题,请复制请求网址,去除 signature
查询参数,并按照以下说明重新生成有效的签名:
如需通过下面的立即对网址进行签名 widget,使用您的客户端 ID 来生成数字签名,请按以下步骤操作:
- 按照第 1 步:获取您的网址签名密钥中所述的方法,获取您的客户端 ID 网址签名密钥。
- 在网址字段中,粘贴您在第 2 步:构建未签名请求中获取的未签名请求网址。
- 在网址签名密钥字段中,粘贴您在第 2 步中获取的网址签名密钥。
数字签名是根据您的未签名请求网址和签名密钥生成的,并会附加到您的原始网址中。 - 系统随即会显示您的已签名网址字段,其中包含经过数字签名的网址。