如果您的應用程式允許使用者使用 Google 登入帳戶,您可以監聽並回應跨帳戶防護服務提供的安全性事件通知,提升這些共用使用者的帳戶安全性。
這些通知會提醒您使用者 Google 帳戶發生重大變更,這可能會影響使用者帳戶在您應用程式中的安全性。舉例來說,如果使用者的 Google 帳戶遭到盜用,可能會導致使用者帳戶在您應用程式中的安全性受到影響,例如透過電子郵件帳戶復原功能或單一登入功能。
為協助您降低這類事件的潛在風險,Google 會傳送稱為安全性事件符記的服務物件。這些符記會揭露極少的資訊,只會揭露安全性事件的類型、發生時間,以及受影響使用者的 ID,但您可以利用這些資訊採取適當的回應行動。舉例來說,如果使用者的 Google 帳戶遭到入侵,您可以暫時為該使用者停用「使用 Google 帳戶登入」功能,並防止系統將帳戶復原電子郵件傳送至使用者的 Gmail 地址。
跨帳戶防護功能採用 OpenID 基金會開發的 RISC 標準。
總覽
如要在應用程式或服務中使用跨帳戶防護功能,您必須完成下列工作:
在 中設定專案。
建立事件接收器端點,Google 會將安全性事件符記傳送至該端點。這個端點負責驗證收到的權杖,然後根據您選擇的方式回應安全事件。
向 Google 註冊端點,開始接收安全性事件符記。
修課條件
您只會收到 Google 使用者授予服務存取其個人資料或電子郵件地址權限的安全性事件符記。您可以要求 profile
或 email
範圍來取得這項權限。較新的 Sign In With Google 或舊版 Google Sign-in SDK 會預設要求這些範圍,但如果您未使用預設設定,或直接存取 Google 的 OpenID Connect 端點,請務必要求至少一個範圍。
在 中設定專案
您必須先建立服務帳戶,並在 專案中啟用 RISC API,才能開始接收安全性事件符記。您必須使用在應用程式中存取 Google 服務 (例如 Google 登入) 時所用的 專案。
如何建立服務帳戶:
依序按一下「建立憑證」>「服務帳戶」。
按照這裡的操作說明,建立具備 RISC 設定管理員角色 (
roles/riscconfigs.admin
) 的新服務帳戶。為新建立的服務帳戶建立金鑰。選擇 JSON 鍵類型,然後按一下「建立」。建立金鑰後,您會下載包含服務帳戶憑證的 JSON 檔案。請將這個檔案儲存在安全的地方,但也要讓事件接收端端點可以存取。
在專案的「憑證」頁面中,請一併記下用於「使用 Google 帳戶登入」或「Google 登入 (舊版)」的用戶端 ID。通常,您會為每個支援的平台提供一個用戶端 ID。您需要這些用戶端 ID 來驗證安全性事件權杖,詳情請見下一節。
如要啟用 RISC API,請按照下列步驟操作:
在中開啟 RISC API 頁面。確認您用來存取 Google 服務的專案是否仍處於選取狀態。
請詳閱 RISC 條款,確保您瞭解相關規定。
如果您要為機構擁有的專案啟用 API,請確認您已獲授權將機構綁定至 RISC 條款。
只有在同意 RISC 條款時,才點選「啟用」。
建立事件接收器端點
如要接收 Google 的安全性事件通知,您必須建立負責處理 HTTPS POST 要求的 HTTPS 端點。註冊這個端點後 (請參閱下方說明),Google 就會開始將稱為安全性事件權杖的加密簽署字串發布至端點。安全性事件權杖是已簽署的 JWT,其中包含單一安全性相關事件的資訊。
針對您在端點收到的每個安全性事件符記,請先驗證及解碼符記,然後視服務而定適當地處理安全性事件。在解碼前驗證事件符記是必要的步驟,可防止不肖人士發動惡意攻擊。以下各節將說明這些工作:
1. 解碼及驗證安全性事件權杖
由於安全性事件符記是特定類型的 JWT,因此您可以使用任何 JWT 程式庫 (例如 jwt.io 上列出的程式庫) 來解碼及驗證這些符記。無論您使用哪個程式庫,憑證驗證程式碼都必須執行以下操作:
- 請從 Google 的 RISC 設定文件中取得跨帳戶保護核發者 ID (
issuer
) 和簽署金鑰憑證 URI (jwks_uri
),您可以在https://accounts.google.com/.well-known/risc-configuration
找到這份文件。 - 使用您選擇的 JWT 程式庫,從安全性事件符記的標頭取得簽署金鑰 ID。
- 從 Google 的簽署金鑰憑證文件中,取得您在先前步驟中取得的金鑰 ID 所對應的公開金鑰。如果文件中沒有您要尋找的 ID 鍵,安全性事件權杖很可能無效,端點應會傳回 HTTP 錯誤 400。
- 使用您選擇的 JWT 程式庫,驗證下列項目:
- 安全性事件權杖會使用您在先前步驟中取得的公開金鑰進行簽署。
- 權杖的
aud
要求為應用程式的用戶端 ID 之一。 - 權杖的
iss
要求與您從 RISC 探索文件取得的核發者 ID 相符。請注意,您不需要驗證權杖的到期日 (exp
),因為安全性事件權杖代表的是歷史事件,因此不會到期。
例如:
Java
使用 java-jwt 和 jwks-rsa-java:
public DecodedJWT validateSecurityEventToken(String token) {
DecodedJWT jwt = null;
try {
// In a real implementation, get these values from
// https://accounts.google.com/.well-known/risc-configuration
String issuer = "accounts.google.com";
String jwksUri = "https://www.googleapis.com/oauth2/v3/certs";
// Get the ID of the key used to sign the token.
DecodedJWT unverifiedJwt = JWT.decode(token);
String keyId = unverifiedJwt.getKeyId();
// Get the public key from Google.
JwkProvider googleCerts = new UrlJwkProvider(new URL(jwksUri), null, null);
PublicKey publicKey = googleCerts.get(keyId).getPublicKey();
// Verify and decode the token.
Algorithm rsa = Algorithm.RSA256((RSAPublicKey) publicKey, null);
JWTVerifier verifier = JWT.require(rsa)
.withIssuer(issuer)
// Get your apps' client IDs from the API console:
// ?project=_
.withAudience("123456789-abcedfgh.apps.googleusercontent.com",
"123456789-ijklmnop.apps.googleusercontent.com",
"123456789-qrstuvwx.apps.googleusercontent.com")
.acceptLeeway(Long.MAX_VALUE) // Don't check for expiration.
.build();
jwt = verifier.verify(token);
} catch (JwkException e) {
// Key not found. Return HTTP 400.
} catch (InvalidClaimException e) {
} catch (JWTDecodeException exception) {
// Malformed token. Return HTTP 400.
} catch (MalformedURLException e) {
// Invalid JWKS URI.
}
return jwt;
}
Python
import json
import jwt # pip install pyjwt
import requests # pip install requests
def validate_security_token(token, client_ids):
# Get Google's RISC configuration.
risc_config_uri = 'https://accounts.google.com/.well-known/risc-configuration'
risc_config = requests.get(risc_config_uri).json()
# Get the public key used to sign the token.
google_certs = requests.get(risc_config['jwks_uri']).json()
jwt_header = jwt.get_unverified_header(token)
key_id = jwt_header['kid']
public_key = None
for key in google_certs['keys']:
if key['kid'] == key_id:
public_key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(key))
if not public_key:
raise Exception('Public key certificate not found.')
# In this situation, return HTTP 400
# Decode the token, validating its signature, audience, and issuer.
try:
token_data = jwt.decode(token, public_key, algorithms='RS256',
options={'verify_exp': False},
audience=client_ids, issuer=risc_config['issuer'])
except:
raise
# Validation failed. Return HTTP 400.
return token_data
# Get your apps' client IDs from the API console:
# ?project=_
client_ids = ['123456789-abcedfgh.apps.googleusercontent.com',
'123456789-ijklmnop.apps.googleusercontent.com',
'123456789-qrstuvwx.apps.googleusercontent.com']
token_data = validate_security_token(token, client_ids)
如果權杖有效且已成功解碼,請傳回 HTTP 狀態 202。接著,處理符記所指示的安全性事件。
2. 處理安全性事件
經過解碼後,安全事件符記會如下所示:
{
"iss": "https://accounts.google.com/",
"aud": "123456789-abcedfgh.apps.googleusercontent.com",
"iat": 1508184845,
"jti": "756E69717565206964656E746966696572",
"events": {
"https://schemas.openid.net/secevent/risc/event-type/account-disabled": {
"subject": {
"subject_type": "iss-sub",
"iss": "https://accounts.google.com/",
"sub": "7375626A656374"
},
"reason": "hijacking"
}
}
}
iss
和 aud
宣告會指出憑證的發出者 (Google) 和憑證的預期接收者 (您的服務)。您已在前一個步驟中驗證這些權利聲明。
jti
權利要求是用於識別單一安全性事件的字串,且為串流專屬。您可以使用這個 ID 追蹤收到的安全性事件。
events
權利要求包含符記代表的安全性事件相關資訊。這個權利要求是從事件類型 ID 對應至 subject
權利要求,可指定此事件涉及的使用者,以及任何可能可用的事件相關詳細資料。
subject
憑證會使用使用者的專屬 Google 帳戶 ID (sub
) 識別特定使用者。這個 Google 帳戶 ID 與新版「Sign In with Google」(Javascript、HTML) 程式庫、舊版 Google Sign-in 程式庫或 OpenID Connect 所發出的 JWT ID 權杖所含的 ID (sub
) 相同。如果聲明的 subject_type
為 id_token_claims
,則可能會包含含有使用者電子郵件地址的 email
欄位。
使用 events
權利要求中的資訊,針對指定使用者帳戶的事件類型採取適當行動。
OAuth 權杖 ID
針對個別權杖的 OAuth 事件,權杖主體 ID 類型包含下列欄位:
token_type
:僅支援refresh_token
。token_identifier_alg
:如要查看可能的值,請參閱下方表格。token
:請參閱下表。
token_identifier_alg | 符記 |
---|---|
prefix |
符記的前 16 個字元。 |
hash_base64_sha512_sha512 |
使用 SHA-512 的符記雙雜湊。 |
如果您整合這些事件,建議您根據這些可能的值為符記建立索引,確保在收到事件時能快速比對。
支援的事件類型
跨帳戶防護功能支援下列安全性事件類型:
事件類型 | 屬性 | 如何回應 |
---|---|---|
https://schemas.openid.net/secevent/risc/event-type/sessions-revoked |
必要:結束使用者目前開放的工作階段,重新保護使用者帳戶。 | |
https://schemas.openid.net/secevent/oauth/event-type/tokens-revoked |
必要:如果權杖是用於 Google 登入,請終止使用者目前開放的工作階段。此外,您可能還想建議使用者設定其他登入方式。 建議:如果權杖用於存取其他 Google API,請刪除您儲存的任何使用者 OAuth 權杖。 |
|
https://schemas.openid.net/secevent/oauth/event-type/token-revoked |
如要瞭解符記 ID,請參閱「OAuth 符記 ID」一節 |
必要:如果您儲存了對應的重新整理權杖,請將其刪除,並要求使用者在下次需要存取權杖時重新同意。 |
https://schemas.openid.net/secevent/risc/event-type/account-disabled |
reason=hijacking 、reason=bulk-account |
必要:如果帳戶停用的原因是 建議:如果帳戶停用的原因是 建議:如果使用者未提供原因,請為使用者停用 Google 登入功能,並停用使用者 Google 帳戶 (通常是 Gmail 帳戶,但不一定是 Gmail 帳戶) 的電子郵件地址。為使用者提供其他登入方法。 |
https://schemas.openid.net/secevent/risc/event-type/account-enabled |
建議:為使用者重新啟用 Google 登入功能,並使用使用者的 Google 帳戶電子郵件地址重新啟用帳戶救援功能。 | |
https://schemas.openid.net/secevent/risc/event-type/account-credential-change-required |
建議:請留意服務中的可疑活動,並採取適當行動。 | |
https://schemas.openid.net/secevent/risc/event-type/verification |
state=state | 建議:記錄收到測試權杖。 |
重複和遺漏的事件
跨帳戶防護功能會嘗試重新傳送系統認為尚未傳送的事件。因此,您可能會多次收到相同的事件。如果這會導致重複動作,造成使用者不便,建議您使用 jti
權利要求 (事件的專屬 ID) 來去除事件重複。Google Cloud Dataflow 等外部工具可能有助於執行資料去重作業。
請注意,事件會在重試次數有限的情況下傳送,因此如果接收器長時間無法運作,您可能會永久錯過某些事件。
註冊接收器
如要開始接收安全性事件,請使用 RISC API 註冊接收端端點。對 RISC API 的呼叫必須搭配授權權杖。
您只會收到應用程式使用者的安全性事件,因此必須在 GCP 專案中設定 OAuth 同意畫面,才能執行下文所述步驟。
1. 產生授權權杖
如要為 RISC API 產生授權權杖,請使用下列宣告建立 JWT:
{ "iss": SERVICE_ACCOUNT_EMAIL, "sub": SERVICE_ACCOUNT_EMAIL, "aud": "https://risc.googleapis.com/google.identity.risc.v1beta.RiscManagementService", "iat": CURRENT_TIME, "exp": CURRENT_TIME + 3600 }
使用服務帳戶的私密金鑰簽署 JWT,您可以在建立服務帳戶金鑰時下載的 JSON 檔案中找到這組金鑰。
例如:
Java
使用 java-jwt 和 Google 的驗證程式庫:
public static String makeBearerToken() {
String token = null;
try {
// Get signing key and client email address.
FileInputStream is = new FileInputStream("your-service-account-credentials.json");
ServiceAccountCredentials credentials =
(ServiceAccountCredentials) GoogleCredentials.fromStream(is);
PrivateKey privateKey = credentials.getPrivateKey();
String keyId = credentials.getPrivateKeyId();
String clientEmail = credentials.getClientEmail();
// Token must expire in exactly one hour.
Date issuedAt = new Date();
Date expiresAt = new Date(issuedAt.getTime() + 3600000);
// Create signed token.
Algorithm rsaKey = Algorithm.RSA256(null, (RSAPrivateKey) privateKey);
token = JWT.create()
.withIssuer(clientEmail)
.withSubject(clientEmail)
.withAudience("https://risc.googleapis.com/google.identity.risc.v1beta.RiscManagementService")
.withIssuedAt(issuedAt)
.withExpiresAt(expiresAt)
.withKeyId(keyId)
.sign(rsaKey);
} catch (ClassCastException e) {
// Credentials file doesn't contain a service account key.
} catch (IOException e) {
// Credentials file couldn't be loaded.
}
return token;
}
Python
import json
import time
import jwt # pip install pyjwt
def make_bearer_token(credentials_file):
with open(credentials_file) as service_json:
service_account = json.load(service_json)
issuer = service_account['client_email']
subject = service_account['client_email']
private_key_id = service_account['private_key_id']
private_key = service_account['private_key']
issued_at = int(time.time())
expires_at = issued_at + 3600
payload = {'iss': issuer,
'sub': subject,
'aud': 'https://risc.googleapis.com/google.identity.risc.v1beta.RiscManagementService',
'iat': issued_at,
'exp': expires_at}
encoded = jwt.encode(payload, private_key, algorithm='RS256',
headers={'kid': private_key_id})
return encoded
auth_token = make_bearer_token('your-service-account-credentials.json')
這個授權權杖可用於發出 RISC API 呼叫,有效時間為一小時。權杖到期後,請產生新的權杖,以便繼續發出 RISC API 呼叫。
2. 呼叫 RISC 串流設定 API
有了授權權杖後,您可以使用 RISC API 設定專案的安全性事件串流,包括註冊接收端端點。
如要這麼做,請向 https://risc.googleapis.com/v1beta/stream:update
發出 HTTPS POST 要求,指定接收端端點和您感興趣的安全性事件類型:
POST /v1beta/stream:update HTTP/1.1 Host: risc.googleapis.com Authorization: Bearer AUTH_TOKEN { "delivery": { "delivery_method": "https://schemas.openid.net/secevent/risc/delivery-method/push", "url": RECEIVER_ENDPOINT }, "events_requested": [ SECURITY_EVENT_TYPES ] }
例如:
Java
public static void configureEventStream(final String receiverEndpoint,
final List<String> eventsRequested,
String authToken) throws IOException {
ObjectMapper jsonMapper = new ObjectMapper();
String streamConfig = jsonMapper.writeValueAsString(new Object() {
public Object delivery = new Object() {
public String delivery_method =
"https://schemas.openid.net/secevent/risc/delivery-method/push";
public String url = receiverEndpoint;
};
public List<String> events_requested = eventsRequested;
});
HttpPost updateRequest = new HttpPost("https://risc.googleapis.com/v1beta/stream:update");
updateRequest.addHeader("Content-Type", "application/json");
updateRequest.addHeader("Authorization", "Bearer " + authToken);
updateRequest.setEntity(new StringEntity(streamConfig));
HttpResponse updateResponse = new DefaultHttpClient().execute(updateRequest);
Header[] responseContentTypeHeaders = updateResponse.getHeaders("Content-Type");
StatusLine responseStatus = updateResponse.getStatusLine();
int statusCode = responseStatus.getStatusCode();
HttpEntity entity = updateResponse.getEntity();
// Now handle response
}
// ...
configureEventStream(
"https://your-service.example.com/security-event-receiver",
Arrays.asList(
"https://schemas.openid.net/secevent/risc/event-type/account-credential-change-required",
"https://schemas.openid.net/secevent/risc/event-type/account-disabled"),
authToken);
Python
import requests
def configure_event_stream(auth_token, receiver_endpoint, events_requested):
stream_update_endpoint = 'https://risc.googleapis.com/v1beta/stream:update'
headers = {'Authorization': 'Bearer {}'.format(auth_token)}
stream_cfg = {'delivery': {'delivery_method': 'https://schemas.openid.net/secevent/risc/delivery-method/push',
'url': receiver_endpoint},
'events_requested': events_requested}
response = requests.post(stream_update_endpoint, json=stream_cfg, headers=headers)
response.raise_for_status() # Raise exception for unsuccessful requests
configure_event_stream(auth_token, 'https://your-service.example.com/security-event-receiver',
['https://schemas.openid.net/secevent/risc/event-type/account-credential-change-required',
'https://schemas.openid.net/secevent/risc/event-type/account-disabled'])
如果要求傳回 HTTP 200,表示事件串流已成功設定,接收端端點應開始接收安全性事件符記。下一節將說明如何測試串流設定和端點,確認所有項目能否正常運作。
取得及更新目前的串流設定
如果日後想修改串流設定,您可以向 https://risc.googleapis.com/v1beta/stream
提出授權 GET 要求,取得目前的串流設定、修改回應主體,然後將修改後的設定以 POST 方式傳回 https://risc.googleapis.com/v1beta/stream:update
,如上所述。
停止及繼續事件串流
如果您需要停止 Google 的事件串流,請在要求主體中使用 { "status": "disabled" }
,向 https://risc.googleapis.com/v1beta/stream/status:update
提出授權 POST 要求。當串流停用時,Google 就不會將事件傳送至端點,也不會在安全事件發生時緩衝。如要重新啟用事件串流,請將 { "status": "enabled" }
發布至相同的端點。
3. 選用步驟:測試串流設定
您可以透過事件串流傳送驗證權杖,驗證串流設定和接收端端點是否能正常運作。這個權杖可包含專屬字串,您可以使用這個字串驗證端點是否已收到權杖。如要使用這個流程,請務必在註冊接收器時訂閱 https://schemas.openid.net/secevent/risc/event-type/verification 事件類型。
如要要求驗證權杖,請向 https://risc.googleapis.com/v1beta/stream:verify
發出授權的 HTTPS POST 要求。在要求主體中指定一些識別字串:
{ "state": "ANYTHING" }
例如:
Java
public static void testEventStream(final String stateString,
String authToken) throws IOException {
ObjectMapper jsonMapper = new ObjectMapper();
String json = jsonMapper.writeValueAsString(new Object() {
public String state = stateString;
});
HttpPost updateRequest = new HttpPost("https://risc.googleapis.com/v1beta/stream:verify");
updateRequest.addHeader("Content-Type", "application/json");
updateRequest.addHeader("Authorization", "Bearer " + authToken);
updateRequest.setEntity(new StringEntity(json));
HttpResponse updateResponse = new DefaultHttpClient().execute(updateRequest);
Header[] responseContentTypeHeaders = updateResponse.getHeaders("Content-Type");
StatusLine responseStatus = updateResponse.getStatusLine();
int statusCode = responseStatus.getStatusCode();
HttpEntity entity = updateResponse.getEntity();
// Now handle response
}
// ...
testEventStream("Test token requested at " + new Date().toString(), authToken);
Python
import requests
import time
def test_event_stream(auth_token, nonce):
stream_verify_endpoint = 'https://risc.googleapis.com/v1beta/stream:verify'
headers = {'Authorization': 'Bearer {}'.format(auth_token)}
state = {'state': nonce}
response = requests.post(stream_verify_endpoint, json=state, headers=headers)
response.raise_for_status() # Raise exception for unsuccessful requests
test_event_stream(auth_token, 'Test token requested at {}'.format(time.ctime()))
如果要求成功,系統就會將驗證權杖傳送至您註冊的端點。舉例來說,如果端點處理驗證權杖的方式是直接記錄,您可以檢查記錄,確認是否已收到權杖。
錯誤代碼參考資料
RISC API 可能會傳回以下錯誤:
錯誤代碼 | 錯誤訊息 | 建議採取的動作 |
---|---|---|
400 | 串流設定必須包含 $fieldname 欄位。 | 您對 https://risc.googleapis.com/v1beta/stream:update 端點提出的要求無效,或無法剖析。請在要求中加入 $fieldname。 |
401 | 未經授權。 | 無法提供授權,請務必在要求中附上 授權權杖,且該權杖有效且尚未過期。 |
403 | 提交端點必須是 HTTPS 網址。 | 提交端點 (即您希望 RISC 事件提交至的端點) 必須為 HTTPS。我們不會將 RISC 事件傳送至 HTTP 網址。 |
403 | 現有的串流設定沒有符合 RISC 規格的提交方式。 | Google Cloud 專案必須已設定 RISC。如果您使用 Firebase 且已啟用 Google 登入功能,Firebase 就會為您的專案管理 RISC;您將無法建立自訂設定。如果您未在 Firebase 專案中使用 Google 登入,請停用該功能,然後在一小時後再嘗試更新。 |
403 | 找不到專案。 | 請確認您使用的是正確的服務帳戶和專案。您可能使用了與已刪除專案相關聯的服務帳戶。瞭解 如何查看與專案相關聯的所有服務帳戶。 |
403 | 服務帳戶需要存取 RISC 設定的權限 | 前往專案的 ,然後按照這些操作說明,將「RISC 設定管理員」角色 (roles/riscconfigs.admin ) 指派給向專案發出呼叫的服務帳戶。 |
403 | 只有服務帳戶可以呼叫串流管理 API。 | 以下進一步說明如何使用服務帳戶呼叫 Google API。 |
403 | 提交端點不屬於任何專案的網域。 | 每個專案都有一組授權網域。如果您要傳送 RISC 事件的端點 (即端點) 未託管在其中一個端點上,則必須將端點的網域新增至該組。 |
403 | 如要使用這個 API,您的專案至少必須設定一個 OAuth 用戶端。 | 您必須建構支援 Google 登入 的應用程式,RISC 才能運作。這個連線需要 OAuth 用戶端。如果您的專案沒有 OAuth 用戶端,RISC 可能就無法派上用場。進一步瞭解 Google 如何使用 OAuth 存取 API。 |
403 |
不支援的狀態。 狀態無效。 |
我們目前僅支援串流狀態「enabled 」和「disabled 」。 |
404 |
專案沒有 RISC 設定。 專案沒有現有的 RISC 設定,無法更新狀態。 |
呼叫 https://risc.googleapis.com/v1beta/stream:update 端點,建立新的串流設定。 |
4XX/5XX | 無法更新狀態。 | 請查看詳細錯誤訊息,瞭解詳情。 |
存取權杖範圍
如果您決定使用存取權杖來驗證 RISC API,以下是應用程式必須要求的範圍:
端點 | 範圍 |
---|---|
https://risc.googleapis.com/v1beta/stream/status |
https://www.googleapis.com/auth/risc.status.readonly
OR https://www.googleapis.com/auth/risc.status.readwrite |
https://risc.googleapis.com/v1beta/stream/status:update |
https://www.googleapis.com/auth/risc.status.readwrite |
https://risc.googleapis.com/v1beta/stream |
https://www.googleapis.com/auth/risc.configuration.readonly
OR https://www.googleapis.com/auth/risc.configuration.readwrite
|
https://risc.googleapis.com/v1beta/stream:update |
https://www.googleapis.com/auth/risc.configuration.readwrite |
https://risc.googleapis.com/v1beta/stream:verify |
https://www.googleapis.com/auth/risc.verify |
需要協助嗎?
首先,請參閱錯誤代碼參考資料部分。如果您仍有疑問,請在 Stack Overflow 上張貼問題,並加上 #SecEvents 標記。