使用 Go 驗證使用者

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

目標

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

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

費用

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

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

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

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

事前準備

  1. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  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 (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  3. Install the 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 (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  7. Install the Google Cloud CLI.

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

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

    gcloud init
  10. 準備開發環境
  11. 設定專案

    1. 在終端機視窗中,將範例應用程式存放區複製到本機電腦:

      git clone https://github.com/GoogleCloudPlatform/golang-samples.git
    2. 變更為包含範例程式碼的目錄:

      cd golang-samples/getting-started/authenticating-users

    背景

    此教學課程使用 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 僅需一行程式碼:

    userID := r.Header.Get("X-Goog-Authenticated-User-ID")

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

    瞭解程式碼

    本節將說明程式碼的運作方式。如要執行應用程式,請直接跳至「部署應用程式」一節。

    • go.mod 檔案會定義 Go 模組,以及該模組依附的模組。

      module github.com/GoogleCloudPlatform/golang-samples/getting-started/authenticating-users
      
      go 1.24.0
      
      require (
      	cloud.google.com/go/compute/metadata v0.6.0
      	github.com/golang-jwt/jwt v3.2.2+incompatible
      )
      
      require golang.org/x/sys v0.29.0 // indirect
      
    • app.yaml 檔案會告知 App Engine 程式碼所需的語言環境。

      runtime: go112
    • 應用程式會先匯入套件,然後定義 main 函式。main 函式會註冊索引處理常式,並啟動 HTTP 伺服器。

      
      // The authenticating-users program is a sample web server application that
      // extracts and verifies user identity data passed to it via Identity-Aware
      // Proxy.
      package main
      
      import (
      	"encoding/json"
      	"fmt"
      	"log"
      	"net/http"
      	"os"
      	"time"
      
      	"cloud.google.com/go/compute/metadata"
      	"github.com/golang-jwt/jwt"
      )
      
      // app holds the Cloud IAP certificates and audience field for this app, which
      // are needed to verify authentication headers set by Cloud IAP.
      type app struct {
      	certs map[string]string
      	aud   string
      }
      
      func main() {
      	a, err := newApp()
      	if err != nil {
      		log.Fatal(err)
      	}
      
      	http.HandleFunc("/", a.index)
      
      	port := os.Getenv("PORT")
      	if port == "" {
      		port = "8080"
      		log.Printf("Defaulting to port %s", port)
      	}
      
      	log.Printf("Listening on port %s", port)
      	if err := http.ListenAndServe(":"+port, nil); err != nil {
      		log.Fatal(err)
      	}
      }
      
      // newApp creates a new app, returning an error if either the Cloud IAP
      // certificates or the app's audience field cannot be obtained.
      func newApp() (*app, error) {
      	certs, err := certificates()
      	if err != nil {
      		return nil, err
      	}
      
      	aud, err := audience()
      	if err != nil {
      		return nil, err
      	}
      
      	a := &app{
      		certs: certs,
      		aud:   aud,
      	}
      	return a, nil
      }
      
    • index 函式會取得 IAP 從傳入要求中加入的 JWT 斷言標頭值,並呼叫 validateAssertion 函式來驗證經加密簽署的值。接著,系統會在最小網頁回應中使用該電子郵件地址。

      
      // index responds to requests with our greeting.
      func (a *app) index(w http.ResponseWriter, r *http.Request) {
      	if r.URL.Path != "/" {
      		http.NotFound(w, r)
      		return
      	}
      
      	assertion := r.Header.Get("X-Goog-IAP-JWT-Assertion")
      	if assertion == "" {
      		fmt.Fprintln(w, "No Cloud IAP header found.")
      		return
      	}
      	email, _, err := validateAssertion(assertion, a.certs, a.aud)
      	if err != nil {
      		log.Println(err)
      		fmt.Fprintln(w, "Could not validate assertion. Check app logs.")
      		return
      	}
      
      	fmt.Fprintf(w, "Hello %s\n", email)
      }
      
    • validateAssertion 函式會驗證聲明是否已正確簽署,並傳回相關聯的電子郵件地址和使用者 ID。

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

      
      // validateAssertion validates assertion was signed by Google and returns the
      // associated email and userID.
      func validateAssertion(assertion string, certs map[string]string, aud string) (email string, userID string, err error) {
      	token, err := jwt.Parse(assertion, func(token *jwt.Token) (interface{}, error) {
      		keyID := token.Header["kid"].(string)
      
      		_, ok := token.Method.(*jwt.SigningMethodECDSA)
      		if !ok {
      			return nil, fmt.Errorf("unexpected signing method: %q", token.Header["alg"])
      		}
      
      		cert := certs[keyID]
      		return jwt.ParseECPublicKeyFromPEM([]byte(cert))
      	})
      
      	if err != nil {
      		return "", "", err
      	}
      
      	claims, ok := token.Claims.(jwt.MapClaims)
      	if !ok {
      		return "", "", fmt.Errorf("could not extract claims (%T): %+v", token.Claims, token.Claims)
      	}
      
      	if claims["aud"].(string) != aud {
      		return "", "", fmt.Errorf("mismatched audience. aud field %q does not match %q", claims["aud"], aud)
      	}
      	return claims["email"].(string), claims["sub"].(string), nil
      }
      
    • 您可以自行查詢專案的數字 ID 和名稱,並在原始碼中加入這些資訊,但 audience 函式也能為您代勞,方法是查詢每個 App Engine 應用程式可用的標準中繼資料服務。由於中繼資料服務位於應用程式程式碼外部,因此結果會儲存在全域變數中,並在後續呼叫時傳回,不必再查詢中繼資料。 Google Cloud

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

      
      // audience returns the expected audience value for this service.
      func audience() (string, error) {
      	projectNumber, err := metadata.NumericProjectID()
      	if err != nil {
      		return "", fmt.Errorf("metadata.NumericProjectID: %w", err)
      	}
      
      	projectID, err := metadata.ProjectID()
      	if err != nil {
      		return "", fmt.Errorf("metadata.ProjectID: %w", err)
      	}
      
      	return "/projects/" + projectNumber + "/apps/" + projectID, nil
      }
      
    • 簽署者的公用金鑰憑證是驗證數位簽名時的必要資訊。Google 提供了一個網站,可傳回目前使用中的所有公用金鑰憑證。傳回的結果會存放在快取中,以利同一個應用程式執行個體在需要時重複使用。

      
      // certificates returns Cloud IAP's cryptographic public keys.
      func certificates() (map[string]string, error) {
      	const url = "https://www.gstatic.com/iap/verify/public_key"
      	client := http.Client{
      		Timeout: 5 * time.Second,
      	}
      	resp, err := client.Get(url)
      	if err != nil {
      		return nil, fmt.Errorf("Get: %w", err)
      	}
      
      	var certs map[string]string
      	dec := json.NewDecoder(resp.Body)
      	if err := dec.Decode(&certs); err != nil {
      		return nil, fmt.Errorf("Decode: %w", err)
      	}
      
      	return certs, nil
      }
      

    部署應用程式

    現在您可以部署應用程式,然後啟用 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. In the Google Cloud console, go to the Manage resources page.

      Go to Manage resources

    2. In the project list, select the project that you want to delete, and then click Delete.
    3. In the dialog, type the project ID, and then click Shut down to delete the project.