雲原生 API Gateway支援全域認證、路由級認證和消費者鑒權,確保只有授權請求才能訪問服務。本文介紹消費者在雲原生API Gateway中如何通過認證和鑒權確保安全訪問資源。
背景資訊
相比全域認證鑒權適用於統一登入認證等ToC情境,路由、API開啟消費者認證,適用於授權API給夥伴等ToB情境。
對比項 | 全域認證鑒權 | 路由認證+消費者鑒權 |
適用情境 | 統一登入認證等ToC情境。 | 授權API給夥伴等ToB情境。 |
核心差異 | 開啟認證的同時也開啟鑒權。 | 開啟認證後,需要額外做鑒權配置。 |
配置入口 | 。 |
|
認證方式配置(以JWT認證為例) |
|
|
鑒權方式配置 | 建立配置時填寫黑名單或白名單的網域名稱和路徑(Path)列表。
|
|
注意事項
當使用者在消費者認證中開啟認證後,認證策略會即時生效,如果此時路由或者API發行,但是又未給路由或者API配置消費者和授權規則,則預設會拒絕所有訪問請求。
使用消費者認證鑒權
JWT認證
JWT認證流程概覽:
用戶端向API Gateway發起認證請求,請求中一般會攜帶終端使用者的使用者名稱和密碼。
網關將請求直接轉寄給後端服務。
後端服務讀取請求中的驗證資訊(比如使用者名稱、密碼)進行驗證,驗證通過後使用私密金鑰產生標準的Token,返回給網關。
網關將攜帶Token的應答返回給用戶端,用戶端需要將這個Token緩衝到本地。
用戶端向API Gateway發送業務請求,請求中攜帶Token。
網關使用使用者設定的公開金鑰對請求中的Token進行驗證,驗證通過後,將請求透傳給後端服務。
後端服務進行業務處理後應答。
網關將業務應答返回給用戶端。
下文主要介紹產生Token、用戶端向網關發送請求、網關使用設定的公開金鑰進行Token驗證的過程。
認證服務產生Token
下文將通過Java樣本來說明Token的產生方式,其他語言您也可使用相關的工具產生金鑰組。
建立Maven專案並注入依賴。
首先,建立一個Maven專案,注入如下依賴項:
<dependency> <groupId>org.bitbucket.b_c</groupId> <artifactId>jose4j</artifactId> <version>0.7.0</version> </dependency>選擇Token產生方式。
您可以選擇使用預設對稱金鑰樣本產生Token和非對稱金鑰樣本產生Token兩種方式來產生Token。根據您的需求進行選擇:
使用預設對稱金鑰樣本產生Token
程式碼範例:
package org.example; import java.io.UnsupportedEncodingException; import java.security.PrivateKey; import org.jose4j.base64url.Base64; import org.jose4j.json.JsonUtil; import org.jose4j.jwk.OctJwkGenerator; import org.jose4j.jwk.OctetSequenceJsonWebKey; import org.jose4j.jws.AlgorithmIdentifiers; import org.jose4j.jws.JsonWebSignature; import org.jose4j.jwt.JwtClaims; import org.jose4j.jwt.NumericDate; import org.jose4j.keys.HmacKey; import org.jose4j.lang.JoseException; import sun.lwawt.macosx.CSystemTray; public class Main { public static void main(String[] args) throws JoseException, UnsupportedEncodingException { //使用本文上述樣本 String privateKeyJson = "{\n" + " \"k\": \"VoBG-oyqVoyCr9G56ozmq8n_rlDDyYMQOd_DO4GOkEY\",\n" + " \"kty\": \"oct\",\n" + " \"alg\": \"HS256\",\n" + "}"; JwtClaims claims = new JwtClaims(); claims.setGeneratedJwtId(); claims.setIssuedAtToNow(); //設定到期時間,並且小於7天 NumericDate date = NumericDate.now(); date.addSeconds(120*60); claims.setExpirationTime(date); claims.setNotBeforeMinutesInThePast(1); //添加自訂參數,所有值請都使用String類型 //設定消費者標識 claims.setClaim("uid", "11215ac069234abcb8944232b79ae711"); JsonWebSignature jws = new JsonWebSignature(); //設定密碼編譯演算法 jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256); jws.setKey(new HmacKey(Base64.decode(JsonUtil.parseJson(privateKeyJson).get("k").toString()))); jws.setPayload(claims.toJson()); String jwtResult = jws.getCompactSerialization(); System.out.println("Generate Json Web token , result is \n " + jwtResult); } }代碼相關設定說明:
privateKeyJson:即在建立消費者時使用的JWKS,可以在建立消費者時記錄下自己使用的JWKS,也可以在建立消費者後,在消費者基礎配置頁擷取JWKS。設定消費者標識。即
claims.setClaim("uid", "11215ac069234abcb8944232b79ae711"),該消費者標識為建立消費者時控制台預設產生,也可以根據自身邏輯進行修改。您也可以在建立消費者後,在消費者基礎配置頁擷取消費者標識。設定密碼編譯演算法。即
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256),該密碼編譯演算法要和JWKS保持一致。說明目前支援的密碼編譯演算法有ES256、ES384、ES512、RS256、RS384、RS512、PS256、PS384、PS512、HS256、HS384、HS512和EdDSA。
使用對稱式加密的時,需要對"k"進行解碼。
jws.setKey(new HmacKey(Base64.decode(JsonUtil.parseJson(privateKeyJson).get("k").toString())));
設定到期時間。到期時間需要小於7天,超出到期時間後,請重建Token,以保證Token的安全性。
... NumericDate date = NumericDate.now(); date.addSeconds(120*60); claims.setExpirationTime(date); claims.setNotBeforeMinutesInThePast(1); ...根據自身業務需要,可以在JWKS的PAYLOAD中添加自訂參數。
使用非對稱金鑰樣本產生Token
程式碼範例:
package org.example; import java.security.PrivateKey; import org.jose4j.json.JsonUtil; import org.jose4j.jws.AlgorithmIdentifiers; import org.jose4j.jws.JsonWebSignature; import org.jose4j.jwt.JwtClaims; import org.jose4j.jwt.NumericDate; import org.jose4j.lang.JoseException; import org.jose4j.jwk.RsaJsonWebKey; public class Main { public static void main(String[] args) throws JoseException { // 使用 JWK 格式的密鑰樣本 String privateKeyJson = "{" + "\"kty\":\"RSA\"," + "\"n\":\"u-8lR9lyRhu8tl4vRxOl7yfshssx5jRstabc9n4Rxrz102Z7TPFYrXBZHgf67Y0d-zx9tWd5j91WZxLHv4K6VWPN7zEWEQNn3vUg76dPHPzVkZJWziFPS1EvS4a2gRZdrE4nPogaQ72WySVC0yUF2fL0NKeOclD__coCFxn4QQjcDXyu_CUbI_FuDcdw267mVjjylAaFvOZHH0pXsV8m5zXlpc2aiemWYQJD9MtWRcoKlexWMkTwbEqW5-NWAl0Uo202ahDA1NiaQ98Ch4nw6g2E1GvwxxHkbvMuZcs5z8F5Ct_w0IPtvY7ngSyEN-WCU40oj-C5NUCy_73FpXXdWQ\"," + "\"e\":\"AQAB\"," + "\"d\":\"Uxg1IqSZazg-Y2AXhVTBrJG5egwD3yZU3qiN0IsDbx0DkFoisG2R6PXg4W9j2n7nv7sKVhgPXrXdyys5mIrDuperaVQJzrHzzlgSHQSb7VQ5Vekfanq95a5avAkvTrpF5raTkYl6G3OLZRqNhnA7Oxe6NEHVsOPxnBQignZgFtiBtCSZY4RVH6Dx4jFfBBNMC9ifyLWLpHut7eczvxI412nBkxgtEjSeFe083NlumO_ZYHbijPcQf5zFWPLEj2EvlgbSwhjc-uSAF4OljAyG_DHZYvztEIGMdxMgBHgwtEvCfzS6PeUgvUR-SB7m_L8z4gjz6TlpSYe3CnZqE69KdQ\"," + "\"p\":\"9mNw8U_GxbcSknueUeFFSU9wKD9E9P2ZO5RP5d7o3qGUXOfbrH_g5GMH3YJiPBDZs_2BYFrAACOY4YM-QTiXWVs4xS3OeD8AdH3wEXR_3DMEkOez3cNq3Jy3ZJabm7IUnPMIWv5gpIHghcx0YTtM5RabSgexLMKh2-6sB466378\"," + "\"q\":\"w0PzXI_8Q5jv15OUq-dV_Z0kp91Icumf6PEERZN4v3i3VolLBnamMiIQiF74ywclKpZmtfOQTfyL41Xj2vbm2Aus6akRsU3NhQlVtIQTzHWUuEQgMIJYDK7--FzEcZORm1qBiEffiWv6U-slyCcLcNDNT-wjMX8BrS4oWHIoCOc\"," + "\"dp\":\"fRRiY76yE_EqVn63Eq4ftGXFdEkaQpzzS1GxderBoTO506hI1rtcedTkS0lDgWa0fjE1mqq3SdrIY8NyuT13Z_9tRHxKkrS5EGpWkyXnOuwTZ1SY9P2dpD1SxJfIizPOTxb5qOf2O81LI-F1O18VXD8rultJUIXGEZaKcpO8vpU\"," + "\"dq\":\"V6EP_vMzD5b707AMYVURFx7Fi3vX_pHvzJcVBrBW2P6wsGouvDjU_tygtMKCPoL3X_RdJbynfwgeMyihd-ujz0L2F2pjYUF8QP7ecoNvays9UbBpDbwBDbge_pCLLDlAeAqW5PT0UXSew7hcnUVAciGSchKT_Kt1siVrv72DT_M\"," + "\"qi\":\"w5fENzxOivbbbUbawAWSuLgWPtvbTKn2XuPzwr_JzZH08-nadWwQFChcvAiCs8V-306TQOh8NfY308QpGDIq-iRfrS7CEePOjRzpHJfsaQ1IFQqzgDZ9VGdJRDlZqRHx0DbqwMlleVKTC6ER6varalbr4lKU-ZPUQLCzj0e4PMs\"" + "}"; JwtClaims claims = new JwtClaims(); claims.setGeneratedJwtId(); // 自動產生 jti(JWT ID) claims.setIssuedAtToNow(); // 設定簽發時間(iat)為目前時間 // 設定到期時間,並且小於7天 NumericDate date = NumericDate.now(); date.addSeconds(120 * 60); // 設定到期時間為目前時間+120分鐘 claims.setExpirationTime(date); claims.setNotBeforeMinutesInThePast(1); // nbf 時間為目前時間往前推1分鐘 // 添加自訂參數,所有值請都使用String類型 // 設定消費者標識 claims.setClaim("uid", "11215ac069234abcb8944232b79ae711"); JsonWebSignature jws = new JsonWebSignature(); // 設定密碼編譯演算法為 RSA-SHA256 jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256); // 解析私密金鑰並設定到簽名器中 PrivateKey privateKey = new RsaJsonWebKey(JsonUtil.parseJson(privateKeyJson)).getPrivateKey(); jws.setKey(privateKey); // 設定 payload 內容 jws.setPayload(claims.toJson()); // 產生 JWT Token String jwtResult = jws.getCompactSerialization(); System.out.println("Generate Json Web token , result is \n " + jwtResult); } }代碼相關設定說明:
設定
privateKeyJson、消費者標識、到期時間,同對稱式加密演算法。設定密碼編譯演算法,即
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256)。該密碼編譯演算法和JWKS保持一致。對於非對稱式加密演算法,要用其私密金鑰進行加密。
... jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256); PrivateKey privateKey = new RsaJsonWebKey(JsonUtil.parseJson(privateKeyJson)).getPrivateKey(); jws.setKey(privateKey); ...根據自身業務需要,可以在JWKS的
PAYLOAD中添加自訂參數。
用戶端向網關發送業務請求
目前雲原生API Gateway支援Header的模式傳遞Token,客戶可以自訂請求Header的名字和Token的首碼,請求驗證時必須與消費者認證方式配對的key和首碼保持一致。
請求不攜帶提供JWT,返回401。
curl http://xxx.hello.com/test請求攜帶錯誤JWT,返回401。
curl http://xxx.hello.com/test -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxODY1NjczODE5fQ.-vBSV0bKeDwQcuS6eeSZN9dLTUnSnZVk8eVCXdooCQ1'請求攜帶JWT,但JWT代表的消費者無權訪問API或者路由時,返回403。
# consumer1沒有授權給下列路徑指定路由或者api curl 'http://xxx.example.com/test' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxODY1NjczODE5fQ.-vBSV0bKeDwQcuS6eeSZN9dLTUnSnZVk8eVCXdooCQ4'
服務端驗證Token請求
服務端驗證Token主要分為以下三步:
服務端收到使用者請求時,先檢查是否攜帶Token,未攜帶Token的請求,直接拒絕,返回401。
攜帶Token的請求,使用使用者jwks中配置的私密金鑰驗證Token是否合法且有效,不合法或者失效,直接拒絕,返回401。
如果Token合法且有效後,再校正其代表的消費者是否授權給正在訪問的API或者路由。
常見錯誤碼說明
HTTP 狀態代碼 | 出錯資訊 | 原因說明 |
401 | Jwt missing | 要求標頭未提供JWT |
401 | Jwt expired | JWT已經到期 |
401 | Jwt verification fails | JWT payload校正失敗,如iss不匹配 |
403 | Access Denied | 無許可權訪問當前路由 |
AK/SK(HMAC)認證
用戶端產生簽名
用戶端產生簽名流程概覽:
提取簽名串:從原始請求中提取關鍵資料,得到一個用來簽名的字串。
加密簽名:使用密碼編譯演算法和配置的SK對關鍵資料簽名串進行加密處理,得到簽名。
添加簽名:將簽名所相關的所有頭加入到原始HTTP請求中,得到最終HTTP請求。
步驟一:提取簽名串
用戶端需要從Http請求中提取出關鍵資料,組合成一個簽名串,產生的簽名串格式如下:
HTTPMethod
Accept
Content-MD5
Content-Type
Date
Headers
PathAndParameters以上7個欄位構成整個簽名串,欄位之間使用\n間隔,如果Headers欄位為空白,則不需要加\n,其他欄位如果為空白都需要保留\n。簽名對大小寫敏感。下面介紹每個欄位的擷取規則:
HTTPMethod:HTTP的方法,全部大寫,例如POST。
Accept:請求中的Accept頭的值,可為空白。建議顯式設定Accept Header。當Accept為空白時,部分Http用戶端會給Accept設定預設值為
*/*,導致簽名校正失敗。Content-MD5:請求中的Content-MD5頭的值,可為空白。只有在請求存在Body且Body為非Form形式時才計算Content-MD5頭,下面是Java的Content-MD5值的參考計算方式:
String content-MD5 = Base64.encodeBase64(MD5(bodyStream.getbytes("UTF-8")));Content-Type:請求中的Content-Type頭的值,可為空白。
Date:請求中的Date頭的值,當未開啟
date_offset配置時,可為空白,否則將用於時間位移校正。Headers:使用者可以選取指定的Header參與簽名,關於Header的簽名串拼接方式有以下規則:
參與簽名計算的Header的Key按照字典排序後使用如下方式拼接。
HeaderKey1 + ":" + HeaderValue1 + "\n"\+ HeaderKey2 + ":" + HeaderValue2 + "\n"\+ ... HeaderKeyN + ":" + HeaderValueN + "\n"
某個Header的Value為空白,則使用HeaderKey+":"+"\n"參與簽名,需要保留Key和英文冒號。
所有參與簽名的Header的Key的集合使用英文逗號分割放到Key為X-Ca-Signature-Headers的Header中。
以下Header不參與Header簽名計算:X-Ca-Signature、X-Ca-Signature-Headers、Accept、Content-MD5、Content-Type、Date。
PathAndParameters: 這個欄位包含Path,Query和Form中的所有參數,具體組織形式如下:
Path + "?" + Key1 + "=" + Value1 + "&" + Key2 + "=" + Value2 + ... "&" + KeyN + "=" + ValueN重要Query和Form參數對的Key按照字典排序後使用上面的方式拼接。
Query和Form參數為空白時,則直接使用Path,不需要添加
?。參數的Value為空白時只保留Key參與簽名,等號不需要再加入簽名。
Query和Form存在數組參數時(key相同,value不同的參數) ,取第一個Value參與簽名計算。
步驟二:加密簽名
用戶端從HTTP請求中提取出關鍵資料群組裝成簽名串後,需要對簽名串進行加密及編碼處理,形成最終的簽名。具體的加密形式如下,其中 stringToSign 為提取出來的簽名串,secret 為AK/SK認證身份配置中的SK,sign 為最終產生的簽名:
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
byte[] secretBytes = secret.getBytes("UTF-8");
hmacSha256.init(new SecretKeySpec(secretBytes, 0, secretBytes.length, "HmacSHA256"));
byte[] result = hmacSha256.doFinal(stringToSign.getBytes("UTF-8"));
String sign = Base64.encodeBase64String(result);總結:將 stringToSign 使用UTF-8解碼後得到Byte數組,然後使用密碼編譯演算法對Byte數組進行加密,使用Base64演算法進行編碼,形成最終的簽名。
步驟三:添加簽名
用戶端需要將以下四個Header放在HTTP請求中傳輸給API Gateway,進行簽名校正:
x-ca-key:取值為AK/SK認證身份配置中的AK。
x-ca-signature-method:簽名演算法,取值HmacSHA256或者HmacSHA1,可選,預設值為HmacSHA256。
x-ca-signature-headers:所有簽名頭的Key的集合,使用英文逗號分隔,可選。
x-ca-signature:簽名,必選。
服務端簽名驗證
伺服器驗證用戶端簽名概覽:
提取簽名串:從接收到的請求中提取關鍵資料,得到一個用來簽名的字串。
提取AK:從接收到的請求中讀取AK ,通過AK 查詢到對應的SK。
計算簽名:使用密碼編譯演算法和SK對關鍵資料簽名串進行加密處理,得到簽名。
簽名驗證:從接收到的請求中讀取用戶端簽名,對比伺服器端簽名和用戶端簽名的一致性。
異常處理
網關簽名校正失敗時,會將服務端的簽名串(StringToSign)放到HTTP Response的Header中返回到用戶端,Key為:X-Ca-Error-Message,使用者只需要將本地計算的簽名串(StringToSign)與服務端返回的簽名串進行對比即可找到問題。如果服務端與用戶端的StringToSign一致,請檢查用於簽名計算的AK/SK認證身份中的SK是否正確。因為HTTP Header中無法表示換行,因此StringToSign中的分行符號都被替換成#,如下所示:
X-Ca-Error-Message: Server StringToSign:`GET#application/json##application/json##X-Ca-Key:200000#X-Ca-Timestamp:1589458000000#/app/v1/config/keys?keys=TEST`相關錯誤碼
HTTP 狀態代碼 | 出錯資訊 | 原因說明 |
401 | Invalid Key | 要求標頭未提供x-ca-key,或者x-ca-key無效。 |
401 | Empty Signature | 要求標頭未提供x-ca-signature簽名串。 |
400 | Invalid Signature | 要求標頭x-ca-signature簽名串,與服務端計算得到簽名不一致。 |
400 | Invalid Content-MD5 | 要求標頭content-md5不正確。 |
400 | Invalid Date | 根據要求標頭date計算時間位移超過配置的 date_offset。 |
413 | Request Body Too Large | 請求Body超過限制大小:32 MB。 |
413 | Payload Too Large | 請求Body超過全域配置DownstreamConnectionBufferLimits。 |
403 | Unauthorized Consumer | 請求的調用方無存取權限。 |
範例程式碼(Golang)
go
package main
import (
"bytes"
"crypto/hmac"
"crypto/md5"
"crypto/sha256"
"encoding/base64"
"fmt"
"io"
"net/http"
"strings"
"time"
)
func generateHMACSignature(toSign string, key string) (string, error) {
h := hmac.New(sha256.New, []byte(key))
h.Write([]byte(toSign))
return base64.StdEncoding.EncodeToString(h.Sum(nil)), nil
}
func test(accessKey, secretKey string) {
body := `{"hello":"world"}`
h := md5.New()
h.Write([]byte(body))
payload := base64.StdEncoding.EncodeToString(h.Sum(nil))
headers := map[string]string{
"accept": "application/json",
"content-type": "application/json",
"date": time.Now().Format("2006-01-02 15:04:05"),
"x-ca-key": accessKey,
"foo": "bar",
"x-ca-signature-headers": "foo",
"content-md5": payload,
}
sts := strings.Join([]string{"POST", headers["accept"], headers["content-md5"], headers["content-type"], headers["date"], "foo:bar", "/post"}, "\n")
fmt.Printf("String to sign is: %s\n", strings.ReplaceAll(sts, "\n", "#"))
sign, _ := generateHMACSignature(sts, secretKey)
fmt.Printf("Signed string is: %s\n", sign)
headers["x-ca-signature"] = sign
req, _ := http.NewRequest("POST", "http://localhost:8080/post", bytes.NewBufferString(body))
for k, v := range headers {
req.Header.Add(k, v)
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("read body error")
}
defer resp.Body.Close()
fmt.Println("Headers are as follows:")
for k, v := range resp.Header {
// 如果簽名計算失敗,X-Ca-Error-Message回應標頭中會包含server端計算簽名的字串,使用者可據此排查問題
fmt.Printf(" %s: %s\n", k, v)
}
respBody, _ := io.ReadAll(resp.Body)
fmt.Println(string(respBody))
}
func main() {
test("appKey", "appSecret")
}
API Key認證
按照設定的憑證來源方式來驗證請求,API與路由請求類似,下面以路由為例:
API Key的憑證來源主要有三類:
預設憑證來源:Authorization: <Bearer> Token。
自訂Header,填寫Header參數名。
自訂Query參數,填寫query參數名。
預設憑證來源
假設以下請求會匹配到這條路由 abc ,將API Key設定在HTTP特定要求標頭(Authorization)中,並為API Key追加首碼Bearer,留意Bearer與API Key中間留有空格。
curl http://xxx.test.com/test -H 'x-api-key: Bearer 2bda943c-ba2b-11ec-ba07-00163e1250b5'將API Key設定在http要求標頭中
curl http://xxx.test.com/test -H 'x-api-key: Bearer 2bda943c-ba2b-11ec-ba07-00163e1250b5'自訂header來源
假設以下請求會匹配到這條路由 abc ,API Key 設定在 http 要求標頭中。
如果路由未開啟消費者認證鑒權策略,則拒絕訪問,提示401。
curl http://xxx.test.com/test?apikey=2bda943c-ba2b-11ec-ba07-00163e1250b5如果路由已開啟消費者認證鑒權策略,但未授權,則提示403。
curl http://xxx.test.com/test -H 'x-api-key: 2bda943c-ba2b-11ec-ba07-00163e1250b5'
自訂Query參數
假設以下請求會匹配到這條路由 abc ,將 API Key 設定在 url 參數中。
如果路由未開啟消費者認證鑒權策略,則拒絕訪問,提示401。
curl http://xxx.test.com/test?apikey=2bda943c-ba2b-11ec-ba07-00163e1250b5如果路由已開啟消費者認證鑒權策略,但未授權,則提示403。
curl http://xxx.test.com/test?apikey=2bda943c-ba2b-11ec-ba07-00163e1250b5
相關錯誤碼
HTTP 狀態代碼 | 出錯資訊 | 原因說明 |
401 | Request denied by Key Auth check. Muti API key found in request. | 請求提供多個 API Key。 |
401 | Request denied by Key Auth check. No API key found in request. | 請求未提供 API Key。 |
401 | Request denied by Key Auth check. Invalid API key. | 不允許當前 API Key 訪問。 |
403 | Request denied by Key Auth check. Unauthorized consumer. | 請求的調用方無存取權限。 |
相關文檔
您可以對API進行授權和管理,具體操作,請參見授權管理。