ปกป้องบัญชีผู้ใช้ด้วยการป้องกันแบบครอบคลุมหลายบริการ

หากแอปอนุญาตให้ผู้ใช้ลงชื่อเข้าใช้บัญชีของตนเองโดยใช้ Google ได้ คุณจะเพิ่มความปลอดภัยให้บัญชีของผู้ใช้ที่แชร์ได้ด้วยการฟังและตอบสนองต่อการแจ้งเตือนเกี่ยวกับเหตุการณ์ด้านความปลอดภัยที่ได้รับจากบริการการป้องกันแบบครอบคลุมหลายบริการ

การแจ้งเตือนเหล่านี้จะแจ้งให้คุณทราบถึงการเปลี่ยนแปลงสำคัญที่เกิดขึ้นกับบัญชี Google ของผู้ใช้ ซึ่งมักส่งผลกระทบด้านความปลอดภัยกับบัญชีของผู้ใช้ในแอปของคุณด้วย เช่น หากมีการลักลอบใช้บัญชี Google ของผู้ใช้ อาจทำให้บัญชีผู้ใช้ในแอปถูกบุกรุกผ่านการกู้คืนบัญชีอีเมลหรือการใช้การลงชื่อเพียงครั้งเดียวได้

Google จะส่งออบเจ็กต์บริการที่เรียกว่าโทเค็นเหตุการณ์ด้านความปลอดภัยเพื่อช่วยคุณลดความเสี่ยงจากเหตุการณ์ดังกล่าว โทเค็นเหล่านี้จะเปิดเผยข้อมูลเพียงเล็กน้อยเท่านั้น ทั้งประเภทของการดำเนินการด้านความปลอดภัยและเวลาที่เกิดเหตุการณ์ขึ้น รวมถึงตัวระบุของผู้ใช้ที่ได้รับผลกระทบ แต่คุณใช้โทเค็นดังกล่าวในการดำเนินการตามความเหมาะสมได้ เช่น หากบัญชี Google ของผู้ใช้ถูกบุกรุก คุณอาจปิดใช้ฟีเจอร์ลงชื่อเข้าใช้ด้วย Google สำหรับผู้ใช้รายนั้นไว้ชั่วคราวและป้องกันไม่ให้ส่งอีเมลการกู้คืนบัญชีไปยังที่อยู่ Gmail ของผู้ใช้ได้

การป้องกันแบบครอบคลุมหลายบริการอิงจากมาตรฐาน RISC ซึ่งพัฒนาขึ้นโดยมูลนิธิ OpenID

ภาพรวม

หากต้องการใช้การป้องกันแบบครอบคลุมหลายบริการกับแอปหรือบริการ คุณต้องทำงานต่อไปนี้ให้เสร็จสิ้น

  1. ตั้งค่าโปรเจ็กต์ใน API Console

  2. สร้างปลายทางตัวรับเหตุการณ์ซึ่ง Google จะส่งโทเค็นสำหรับเหตุการณ์ด้านความปลอดภัยไปให้ ปลายทางนี้มีหน้าที่ตรวจสอบโทเค็นที่ได้รับ แล้วตอบสนองต่อการดำเนินการด้านความปลอดภัยด้วยวิธีใดก็ตามที่คุณเลือก

  3. ลงทะเบียนปลายทางกับ Google เพื่อเริ่มรับโทเค็นการดำเนินการด้านความปลอดภัย

วิชาบังคับก่อน

คุณจะได้รับโทเค็นการดำเนินการด้านความปลอดภัยสำหรับผู้ใช้ Google ที่อนุญาตให้ใช้บริการของคุณในการเข้าถึงข้อมูลโปรไฟล์หรืออีเมลเท่านั้น คุณได้รับสิทธิ์นี้จากการส่งคำขอขอบเขต profile หรือ email ลงชื่อเข้าใช้ด้วย Google เวอร์ชันใหม่หรือ SDK Google Sign-In เดิมจะขอขอบเขตเหล่านี้โดยค่าเริ่มต้น แต่หากคุณไม่ได้ใช้การตั้งค่าเริ่มต้น หรือหากคุณเข้าถึงปลายทาง OpenID Connect ของ Google โดยตรง คุณต้องขอขอบเขตเหล่านี้อย่างน้อย 1 ขอบเขต

ตั้งค่าโปรเจ็กต์ใน API Console

ก่อนที่จะเริ่มรับโทเค็นการดำเนินการด้านความปลอดภัย คุณต้องสร้างบัญชีบริการและเปิดใช้ RISC API ในโปรเจ็กต์API Console คุณต้องใช้API Console โปรเจ็กต์เดียวกับที่ใช้ในการเข้าถึงบริการของ Google เช่น Google Sign-In ในแอป

วิธีสร้างบัญชีบริการมีดังนี้

  1. เปิด API Console Credentials page เมื่อได้รับข้อความแจ้ง ให้เลือกAPI Consoleโปรเจ็กต์ที่คุณใช้เพื่อเข้าถึงบริการของ Google ในแอป

  2. คลิกสร้างข้อมูลเข้าสู่ระบบ > บัญชีบริการ

  3. สร้างบัญชีบริการใหม่ด้วยบทบาทผู้ดูแลระบบการกำหนดค่า RISC (roles/riscconfigs.admin) โดยทำตามวิธีการเหล่านี้

  4. สร้างคีย์สำหรับบัญชีบริการที่สร้างขึ้นใหม่ เลือกประเภทคีย์ JSON แล้วคลิก Create เมื่อสร้างคีย์แล้ว คุณจะดาวน์โหลดไฟล์ JSON ที่มีข้อมูลเข้าสู่ระบบของบัญชีบริการได้ เก็บไฟล์นี้ไว้ในที่ที่ปลอดภัย แต่ปลายทางรับเหตุการณ์จะเข้าถึงได้

เมื่ออยู่ในหน้าข้อมูลเข้าสู่ระบบของโปรเจ็กต์ โปรดจดรหัสไคลเอ็นต์ที่คุณใช้สำหรับฟีเจอร์ลงชื่อเข้าใช้ด้วย Google หรือ Google Sign-In (เดิม) ด้วย โดยทั่วไป คุณจะมีรหัสไคลเอ็นต์สำหรับแต่ละแพลตฟอร์มที่รองรับ คุณจะต้องใช้รหัสไคลเอ็นต์เหล่านี้เพื่อตรวจสอบโทเค็นเหตุการณ์ด้านความปลอดภัย ตามที่อธิบายไว้ในส่วนถัดไป

วิธีเปิดใช้ RISC API

  1. เปิดหน้า RISC API ในAPI Consoleตรวจสอบว่ายังเลือกโปรเจ็กต์ที่คุณใช้ เพื่อเข้าถึงบริการของ Google อยู่

  2. อ่านข้อกำหนดของ RISC และดูให้แน่ใจว่าคุณเข้าใจข้อกำหนดแล้ว

    หากคุณเปิดใช้ API สำหรับโปรเจ็กต์ขององค์กร โปรดตรวจสอบว่าคุณมีสิทธิ์เชื่อมโยงองค์กรกับข้อกำหนดของ RISC

  3. คลิกเปิดใช้เฉพาะเมื่อคุณยินยอมตามข้อกำหนด RISC เท่านั้น

สร้างปลายทางตัวรับเหตุการณ์

หากต้องการรับการแจ้งเตือนการดำเนินการด้านความปลอดภัยจาก Google คุณต้องสร้างปลายทาง HTTPS ที่จัดการคำขอ HTTPS POST หลังจากที่คุณลงทะเบียนปลายทางนี้ (ดูด้านล่าง) Google จะเริ่มโพสต์สตริงที่มีการรับรองแบบเข้ารหัสที่เรียกว่าโทเค็นเหตุการณ์ความปลอดภัยไปยังปลายทาง โทเค็นการดำเนินการด้านความปลอดภัยคือ JWT ที่มีการรับรอง ซึ่งมีข้อมูลเกี่ยวกับเหตุการณ์ที่เกี่ยวข้องกับความปลอดภัยรายการเดียว

สำหรับโทเค็นการดำเนินการด้านความปลอดภัยแต่ละรายการที่คุณได้รับที่ปลายทาง ให้ตรวจสอบและถอดรหัสโทเค็นก่อน จากนั้นจัดการการดำเนินการด้านความปลอดภัยตามความเหมาะสมสำหรับบริการของคุณ จําเป็นต้องตรวจสอบโทเค็นเหตุการณ์ก่อนถอดรหัสเพื่อป้องกันการโจมตีที่เป็นอันตรายจากผู้ไม่ประสงค์ดี ส่วนต่อไปนี้จะอธิบายงานเหล่านี้

1. ถอดรหัสและตรวจสอบโทเค็นการดำเนินการด้านความปลอดภัย

เนื่องจากโทเค็นการดำเนินการด้านความปลอดภัยเป็น JWT ประเภทหนึ่งโดยเฉพาะ คุณจึงสามารถใช้ไลบรารี JWT ใดก็ได้ เช่น ไลบรารีที่อยู่ใน jwt.io เพื่อถอดรหัสและตรวจสอบ ไม่ว่าจะใช้ไลบรารีใด รหัสการตรวจสอบโทเค็นจะต้องทำดังต่อไปนี้

  1. รับตัวระบุผู้ออกการป้องกันแบบครอบคลุมหลายบริการ (issuer) และ URI ของใบรับรองคีย์ (jwks_uri) จากเอกสารการกำหนดค่า RISC ของ Google ซึ่งดูได้ที่ https://accounts.google.com/.well-known/risc-configuration
  2. ใช้ไลบรารี JWT ที่คุณเลือกเพื่อรับรหัสคีย์ Signing จากส่วนหัวของโทเค็นเหตุการณ์ความปลอดภัย
  3. จากเอกสารใบรับรองคีย์การลงชื่อของ Google ให้รับคีย์สาธารณะที่มีรหัสคีย์ซึ่งได้รับในขั้นตอนก่อนหน้า หากเอกสารไม่มีคีย์ที่มีรหัสที่กำลังค้นหา อาจเป็นไปได้ว่าโทเค็นการดำเนินการด้านความปลอดภัยไม่ถูกต้อง และปลายทางควรแสดงข้อผิดพลาด HTTP 400
  4. ยืนยันข้อมูลต่อไปนี้โดยใช้ไลบรารี JWT ที่ต้องการ
    • โทเค็นการดำเนินการด้านความปลอดภัยจะลงนามโดยใช้คีย์สาธารณะที่คุณได้รับในขั้นตอนก่อนหน้า
    • การอ้างสิทธิ์ aud ของโทเค็นเป็นหนึ่งในรหัสไคลเอ็นต์ของแอป
    • การอ้างสิทธิ์ iss ของโทเค็นตรงกับตัวระบุผู้ออกที่คุณได้รับจากเอกสารการค้นพบ RISC โปรดทราบว่าคุณไม่จำเป็นต้องยืนยันวันหมดอายุของโทเค็น (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:
                // https://console.developers.google.com/apis/credentials?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:
# https://console.developers.google.com/apis/credentials?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 เป็นสตริงที่ระบุการดำเนินการด้านความปลอดภัย 1 รายการ และไม่ซ้ำกันสำหรับสตรีม คุณสามารถใช้ตัวระบุนี้เพื่อติดตามการดำเนินการด้านความปลอดภัยที่คุณได้รับ

การอ้างสิทธิ์ events มีข้อมูลเกี่ยวกับเหตุการณ์ด้านความปลอดภัยที่โทเค็นนำเสนอ การอ้างสิทธิ์นี้เป็นการแมปจากตัวระบุประเภทเหตุการณ์กับการอ้างสิทธิ์ subject ซึ่งระบุผู้ใช้เกี่ยวกับเหตุการณ์นี้ รวมถึงรายละเอียดเพิ่มเติมเกี่ยวกับเหตุการณ์ที่อาจมี

การอ้างสิทธิ์ subject จะระบุผู้ใช้รายใดรายหนึ่งด้วยรหัสบัญชี Google (sub) ที่ไม่ซ้ำกันของผู้ใช้ รหัสบัญชี Google นี้เป็นตัวระบุเดียวกัน (sub) ที่มีอยู่ในโทเค็นรหัส JWT ที่ออกโดยไลบรารีฟีเจอร์ลงชื่อเข้าใช้ด้วย Google รุ่นใหม่ (Javascript, HTML), ไลบรารี Google Sign-In เดิม หรือ OpenID Connect เมื่อ subject_type ของการอ้างสิทธิ์คือ id_token_claims ก็อาจมีช่อง email สำหรับอีเมลของผู้ใช้ด้วย

ใช้ข้อมูลในการอ้างสิทธิ์ events เพื่อดำเนินการที่เหมาะสมกับประเภทเหตุการณ์ในบัญชีของผู้ใช้ที่ระบุ

ตัวระบุโทเค็น OAuth

สำหรับเหตุการณ์ OAuth เกี่ยวกับโทเค็นแต่ละรายการ ประเภทตัวระบุหัวเรื่องของโทเค็นจะมีช่องต่อไปนี้

  • 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 Sign-In ให้สิ้นสุดเซสชันที่ใช้งานอยู่ในปัจจุบัน นอกจากนี้ คุณอาจต้องแนะนำให้ผู้ใช้ตั้งค่าวิธีลงชื่อเข้าใช้อื่น

แนะนำ: หากโทเค็นมีไว้สําหรับเข้าถึง Google API อื่นๆ ให้ลบโทเค็น OAuth ของผู้ใช้ที่เก็บไว้

https://schemas.openid.net/secevent/oauth/event-type/token-revoked ดูตัวระบุโทเค็นในส่วนตัวระบุโทเค็น OAuth

จำเป็น: หากคุณจัดเก็บโทเค็นการรีเฟรชที่เกี่ยวข้อง ให้ลบโทเค็นดังกล่าวและขอให้ผู้ใช้ยินยอมอีกครั้งในครั้งถัดไปที่ต้องใช้โทเค็นเพื่อการเข้าถึง

https://schemas.openid.net/secevent/risc/event-type/account-disabled reason=hijacking
reason=bulk-account

จำเป็น: หากเหตุผลที่บัญชีถูกปิดใช้คือ hijacking ให้รักษาความปลอดภัยบัญชีของผู้ใช้อีกครั้งโดยการสิ้นสุดเซสชันที่เปิดอยู่ในปัจจุบัน

แนะนำ: หากสาเหตุที่บัญชีถูกปิดใช้คือ bulk-account ให้วิเคราะห์กิจกรรมของผู้ใช้ในบริการของคุณและกำหนดการดำเนินการติดตามผลที่เหมาะสม

แนะนำ: หากไม่ได้ระบุเหตุผล ให้ปิดใช้ Google Sign-In สำหรับผู้ใช้และปิดใช้การกู้คืนบัญชีโดยใช้อีเมลที่เชื่อมโยงกับบัญชี Google ของผู้ใช้ (โดยทั่วไปอาจไม่ใช่บัญชี Gmail เสมอไป) เสนอวิธีการลงชื่อเข้าใช้อื่นให้ผู้ใช้

https://schemas.openid.net/secevent/risc/event-type/account-enabled แนะนำ: เปิดใช้ Google Sign-In อีกครั้งสำหรับผู้ใช้ และเปิดใช้การกู้คืนบัญชีอีกครั้งด้วยอีเมลบัญชี Google ของผู้ใช้
https://schemas.openid.net/secevent/risc/event-type/account-purged แนะนำ: ลบบัญชีของผู้ใช้หรือเสนอวิธีอื่นในการลงชื่อเข้าใช้ให้กับผู้ใช้
https://schemas.openid.net/secevent/risc/event-type/account-credential-change-required แนะนำ: ระวังกิจกรรมที่น่าสงสัยในบริการและดำเนินการตามความเหมาะสม
https://schemas.openid.net/secevent/risc/event-type/verification state=state แนะนำ: บันทึกว่าได้รับโทเค็นทดสอบแล้ว

กิจกรรมซ้ำและกิจกรรมที่พลาดไป

การป้องกันแบบครอบคลุมหลายบริการจะพยายามส่งเหตุการณ์ที่เชื่อว่าไม่มีการส่งอีกครั้ง ดังนั้นบางครั้งคุณอาจได้รับเหตุการณ์เดียวกันหลายครั้ง หากวิธีนี้อาจทำให้เกิดการดำเนินการซ้ำๆ ที่สร้างความไม่สะดวกต่อผู้ใช้ ให้พิจารณาใช้การอ้างสิทธิ์ jti (ซึ่งเป็นตัวระบุที่ไม่ซ้ำกันสำหรับเหตุการณ์) เพื่อขจัดเหตุการณ์ซ้ำ มีเครื่องมือภายนอก เช่น Google Cloud Dataflow ที่อาจช่วยให้คุณดำเนินการโฟลว์ข้อมูลที่ทำซ้ำได้

โปรดทราบว่าระบบจะส่งกิจกรรมโดยพยายามส่งอีกครั้งแบบจำกัด ดังนั้นหากผู้รับสายของคุณไม่ได้ใช้งานเป็นเวลานาน คุณอาจพลาดบางกิจกรรมไปอย่างถาวร

ลงทะเบียนผู้รับ

หากต้องการเริ่มรับการดำเนินการด้านความปลอดภัย ให้ลงทะเบียนปลายทางตัวรับโดยใช้ RISC API การเรียกใช้ RISC API ต้องมีโทเค็นการให้สิทธิ์ด้วย

คุณจะได้รับการดำเนินการด้านความปลอดภัยสำหรับผู้ใช้แอปเท่านั้น คุณจึงต้องกำหนดค่าหน้าจอคำยินยอม OAuth ในโปรเจ็กต์ GCP ซึ่งเป็นข้อกำหนดเบื้องต้นสำหรับขั้นตอนที่อธิบายไว้ด้านล่าง

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 ได้เป็นเวลา 1 ชั่วโมง เมื่อโทเค็นหมดอายุ ให้สร้างโทเค็นใหม่เพื่อเรียก RISC API ต่อไป

2. เรียกใช้ API การกำหนดค่าสตรีม RISC

เมื่อมีโทเค็นการให้สิทธิ์แล้ว คุณจะใช้ RISC API เพื่อกำหนดค่าสตรีมเหตุการณ์ความปลอดภัยของโปรเจ็กต์ รวมถึงลงทะเบียนปลายทางของผู้รับได้

ซึ่งทำได้โดยส่งคำขอ HTTPS POST ไปยัง https://risc.googleapis.com/v1beta/stream:update โดยระบุปลายทางเครื่องรับและประเภทของเหตุการณ์ด้านความปลอดภัยที่คุณสนใจ

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 แสดงว่ากำหนดค่าสตรีมเหตุการณ์เรียบร้อยแล้วและปลายทางของตัวรับควรเริ่มได้รับโทเค็นเหตุการณ์ด้านความปลอดภัย ส่วนถัดไปจะอธิบายวิธีทดสอบการกำหนดค่าสตรีมและปลายทางเพื่อยืนยันว่าทุกอย่างทำงานร่วมกันได้อย่างถูกต้อง

รับและอัปเดตการกำหนดค่าสตรีมปัจจุบัน

หากในอนาคตคุณต้องการแก้ไขการกำหนดค่าสตรีม ก็สามารถทำได้โดยส่งคำขอ GET ที่ได้รับอนุญาตไปยัง https://risc.googleapis.com/v1beta/stream เพื่อรับการกำหนดค่าสตรีมปัจจุบัน แก้ไขเนื้อความการตอบกลับ แล้วโพสต์การกำหนดค่าที่แก้ไขแล้วกลับไปยัง https://risc.googleapis.com/v1beta/stream:update ตามที่อธิบายไว้ข้างต้น

หยุดและสตรีมเหตุการณ์ต่อ

หากคุณจำเป็นต้องหยุดสตรีมกิจกรรมจาก Google ให้ส่งคำขอ POST ที่ได้รับอนุญาตไปยัง https://risc.googleapis.com/v1beta/stream/status:update โดยมี { "status": "disabled" } ในเนื้อหาคำขอ ขณะที่สตรีมปิดใช้งานอยู่ Google จะไม่ส่งเหตุการณ์ไปยังปลายทางและจะไม่บัฟเฟอร์การดำเนินการด้านความปลอดภัยเมื่อเกิดขึ้น หากต้องการเปิดใช้สตรีมเหตุการณ์อีกครั้ง ให้โพสต์ { "status": "enabled" } ไปยังปลายทางเดียวกัน

3. ไม่บังคับ: ทดสอบการกำหนดค่าสตรีม

คุณสามารถยืนยันว่าการกำหนดค่าสตรีมและปลายทางตัวรับทำงานด้วยกันได้อย่างถูกต้องโดยการส่งโทเค็นการยืนยันผ่านสตรีมเหตุการณ์ โทเค็นนี้อาจมีสตริงที่ไม่ซ้ำกันซึ่งใช้ยืนยันว่าปลายทางได้รับโทเค็นแล้ว หากต้องการใช้ขั้นตอนนี้ โปรดสมัครใช้บริการประเภทเหตุการณ์ https://schemas.openid.net/secevent/risc/event-type/verification เมื่อลงทะเบียนผู้รับ

หากต้องการขอโทเค็นการยืนยัน ให้ส่งคำขอ HTTPS POST ที่ได้รับอนุญาตไปยัง https://risc.googleapis.com/v1beta/stream:verify ในส่วนเนื้อหาของคำขอ ให้ระบุสตริงที่ระบุดังนี้

{
  "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 URL ปลายทางการนำส่ง (เช่น ปลายทางที่คุณคาดว่าจะมีการนำส่งเหตุการณ์ RISC ไปให้) ต้องเป็น HTTPS เราจะไม่ส่งเหตุการณ์ RISC ไปยัง HTTP URL
403 การกำหนดค่าสตรีมที่มีอยู่ไม่มีวิธีการแสดงโฆษณาที่เป็นไปตามข้อกำหนดสำหรับ RISC โปรเจ็กต์ Google Cloud ต้องมีการกําหนดค่า RISC อยู่แล้ว หากคุณใช้ Firebase และเปิดใช้ Google Sign-In อยู่ Firebase จะจัดการ RISC สำหรับโปรเจ็กต์ของคุณ คุณจะสร้างการกำหนดค่าที่กำหนดเองไม่ได้ หากไม่ได้ใช้ Google Sign-In สำหรับโปรเจ็กต์ Firebase โปรดปิดใช้ แล้วลองอัปเดตอีกครั้งหลังจากผ่านไป 1 ชั่วโมง
403 ไม่พบโปรเจ็กต์ ตรวจสอบว่าคุณใช้บัญชีบริการที่ถูกต้องสำหรับโปรเจ็กต์ที่ถูกต้อง คุณอาจใช้บัญชีบริการที่เชื่อมโยงกับโปรเจ็กต์ที่ลบไปแล้ว ดู วิธีดูบัญชีบริการทั้งหมดที่เชื่อมโยงกับโปรเจ็กต์
403 บัญชีบริการต้องมีสิทธิ์เข้าถึงการกำหนดค่า RISC ไปที่ API Console ของโปรเจ็กต์และมอบหมายบทบาท "ผู้ดูแลระบบการกำหนดค่า RISC" (roles/riscconfigs.admin) ให้กับบัญชีบริการที่เรียกใช้โปรเจ็กต์โดยทำตามวิธีการเหล่านี้
403 บัญชีบริการเท่านั้นที่ควรเรียกใช้ API การจัดการสตรีม ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีเรียกใช้ Google APIs ด้วยบัญชีบริการ
403 ปลายทางการส่งไม่ได้อยู่ในโดเมนของโปรเจ็กต์ ทุกโปรเจ็กต์จะมีชุดโดเมนที่ได้รับอนุญาต หากปลายทางการนำส่ง (เช่น ปลายทางที่คุณคาดว่าจะมีการนำส่งเหตุการณ์ RISC ไปให้) ไม่ได้โฮสต์อยู่ในปลายทางใดปลายทางหนึ่ง เราจำเป็นต้องให้คุณเพิ่มโดเมนของปลายทางในชุดดังกล่าว
403 หากต้องการใช้ API นี้ โปรเจ็กต์ต้องมีการกำหนดค่าไคลเอ็นต์ OAuth อย่างน้อย 1 รายการ RISC จะทำงานก็ต่อเมื่อคุณสร้างแอปที่รองรับ Google Sign-In เท่านั้น การเชื่อมต่อนี้ต้องใช้ไคลเอ็นต์ OAuth หากโปรเจ็กต์ของคุณไม่มีไคลเอ็นต์ OAuth ก็มีแนวโน้มที่ RISC จะไม่เป็นประโยชน์ต่อคุณ ดูข้อมูลเพิ่มเติมเกี่ยวกับการใช้ OAuth ของ Google สำหรับ 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 หรือ 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 หรือ 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