如何从 Google Identity Aware Proxy 后面的 Web 应用程序访问已经过身份验证的用户?

How to access already authenticated user from web application behind Google Identity Aware Proxy?

我有一个 Web 应用程序位于 Google 的身份识别代理 (IAP) 后面。 IAP 在转发到我的 Web 应用程序之前对用户进行身份验证。如何从我的 Web 应用程序访问已通过身份验证的用户?

Getting the user's identity 中指出有 X-Goog-Authenticated-User-EmailX-Goog-Authenticated-User-Id headers。但是,我没有在回复中看到那些 headers:

accept-ranges: bytes
alt-svc: clear
content-length: 14961
content-type: text/html; charset=utf-8
date: Thu, 01 Apr 2021 15:21:01 GMT
last-modified: Wed, 31 Mar 2021 19:34:58 GMT
via: 1.1 google

我确实看到了一些饼干:

GCP_IAAP_AUTH_TOKEN_xxx
GCP_IAP_UID
GCP_IAP_XSRF_NONCE_xxx

例如,我希望能够在我的网络应用程序中显示他们的姓名和头像照片,以表明他们已通过身份验证并已登录。我知道可以通过 Google 的 OAuth2 结构获得该信息,但我怎样才能从 IAP 获得它?

在@JohnHanley 提到 headers 仅在 IAP 后面的 运行 时才出现后,我才能够使它正常工作。本地开发时看不到。

我可以在部署一个简单的临时 /headers 路由后看到它们,该路由循环遍历它们并写入 ResponseWriter。 X-Goog-Authenticated-User-IdX-Goog-Authenticated-User-EmailX-Goog-Iap-Jwt-Assertion

import (
    "fmt"
    "net/http"

    "github.com/rs/zerolog/log"
)

func headersHandler(w http.ResponseWriter, r *http.Request) {
    log.Info().Msg("Entering headersHandler")

    fmt.Fprintf(w, "Request Headers\n\n")
    log.Debug().Msg("Request Headers:")
    for name, values := range r.Header {
        log.Debug().Interface(name, values).Send()
        fmt.Fprintf(w, "%s = %s\n", name, values)
    }
}

这是一条临时路线。一旦我确认 headers,我就删除了它。

此外,我必须为托管我的 Web 应用程序的 ProjectId 启用 Google 的 People API

之后,我使用 google.golang.org/api/people/v1 的 Go 包进行了测试,发现通过 people/me 使用当前经过身份验证的用户的约定在我的情况下不起作用,因为它 returns 正在使用的服务帐户。相反,我必须以编程方式填写用户 ID people/userid。然后就成功了。

对于我的 use-case,我创建了一个 /user 路由到 return 用户信息的一个子集,即姓名、电子邮件、照片 url。

结构:

type GoogleUser struct {
    Name     string `json:"name"`
    Email    string `json:"email"`
    PhotoUrl string `json:"photo_url"`
}

处理程序:

func userHandler(w http.ResponseWriter, r *http.Request) {
    log.Info().Msg("Entering userHandler")

    var err error

    // Make sure this is a valid API request
    // Request header Content-Type: application/json must be present
    if !ValidAPIRequest(r) {
        err = writeJSONError(w, ResponseStatusNotFound("Not found"))
        if err != nil {
            log.Error().Msg(err.Error())
        }
        return
    }

    // Extract user id from header
    var userId string = r.Header.Get("X-Goog-Authenticated-User-Id")
    if userId != "" {
        userId = strings.ReplaceAll(userId, "accounts.google.com:", "")
    }

    // Extract user email from header
    var userEmail string = r.Header.Get("X-Goog-Authenticated-User-Email")
    if userEmail != "" {
        userEmail = strings.ReplaceAll(userEmail, "accounts.google.com:", "")
    }

    // Get the currently authenticated Google user
    googleUser, err := GetCurrentGoogleUser(userId, userEmail)
    if err != nil {
        log.Error().Msg(err.Error())
        err = writeJSONError(w, ResponseStatusInternalError(err.Error()))
        if err != nil {
            log.Error().Msg(err.Error())
        }
        return
    }

    // Write the JSON response
    err = writeJSONGoogleUser(w, http.StatusOK, &googleUser)
    if err != nil {
        log.Error().Msg(err.Error())
    }
}

Google 人 API:

func GetCurrentGoogleUser(userId string, userEmail string) (GoogleUser, error) {
    // Pre-conditions
    if userId == "" {
        return GoogleUser{}, errors.New("userId is blank")
    }
    if userEmail == "" {
        return GoogleUser{}, errors.New("userEmail is blank")
    }

    log.Debug().
        Str("userId", userId).
        Str("userEmail", userEmail).
        Send()

    ctx := context.Background()

    // Instantiate a new People service 
    peopleService, err := people.NewService(ctx, option.WithAPIKey(GoogleAPIKey))
    if err != nil {
        return GoogleUser{}, err
    }

    // Define the resource name using the user id
    var resourceName string = fmt.Sprintf("people/%s", userId)
    
    // Get the user profile 
profile, err := peopleService.People.Get(resourceName).PersonFields("names,photos").Do()
    if err != nil {
        return GoogleUser{}, err
    }

    log.Debug().
        Interface("profile", profile).
        Send()

    return GoogleUser{Name: profile.Names[0].DisplayName, Email: userEmail, PhotoUrl: profile.Photos[0].Url}, nil
}