使用 Python 專用的 Cloud Identity-Aware Proxy 驗證使用者

對於在 Google Cloud 代管平台 (例如 App Engine) 上執行的應用程式,您可透過 Identity-Aware Proxy (IAP) 控管存取權,無須自行管理使用者驗證和工作階段。IAP 不僅可控管對應用程式的存取權,還能提供已驗證使用者的相關資訊,包括電子郵件地址和應用程式的永久 ID (採用新的 HTTP 標頭)。

目標

  • 要求 App Engine 應用程式的使用者必須使用 IAP 驗證自己的身分。

  • 在應用程式中存取使用者的身分,以顯示目前使用者已驗證的電子郵件地址。

費用

在本文件中,您會使用下列 Google Cloud的計費元件:

如要根據預測用量估算費用,請使用 Pricing Calculator

初次使用 Google Cloud 的使用者可能符合免費試用期資格。

完成本文所述工作後,您可以刪除建立的資源,避免繼續計費,詳情請參閱「清除所用資源」。

事前準備

  1. 登入 Google Cloud 帳戶。如果您是 Google Cloud新手,歡迎 建立帳戶,親自評估產品在實際工作環境中的成效。新客戶還能獲得價值 $300 美元的免費抵免額,可用於執行、測試及部署工作負載。
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  3. 安裝 Google Cloud CLI。

  4. 若您採用的是外部識別資訊提供者 (IdP),請先使用聯合身分登入 gcloud CLI

  5. 執行下列指令,初始化 gcloud CLI:

    gcloud init
  6. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  7. 安裝 Google Cloud CLI。

  8. 若您採用的是外部識別資訊提供者 (IdP),請先使用聯合身分登入 gcloud CLI

  9. 執行下列指令,初始化 gcloud CLI:

    gcloud init

背景

此教學課程使用 IAP 對使用者進行身分驗證。這只是其中一種方法。如要進一步瞭解驗證使用者的不同方法,請參閱驗證概念一節。

Hello user-email-address 應用程式

此教學課程使用的應用程式是最小型的 Hello World App Engine 應用程式,其中有一非典型功能:顯示「Hello user-email-address」而非「Hello world」,而 user-email-address 是已驗證的使用者電子郵件地址。

之所以可提供此功能,是因為系統會檢查 IAP 每次傳遞網路要求給應用程式時新增的驗證資訊。每個傳遞至應用程式的網路要求都會新增三個新的要求標頭。前兩個是純文字字串,可以用來識別使用者身分。第三個標頭是具有相同資訊的加密簽署物件。

  • X-Goog-Authenticated-User-Email:使用者的電子郵件地址可用於識別身分。如果可以,請不要讓您的應用程式儲存個人資訊。這個應用程式不會儲存任何資料,而只是回應給使用者。

  • X-Goog-Authenticated-User-Id:此由 Google 指派的使用者 ID 不會顯示使用者的相關資訊,但可讓應用程式知道登入的使用者是先前有出現過的。

  • X-Goog-Iap-Jwt-Assertion:您可以將 Google Cloud 應用程式設為略過 IAP,使其除了接受來自網際網路的網路要求之外,還接受來自其他雲端應用程式的網路要求。如果將應用程式如此設定,則此類要求可能會具有假造的標頭。您可以不使用先前提到的純文字標頭,改為使用並驗證這個經過加密簽署的標頭,確認資訊是由 Google 提供。這個已簽署的標頭中會包含使用者電子郵件地址和永久使用者 ID。

如果您確定已將應用程式設為僅有來自網際網路的網路要求可以與其連線,且無人能停用應用程式的 IAP 服務,則擷取不重複使用者 ID 僅需一行程式碼:

user_id = request.headers.get('X-Goog-Authenticated-User-ID')

但是,有彈性的應用程式可能會發生錯誤,包括非預期的設定或環境問題,因此我們建議建立一個可使用並驗證經加密簽署標頭的函式。這個標頭的簽署無法假造,驗證後即可用於傳回身分識別。

建立原始碼

  1. 使用文字編輯器建立名為 main.py 的檔案,並將下列程式碼貼入其中:

    import sys
    
    from flask import Flask
    app = Flask(__name__)
    
    CERTS = None
    AUDIENCE = None
    
    
    def certs():
        """Returns a dictionary of current Google public key certificates for
        validating Google-signed JWTs. Since these change rarely, the result
        is cached on first request for faster subsequent responses.
        """
        import requests
    
        global CERTS
        if CERTS is None:
            response = requests.get(
                'https://www.gstatic.com/iap/verify/public_key'
            )
            CERTS = response.json()
        return CERTS
    
    
    def get_metadata(item_name):
        """Returns a string with the project metadata value for the item_name.
        See https://cloud.google.com/compute/docs/storing-retrieving-metadata for
        possible item_name values.
        """
        import requests
    
        endpoint = 'http://metadata.google.internal'
        path = '/computeMetadata/v1/project/'
        path += item_name
        response = requests.get(
            '{}{}'.format(endpoint, path),
            headers={'Metadata-Flavor': 'Google'}
        )
        metadata = response.text
        return metadata
    
    
    def audience():
        """Returns the audience value (the JWT 'aud' property) for the current
        running instance. Since this involves a metadata lookup, the result is
        cached when first requested for faster future responses.
        """
        global AUDIENCE
        if AUDIENCE is None:
            project_number = get_metadata('numeric-project-id')
            project_id = get_metadata('project-id')
            AUDIENCE = '/projects/{}/apps/{}'.format(
                project_number, project_id
            )
        return AUDIENCE
    
    
    def validate_assertion(assertion):
        """Checks that the JWT assertion is valid (properly signed, for the
        correct audience) and if so, returns strings for the requesting user's
        email and a persistent user ID. If not valid, returns None for each field.
        """
        from jose import jwt
    
        try:
            info = jwt.decode(
                assertion,
                certs(),
                algorithms=['ES256'],
                audience=audience()
                )
            return info['email'], info['sub']
        except Exception as e:
            print('Failed to validate assertion: {}'.format(e), file=sys.stderr)
            return None, None
    
    
    @app.route('/', methods=['GET'])
    def say_hello():
        from flask import request
    
        assertion = request.headers.get('X-Goog-IAP-JWT-Assertion')
        email, id = validate_assertion(assertion)
        page = "<h1>Hello {}</h1>".format(email)
        return page

    本教學課程稍後的認識程式碼一節會詳加說明 main.py 檔案。

  2. 建立另一個名為 requirements.txt 檔案,並將下列內容貼入其中:

    Flask==2.2.5
    cryptography==41.0.2
    python-jose[cryptography]==3.3.0
    requests==2.31.0

    requirements.txt 檔案列出應用程式需要 App Engine 為其載入的所有非標準 Python 程式庫:

    • Flask 是應用程式使用的 Python 網頁架構。

    • cryptography 模組提供強大的密碼編譯函式。

    • python-jose[cryptography] 提供 JWT 檢查和解碼的功能。

    • requests 會從網站擷取資料。

  3. 建立名為 app.yaml 的檔案,並在其中加入下列文字:

    runtime: python37

    app.yaml 檔案會告知 App Engine 程式碼所需的語言環境。

瞭解程式碼

本節將說明 main.py 程式碼的運作方式。如果您只想執行該應用程式,請直接跳到部署應用程式一節。

下列程式碼位於 main.py 檔案中。當應用程式收到對首頁的 HTTP GET 要求時,Flask 架構會叫用 say_hello 函式:

@app.route('/', methods=['GET'])
def say_hello():
    from flask import request

    assertion = request.headers.get('X-Goog-IAP-JWT-Assertion')
    email, id = validate_assertion(assertion)
    page = "<h1>Hello {}</h1>".format(email)
    return page

say_hello 函式會取得 IAP 從傳入要求中加入的 JWT 斷言標頭值,並呼叫函式以驗證經加密簽署的值。接下來,函式會在其所建立並傳回的最小網頁中使用傳回的第一個值 (電子郵件)。

def validate_assertion(assertion):
    """Checks that the JWT assertion is valid (properly signed, for the
    correct audience) and if so, returns strings for the requesting user's
    email and a persistent user ID. If not valid, returns None for each field.
    """
    from jose import jwt

    try:
        info = jwt.decode(
            assertion,
            certs(),
            algorithms=['ES256'],
            audience=audience()
            )
        return info['email'], info['sub']
    except Exception as e:
        print('Failed to validate assertion: {}'.format(e), file=sys.stderr)
        return None, None

validate_assertion 函式使用第三方 jose 程式庫中的 jwt.decode 函式來驗證斷言是否正確簽署,並從斷言中擷取酬載資訊。這項資訊就是已驗證的使用者電子郵件地址,以及該使用者的永久唯一識別碼。如果無法解碼斷言,這個函式會為每個值傳回 None,並輸出訊息以記錄錯誤。

如要驗證 JWT 斷言,您必須知道簽署斷言的實體 (在此為 Google) 的公開金鑰憑證,以及斷言的目標對象。對於 App Engine 應用程式而言,目標對象是包含 Google Cloud 專案識別資訊的字串。此函式會從其先前的函式中取得憑證和目標對象字串。

def audience():
    """Returns the audience value (the JWT 'aud' property) for the current
    running instance. Since this involves a metadata lookup, the result is
    cached when first requested for faster future responses.
    """
    global AUDIENCE
    if AUDIENCE is None:
        project_number = get_metadata('numeric-project-id')
        project_id = get_metadata('project-id')
        AUDIENCE = '/projects/{}/apps/{}'.format(
            project_number, project_id
        )
    return AUDIENCE

您可以自行查詢專案的數字 ID 和名稱,並在原始碼中加入這些資訊,但 audience 函式也能為您代勞,方法是查詢每個 App Engine 應用程式可用的標準中繼資料服務。由於中繼資料服務位於應用程式程式碼外部,因此結果會儲存在全域變數中,後續呼叫時不必查詢中繼資料,即可傳回結果。 Google Cloud

def get_metadata(item_name):
    """Returns a string with the project metadata value for the item_name.
    See https://cloud.google.com/compute/docs/storing-retrieving-metadata for
    possible item_name values.
    """
    import requests

    endpoint = 'http://metadata.google.internal'
    path = '/computeMetadata/v1/project/'
    path += item_name
    response = requests.get(
        '{}{}'.format(endpoint, path),
        headers={'Metadata-Flavor': 'Google'}
    )
    metadata = response.text
    return metadata

App Engine 中繼資料服務 (以及其他Google Cloud 運算服務中類似的中繼資料服務) 外觀如同網站,可透過標準網路查詢進行查詢。不過此中繼資料服務實際上不是外部網站,而是內部功能。此功能可傳回與執行中應用程式相關的所需資訊,因此您可以安全地使用 http 而不須使用 https 要求。此服務可用於取得目前所需的 Google Cloud ID,藉此定義 JWT 斷言的目標對象。

def certs():
    """Returns a dictionary of current Google public key certificates for
    validating Google-signed JWTs. Since these change rarely, the result
    is cached on first request for faster subsequent responses.
    """
    import requests

    global CERTS
    if CERTS is None:
        response = requests.get(
            'https://www.gstatic.com/iap/verify/public_key'
        )
        CERTS = response.json()
    return CERTS

簽署者的公用金鑰憑證是驗證數位簽名時的必要資訊。Google 提供了一個網站,可傳回目前使用中的所有公用金鑰憑證。傳回的結果會存放在快取中,以利同一個應用程式執行個體在需要時重複使用。

部署應用程式

現在您可以部署應用程式,然後啟用 IAP,要求使用者必須經過驗證才能存取應用程式。

  1. 在終端機視窗中,前往包含 app.yaml 檔案的目錄,然後將應用程式部署至 App Engine:

    gcloud app deploy
    
  2. 請在系統提示時選取附近的地區。

  3. 當系統詢問您是否要繼續進行部署作業時,請輸入 Y

    您的應用程式在幾分鐘內即會上線。

  4. 查看應用程式:

    gcloud app browse
    

    在輸出中,複製 web-site-url (應用程式的網址)。

  5. 在瀏覽器視窗中,貼上 web-site-url 即可開啟應用程式。

    由於您尚未使用 IAP,因此系統不會顯示任何電子郵件,也不會傳送任何使用者資訊給應用程式。

啟用 IAP

現在有了 App Engine 執行個體,您可以使用 IAP 進行保護:

  1. 前往 Google Cloud 控制台的「Identity-Aware Proxy」頁面。

    前往「Identity-Aware Proxy」頁面

  2. 由於這是您第一次為這項專案啟用驗證選項,因此系統會顯示一則訊息,說明您必須先設定 OAuth 同意畫面,才能使用 IAP。

    按一下 [Configure consent screen] (設定同意畫面)

  3. 在「Credentials」(憑證) 頁面的「OAuth Consent Screen」(OAuth 同意畫面) 分頁中,填寫下列欄位:

    • 如果帳戶屬於 Google Workspace 機構,請選取「外部」並按一下「建立」。一開始,只有您明確允許的使用者才能存取應用程式。

    • 在「Application name」(應用程式名稱) 欄位中,輸入 IAP Example

    • 在「Support email」(支援電子郵件) 欄位中,輸入您的電子郵件地址。

    • 在「Authorized domain」(授權網域) 欄位中,輸入應用程式網址的主機名稱部分,例如 iap-example-999999.uc.r.appspot.com。在欄位中輸入主機名稱後,按下 Enter 鍵。

    • 在「Application homepage link」(應用程式首頁連結) 欄位中,輸入應用程式的網址,例如 https://iap-example-999999.uc.r.appspot.com/

    • 在「Application privacy policy line」(應用程式隱私權政策行) 欄位中,使用與首頁連結相同的網址進行測試。

  4. 按一下 [Save] (儲存),在系統提示您建立憑證時,您可以關閉視窗。

  5. 前往 Google Cloud 控制台的「Identity-Aware Proxy」頁面。

    前往「Identity-Aware Proxy」頁面

  6. 如要重新整理頁面,請按一下「重新整理」圖示 。該頁面會列出您可以保護的資源。

  7. 在「IAP」資料欄中,按一下以開啟應用程式的 IAP。

  8. 在瀏覽器中,再次前往 web-site-url

  9. 除了網頁外,您也可以透過登入畫面來驗證自己的身分。當您登入時,由於 IAP 未具備允許進入應用程式的使用者清單,因此系統會拒絕您的存取。

將已獲授權的使用者加入應用程式

  1. 前往 Google Cloud 控制台的「Identity-Aware Proxy」頁面。

    前往「Identity-Aware Proxy」頁面

  2. 勾選 App Engine 應用程式的核取方塊,然後按一下「新增主體」

  3. 輸入 allAuthenticatedUsers,然後選取 [Cloud IAP/IAP-Secured Web App User] (受 Cloud IAP/IAP 保護的網路應用程式使用者) 角色。

  4. 按一下 [儲存]

從現在起,只要是 Google 可以驗證的使用者都可以存取該應用程式。如有需要,您可以透過僅新增一或多位人員或群組為主體,藉此進一步限制存取權:

  • 任何 Gmail 或 Google Workspace 電子郵件地址

  • Google 網路論壇電子郵件地址

  • Google Workspace 網域名稱

存取應用程式

  1. 透過瀏覽器前往 web-site-url

  2. 如要重新整理頁面,請按一下「重新整理」圖示

  3. 請在登入畫面中,使用您的 Google 憑證登入。

    網頁上會顯示包含您電子郵件地址的「Hello user-email-address」網頁。

    若您看到的網頁沒有變化,則可能是您啟用 IAP 後,瀏覽器尚未將新的要求完全更新。請關閉所有的瀏覽器視窗後重新開啟,然後再試一次。

驗證概念

應用程式可透過多種方式驗證使用者的身分,並限制僅已獲授權的使用者可擁有存取權。以下各節列出了常見的驗證方法,可用於降低應用程式的工作量。

選項 優勢 缺點
應用程式驗證
  • 無論是否有網際網路連線,應用程式都可在任何平台上執行
  • 使用者不需使用任何其他服務來管理驗證
  • 應用程式必須安全地管理使用者憑證,以防止憑證洩漏
  • 應用程式必須維護登入使用者的工作階段資料
  • 應用程式必須提供使用者註冊、密碼變更、密碼復原的功能
OAuth2
  • 應用程式可以在任何網際網路連線平台上執行,包括開發人員工作站
  • 應用程式不需要使用者註冊、密碼變更或密碼復原的功能
  • 使用者資訊外洩的風險可委派給其他服務管理
  • 新的登入安全措施會在應用程式外部處理
  • 使用者必須使用身分服務註冊
  • 應用程式必須維護登入使用者的工作階段資料
IAP
  • 應用程式不需要任何程式碼即可管理使用者、驗證或工作階段狀態
  • 應用程式不包含可能遭到破解的使用者憑證
  • 應用程式只能在服務支援的平台上執行具體而言,就是支援 IAP 的特定 Google Cloud 服務,例如 App Engine。

應用程式管理的驗證

透過此方法,應用程式可自行管理使用者驗證的各個環節。應用程式必須維護自己的使用者憑證資料庫並管理使用者工作階段,此外還需提供下列功能:管理使用者帳戶和密碼、檢查使用者憑證,以及對每個經過驗證的登入發出、檢查和更新使用者工作階段。下圖說明應用程式管理的驗證方法。

應用程式管理流程

如圖所示,使用者登入後,應用程式會建立並維護有關使用者工作階段的資訊。當使用者向應用程式發出要求時,該要求必須包含應用程式負責驗證的工作階段資訊。

此方法的主要優點在於其獨立性,以及受到應用程式的控管,且應用程式甚至不需在網際網路中提供。主要的缺點則是如此一來,便是由應用程式負責提供所有的帳戶管理功能,以及負責保護所有敏感的憑證資料。

使用 OAuth2 進行外部驗證

如果不想在應用程式中處理一切,可選擇的替代方案是使用 Google 或其他供應商提供的外部識別服務,來處理使用者帳戶的所有資訊和功能,並保護敏感憑證。使用者嘗試登入應用程式時,系統會將要求重新導向至身分服務,由該服務對使用者進行驗證,然後在取得必要的驗證資訊後,將要求重新導向回應用程式。詳情請參閱「使用 OAuth 2.0 處理網路伺服器應用程式」。

下圖說明使用 OAuth2 方法進行的外部驗證。

OAuth2 流程

圖表中的流程在使用者傳送存取應用程式的要求時開始。應用程式不會直接回應,而是將使用者的瀏覽器重新導向至 Google 身分平台,該平台會顯示登入 Google 的網頁。成功登入後,使用者的瀏覽器會導向回應用程式。要求中會包含所需資訊,讓應用程式可針對已完成驗證的使用者查詢相關資訊,並對該使用者發出回應。

這個方法對於應用程式有許多好處,可將所有帳戶管理功能和風險委派給外部服務,如此便可在不變更應用程式的情況下,改善登入和帳戶安全性。不過,如上圖所示,應用程式必須具備網際網路存取權才能使用這個方法。在驗證過使用者的身分後,應用程式也需負責管理工作階段。

Identity-Aware Proxy

本教學課程提供的第三種方法是使用 IAP 來處理應用程式發生變更時的所有驗證和工作階段管理。IAP 可攔截傳送至您應用程式的所有網路要求、封鎖任何尚未驗證的網路要求,並讓已加入使用者身分資料的要求通過。

要求的處理方式如下圖所示。

IAP 流程

所有使用者的要求會遭到 IAP 攔截,因而能阻止未經驗證的要求。已驗證的要求則會傳送至應用程式,前提是已驗證的使用者名列允許的使用者清單之中。透過 IAP 傳送的要求會加入標頭,以用於識別提出要求的使用者。

應用程式不再需要處理任何使用者帳戶或工作階段資訊。任何需要使用者唯一識別碼的作業都可直接從每次傳入的網路要求中取得該資訊。不過此流程僅限用於支援 IAP 的運算服務,例如 App Engine 和負載平衡器。您不能在本機開發裝置上使用 IAP。

清除所用資源

為避免因為本教學課程所用資源,導致系統向 Google Cloud 帳戶收取費用,請刪除含有相關資源的專案,或者保留專案但刪除個別資源。

  1. 前往 Google Cloud 控制台的「Manage resources」(管理資源) 頁面。

    前往「Manage resources」(管理資源)

  2. 在專案清單中選取要刪除的專案,然後點選「Delete」(刪除)
  3. 在對話方塊中輸入專案 ID,然後按一下 [Shut down] (關閉) 以刪除專案。