身份验证与授权

此页面仅适用于拥有旧版 Maps APIs for Work 或 Maps API for Business 许可的客户。此页面不适用于拥有新版 Google Maps APIs Premium Plan2016 年 1 月上市发售)的客户。

客户端 ID 和签名

在将 Google Maps APIs 网络服务与 Google Maps APIs for Work 许可搭配使用时,需要两个身份验证参数:客户端 ID 和唯一的数字签名(而不是 API 密钥)。

如果您从免费的 API 网络服务转到 Google Maps APIs for Work 实现,则必须从您的请求中移除 key 参数。Google Maps APIs 网络服务将拒绝同时使用客户端 ID 和密钥发起的请求

您的客户端 ID 和签名

购买 Google Maps APIs for Work 许可后,您将收到 Google 发送的一封欢迎电子邮件,其中包含您的客户端 ID私有加密密钥

  • 您的客户端 ID 用于获取 Google Maps APIs for Work 的特殊功能 - 您在获取任意 API 内容库或服务时必须提供客户端 ID。所有客户端 ID 都以 gme- 前缀开头。以 client 参数值的形式传递客户端 ID。

  • 系统使用您的私有加密密钥生成唯一的数字签名。以 signature 参数值的形式传递此签名。您可以在下面的数字签名部分中找到有关生成签名的详细信息。

下面是一个 Google Maps Directions API 示例:

    https://maps.googleapis.com/maps/api/directions/json
      ?origin=Toronto
      &destination=Montreal
      &client=gme-YOUR_CLIENT_ID
      &signature=YOUR_URL_SIGNATURE

如果您丢失了客户端 ID 或私有加密密钥,可以通过以下方式恢复:登录 Google Cloud Support Portal,然后在页面左侧的链接中点击 Maps:Manage Client ID

报告的可选参数

除了身份验证参数外,以下参数是 Google Maps APIs for Work 请求的可选参数:

  • channel 能够在报告中为不同的渠道单独分组,从而为报告提供更多详细信息。请参阅 Google Maps APIs for Work 网络服务配额与报告文档的渠道报告部分。

数字签名

Google Maps APIs for Work 客户对 Web Service API 的请求需要一个数字 signature,此签名使用在欢迎电子邮件中向您提供的私有加密密钥生成。

签署流程使用一种加密算法将网址与密钥结合起来。我们的服务器可以通过生成的唯一签名来验证,任何使用您的客户端 ID 生成请求的网站都获得了相应授权。签名在每个网址下也是唯一的,从而确保在不生成新签名的情况下将无法修改使用您的客户端 ID 的请求。

您的私有加密密钥

您的私有加密网址签名密钥将随您的客户端 ID 一起发放,是您与 Google 之间的“密码共享密钥”。此签名密钥仅属于您,并且对您的客户端 ID 来说是唯一的。因此,请妥善保存您的签名密钥。此密钥不应在任何请求内传递、存储在任何网站上或发布到任何公共论坛中。获取此签名密钥的任何人都能利用您的身份发出欺诈性请求。

:此私有加密签名密钥与 Google API Console 发放的 API 密钥相同。

如果您丢失了自己的私有加密密钥,请登录 Google Cloud Support Portal 并点击 Maps:Manage Client ID,检索该密钥。

生成数字签名

尝试使用无效签名获取 Google Maps APIs 网络服务将引发 HTTP 403 (Forbidden) 错误。在您将应用转换为使用网址签名时,请务必测试您的签名,确保它们可以发起有效请求。您首先应测试原始网址是否有效,并测试您是否生成正确的签名。

按以下步骤为您的请求创建数字签名:

  1. 构建不带签名的请求网址,务必添加您的 client 参数。请注意,任何非标准字符都需要进行网址编码。例如,对于 Directions API,请按如下所示构建网址:

    https://maps.googleapis.com/maps/api/directions/json?origin=Toronto&destination=Montreal&client=clientID

    注:所有 Google 服务都要求使用 UTF-8 字符编码(它隐式地包含 ASCII)。如果您的应用使用其他字符集运行,确保其使用 UTF-8 构建 URL,并对它们进行正确的 URL 编码。

  2. 去除请求的域部分,只留下路径和查询。例如,对于 Directions API:

    /maps/api/directions/json?origin=Toronto&destination=Montreal&client=clientID

  3. 检索您的私钥(以改良版网址 Base64 形式进行编码),并使用 HMAC-SHA1 算法签署上面的网址。您可能需要将此密钥解码成其原始二进制格式。请注意,在大多数加密内容库中,生成的签名均为二进制格式。

    注:改良版 URL Base64 将标准版 Base64 的 + 字符和 / 字符分别替换成 -_,从而使这些 Base64 签名不再需要接受 URL 编码处理。

  4. 利用改良版 URL Base64 对生成的二进制签名进行编码,将该签名转换成可在 URL 内传递的内容。

  5. 将此签名附加到 signature 参数内的网址。例如,对于 Directions API:

    https://maps.googleapis.com/maps/api/directions/json?origin=Toronto&destination=Montreal&client=clientID&signature=base64signature

如需了解有关使用服务器端代码实现网址签名的示例,请参阅网址签名的示例代码

URL 签名示例代码

下文介绍了利用服务器端代码实现 URL 签名的方法。应始终在服务器端签署 URL,以避免向用户公开密钥。

Python

下例使用标准 Python 内容库来签署 URL。(下载该代码。)

#!/usr/bin/python
# -*- coding: utf-8 -*-
""" Signs a URL using a URL signing secret """

import hashlib
import hmac
import base64
import 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, 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

if __name__ == "__main__":
  input_url = raw_input("URL to Sign: ")
  secret = raw_input("URL signing secret: ")
  print "Signed URL: " + sign_url(input_url, secret)

Java

下例使用从 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;
  }
}

C#

下例使用默认 System.Security.Cryptography 内容库来签署 URL 请求。请注意,我们需要转换默认 Base64 编码,才能实现 URL 安全版本。(下载该代码。)

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

要进行测试,您可以测试下面的网址和私钥,查看其是否生成正确的签名。请注意,私钥仅用于测试目的,不会由任何 Google 服务验证。

  • 网址https://maps.googleapis.com/maps/api/geocode/json?address=New+York&client=clientID
  • 私钥vNIXE0xscrmjlyV-12Nj_BvUPaw=
  • 要签署的网址部分/maps/api/geocode/json?address=New+York&client=clientID
  • 签名chaRF2hTJKOScPr-RQCEhZbSzIE=
  • 完整的签署网址https://maps.googleapis.com/maps/api/geocode/json?address=New+York&client=clientID&signature=chaRF2hTJKOScPr-RQCEhZbSzIE=

其他语言的示例

涵盖更多语言的示例位于网址签名项目中。

解决身份验证问题

如果您的请求格式不正确,或者提供的签名无效,Web Service API 会返回 HTTP 403 (Forbidden) 错误。

如需解决个别 URL 问题,您可以使用 URL 签名调试器。您可以利用它快速验证由您的应用生成的 URL 和签名。

Google Maps APIs for Work 客户也可以登录 Google Cloud Support Portal 并选择 Resources > Google Maps online tools > URL Signing Debugger for Web Service and Image APIs,对各个网址进行问题排查。