數位簽章搭配 API 金鑰
視使用情況而定,驗證要求時,除了 API 金鑰,您可能還需要提供數位簽章。請參閱下列文章:
數位簽章的運作方式
您必須使用 Google Cloud 控制台提供的「網址簽署密鑰」,才能產生數位簽章。這個密鑰基本上是一種私密金鑰,只限您與 Google 共用,且專屬於您的專案。
簽署程序會使用加密演算法,合併網址與共用密鑰。我們的伺服器會根據產生的專屬簽章進行驗證,確認使用您 API 金鑰產生要求的所有網站都已獲得授權。
限制未簽署的要求
如何確保您的 API 金鑰只接受已簽署的要求:
- 前往 Cloud 控制台中的 Google 地圖平台配額頁面。
- 按一下專案下拉式選單,並選取當初建立應用程式/網站 API 金鑰時所用的專案。
- 從 API 下拉式選單中選取 Maps Static API 或 Street View Static API。
- 展開「Unsigned requests」(未簽署要求) 部分。
- 在「Quota Name」(配額名稱) 資料表中,找出要編輯的配額,例如「Unsigned requests per day」(每日未簽署要求),然後點按旁邊的編輯按鈕。
- 在「Edit Quota Limit」(編輯配額限制) 窗格中更新配額限制。
- 選取「Save」(儲存)。
簽署要求
簽署要求包含下列步驟:
步驟 1:取得網址簽署密鑰
如何取得專案網址簽署密鑰:
- 在 Cloud 控制台中前往 Google 地圖平台憑證頁面。
- 選取專案下拉式選單,並選取當初建立 Maps Static API 或 Street View Static API 的 API 金鑰時所用的專案。
- 向下捲動至「Secret Generator」(密鑰產生器) 資訊卡。「Current secret」(目前的密鑰) 欄位含有您目前的網址簽署密鑰。
- 該頁面也提供「Sign a URL now」(立即簽署網址) 小工具,讓您使用目前的簽署密鑰自動簽署 Maps Static API 或 Street View Static API 要求。 向下捲動至「Sign a URL now」(立即簽署網址),即可看到這張資訊卡。
如要取得新的網址簽署密鑰,請選取「Regenerate Secret」(重新產生密鑰)。 前一組密鑰在新密鑰產生後的 24 小時就視為過期, 且含有舊密鑰的要求 24 小時後也會失效。
步驟 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 | 文字字串、結構用途 (http )、連接埠 (8080 ) 等。 |
非預留 | - _ . ~ | 文字字串 |
預留 | ! * ' ( ) ; : @ & = + $ , / ? % # [ ] | 控制字元和 (或) 文字字串 |
如果「預留」字元集內的所有字元是在文字字串內傳遞,則情況也一樣。詳情請參閱「特殊字元」一文。
建立不含簽章的未簽署要求網址。 如需操作說明,請參閱下列開發人員說明文件:
請務必一併在 key
參數中加入 API 金鑰。舉例來說:
https://maps.googleapis.com/maps/api/staticmap?center=Z%C3%BCrich&size=400x400&key=YOUR_API_KEY
產生已簽署的要求
如果是一次性的用途 (例如在網頁上代管簡單的 Maps Static API 或 Street View Static API 圖片,或是進行疑難排解),您可以使用我們提供的「立即簽署網址」小工具自動產生數位簽章。
如果是動態產生的要求,則必須進行伺服器端簽署,這需要一些額外的中間步驟。
不論是哪一種方式,您最後都需要建立結尾附有 signature
參數的要求網址。舉例來說:
https://maps.googleapis.com/maps/api/staticmap?center=Z%C3%BCrich&size=400x400&key=YOUR_API_KEY &signature=BASE64_SIGNATURE
使用「立即簽署網址」小工具
如要在 Google Cloud 控制台中透過「立即簽署網址」小工具,使用 API 金鑰產生數位簽章,請按照下列步驟操作:
- 按照「步驟 1:取得網址簽署密鑰」所述的方式,找出「立即簽署網址」小工具。
- 在「URL」(網址) 欄位中,貼上「步驟 2:建立未簽署的要求」中的未簽署要求網址。
- 「Your Signed URL」(已簽署的網址) 欄位隨即顯示,並已填入數位簽署網址, 請務必建立副本。
在伺服器端產生數位簽章
相較於「立即簽署網址」小工具,在伺服器端產生數位簽章時,您必須另外完成幾項操作。
-
去除網址的通訊協定架構和主機部分,僅保留路徑和查詢:
-
畫面上顯示的網址簽署密鑰會在修改後的網址 Base64 中進行編碼。
大部分的加密編譯程式庫都要求金鑰採用原始位元組格式,因此您可能需要在簽署前將網址簽署密鑰解碼為最初的原始格式。
- 使用 HMAC-SHA1 簽署上述經過去除的要求。
-
大部分的加密編譯程式庫會以原始位元組格式產生簽章,因此請務必使用修改後的網址 Base64,將產生的二進位簽章轉換成可在網址內傳送的內容。
-
將經過 Base64 編碼的簽章附加到
signature
參數中未簽署的原始要求網址。舉例來說:https://maps.googleapis.com/maps/api/staticmap?center=Z%C3%BCrich&size=400x400&key=
YOUR_API_KEY &signature=BASE64_SIGNATURE
/maps/api/staticmap?center=Z%C3%BCrich&size=400x400&key=YOUR_API_KEY
如需使用伺服器端程式碼導入網址簽署的做法範例,請參閱下方的「網址簽署程式碼範例」一節。
網址簽署程式碼範例
後續章節會說明使用伺服器端程式碼導入網址簽署的方式。網址一律應在伺服器端簽署,以避免向使用者暴露您的網址簽署密鑰。
以下範例使用標準 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;
}
}
以下範例使用原生 Node 模組簽署網址 (下載程式碼)。
'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)
錯誤。最有可能發生這個錯誤的情況是:使用的簽署密鑰未連結已傳遞的 API 金鑰,或者非 ASCII 輸入內容在簽署「之前」未經網址編碼。
如要排解這個問題,請複製要求網址,然後去除 signature
查詢參數,再按照下方操作說明重新產生有效的簽章。
如要在 Google Cloud 控制台中透過「立即簽署網址」小工具,使用 API 金鑰產生數位簽章,請按照下列步驟操作:
- 按照「步驟 1:取得網址簽署密鑰」所述的方式,找出「立即簽署網址」小工具。
- 在「URL」(網址) 欄位中,貼上「步驟 2:建立未簽署的要求」中的未簽署要求網址。
- 「Your Signed URL」(已簽署的網址) 欄位隨即顯示,並已填入數位簽署網址, 請務必建立副本。