标题:Go 语言使用 Google OAuth2 获取用户信息的完整教程

本文详细讲解如何在 go 中正确使用 `golang.org/x/oauth2`(含 `google` 子包)完成 google oauth2 授权流程,并成功获取用户基本信息(如邮箱、头像、姓名等),解决常见响应体为空、未解析 json、scope 不足等问题。

在 Go 中集成 Google OAuth2 登录时,许多开发者能顺利获取授权码(code)和访问令牌(token),却卡在最后一步——调用 /userinfo/v2/me 接口获取用户资料。问题往往并非代码逻辑错误,而是HTTP 响应未被正确读取与解析,或权限范围(Scopes)配置不全导致返回数据受限。

✅ 正确做法:完整可运行示例

以下是一个结构清晰、生产可用的 Google OAuth2 用户信息获取流程(基于 golang.org/x/oauth2/google):

package main

import (
    "fmt"
    "io"
    "io/ioutil"
    "log"
    "net/http"
    "net/url"

    "golang.org/x/oauth2"
    "golang.org/x/oauth2/google"
)

var googleconf = &oauth2.Config{
    ClientID:     "YOUR_CLIENT_ID",
    ClientSecret: "YOUR_CLIENT_SECRET",
    RedirectURL:  "http://localhost:3000/googlelogin",
    Scopes: []string{
        "https://www.googleapis.com/auth/userinfo.profile",
        "https://www.googleapis.com/auth/userinfo.email", // ⚠️ 必须显式添加此 Scope 才能获取 email
    },
    Endpoint: google.Endpoint,
}

func main() {
    http.HandleFunc("/googleloginrequest", func(w http.ResponseWriter, r *http.Request) {
        url := googleconf.AuthCodeURL("state", oauth2.AccessTypeOffline)
        http.Redirect(w, r, url, http.StatusFound)
    })

    http.HandleFunc("/googlelogin", func(w http.ResponseWriter, r *http.Request) {
        code := r.FormValue("code")
        if code == "" {
            http.Error(w, "missing code", http.StatusBadRequest)
            return
        }

        tok, err := googleconf.Exchange(r.Context(), code) // ✅ 推荐使用 r.Context() 替代 oauth2.NoContext(已弃用)
        if err != nil {
            log.Printf("OAuth2 exchange error: %v", err)
            http.Error(w, "failed to exchange code for token", http.StatusInternalServerError)
            return
        }

        // ✅ 方式一:使用 conf.Client() 构建带 Token 的 HTTP Client(推荐)
        client := googleconf.Client(r.Context(), tok)
        resp, err := client.Get("https://www.googleapis.com/oauth2/v2/userinfo")
        if err != nil {
            log.Printf("API request error: %v", err)
            http.Error(w, "failed to fetch user info", http.StatusInternalServerError)
            return
        }
        defer resp.Body.Close()

        body, err := io.ReadAll(resp.Body)
        if err != nil {
            log.Printf("Read body error: %v", err)
            http.Error(w, "failed to read response", http.StatusInternalServerError)
            return
        }

        // ✅ 输出结构化 JSON(实际项目中建议定义 struct 解析)
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(http.StatusOK)
        w.Write(body) // 如 {"id":"123","email":"user@gmail.com","verified_email":true,"name":"John Doe",...}
    })

    log.Println("Server starting on :3000")
    log.Fatal(http.ListenAndServe(":3000", nil))
}

? 关键注意事项

  • Scopes 必须完整:仅 userinfo.profile 只返回 id, name, picture, locale;要获取 email、verified_email,必须额外声明 userinfo.email Scope
  • 避免直接拼接 access_token:虽然 https://www.googleapis.com/oauth2/v2/userinfo?access_token=xxx 在调试时可行,但存在安全风险(如日志泄露、URL 长度限制)。应始终使用 conf.Client(ctx, token) 创建自动携带 Authorization: Bearer ... 头的客户端。
  • oauth2.NoContext 已弃用:Go 1.7+ 应使用 r.Context()(如 googleconf.Exchange(r.Context(), code)),确保上下文取消传播与超时控制。
  • 务必读取并关闭 resp.Body:否则连接会泄漏,且你看到的“大响应体”只是 *http.Response 结构体打印(含 Header、Request 等元信息),不是真正的用户数据。真实数据在 resp.Body 流中,需 io.ReadAll() 或 json.NewDecoder().Decode() 解析。
  • API Endpoint 推荐 oauth2/v2/userinfo:相比 userinfo/v2/me,该路径更稳定,且明确支持 email 字段(Google 官方文档推荐)。

? 补充:结构化解析用户信息(最佳实践)

为提升类型安全与可维护性,建议定义结构体并解码:

type GoogleUser struct {
    ID            string `json:"id"`
    Email         string `json:"email"`
    VerifiedEmail bool   `json:"verified_email"`
    Name          string `json:"name"`
    Picture       string `json:"picture"`
    GivenName     string `json:"given_name"`
    FamilyName    string `json:"family_name"`
    Locale        string `json:"locale"`
}

// 替换上面的 io.ReadAll(...) 部分:
var user GoogleUser
if err := json.Unmarshal(body, &user); err != nil {
    log.Printf("JSON decode error: %v", err)
    http.Error(w, "invalid user data", http.StatusInternalServerError)
    return
}
log.Printf("Logged in as: %s (%s)", user.Name, user.Email)

通过以上配置与实践,即可稳定、安全、可维护地完成 Google OAuth2 用户信息获取。无需引入第三方库——官方 golang.org/x/oauth2 经过充分测试,完全满足生产需求。