如何从 Google Identity Aware Proxy 背后的网络应用程序访问已通过身份验证的用户?

问题描述

我有一个网络应用程序,它位于 Google 的 Identity Aware Proxy (IAP) 后面。 IAP 在转发到我的 Web 应用程序之前对用户进行身份验证。如何从我的网络应用程序访问已通过身份验证的用户

Getting the user's identity 中,它声明有 X-Goog-Authenticated-User-EmailX-Goog-Authenticated-User-Id 标头。但是,我在响应标头中没有看到这些:

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

我确实看到了一些 cookie:

GCP_IAAP_AUTH_TOKEN_xxx
GCP_IAP_UID
GCP_IAP_XSRF_NONCE_xxx

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

解决方法

在@JohnHanley 提到只有在 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)
    }
}

这是一条临时路线。确认标题后,我将其删除。

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

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

对于我的用例,我创建了一个 /user 路由来返回用户信息的子集,即姓名、电子邮件、照片网址。

结构:

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,"")
    }

    // 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 People 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
}

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...