OAuth 2.0 和 JWT 授权在 Go 中:从理论到实践
现代 Web 应用程序和 API 很少能离开可靠的认证和授权系统。两个标准——OAuth 2.0 和 JWT(JSON Web Tokens)——已成为开发领域的事实标准。在本文中,我们将通过实际示例,讲解如何在 Go (Golang) 语言中使用 JWT 实现 OAuth 2.0 授权。
我们将创建一个简单的 REST API 服务器,该服务器在通过 OAuth 2.0 提供商(例如 Google)成功认证后颁发 JWT 令牌,并在每次请求时验证这些令牌。
1. OAuth 2.0 和 JWT 基础
在编写代码之前,我们先简要了解一下这些术语。
- OAuth 2.0 — 一种授权协议,允许用户向第三方授予对其资源的有限访问权限,而无需传递登录名和密码。主要角色包括:资源所有者(用户)、客户端(我们的应用程序)、授权服务器(颁发令牌的服务器)、资源服务器(存储受保护数据的服务器)。
- JWT — 一种紧凑的、URL 安全的令牌格式,由三部分组成:头部(header)、载荷(payload)和签名(signature)。JWT 可以包含任意数据(声明 claims),并使用密钥或 RSA 密钥对进行签名。
在我们的实现中,OAuth 2.0 将用于从提供商(Google)获取身份令牌,然后我们将生成自己的 JWT,用于后续与我们的 API 的交互。
2. 项目设置与依赖项
创建一个新的 Go 项目并安装必要的包:
mkdir go-oauth-jwtcd go-oauth-jwtgo mod init example.com/go-oauth-jwtgo get github.com/gin-gonic/gingo get github.com/golang-jwt/jwt/v5go get golang.org/x/oauth2go get golang.org/x/oauth2/google我们将使用:
- Gin — 一个快速的 Go HTTP 框架。
- golang-jwt — 用于处理 JWT 的库。
- oauth2 — Go 标准库中用于 OAuth 2.0 的库。
此外,您还需要在 Google Cloud Console 中注册应用程序,创建 OAuth 2.0 客户端 ID 和密钥,并指定重定向 URI(例如,http://localhost:8080/auth/google/callback)。
3. 通过 Google 实现 OAuth 2.0 登录
我们从创建 OAuth 2.0 配置以及用于登录和回调的端点开始。
package main
import ( "crypto/rand" "encoding/base64" "net/http" "os"
"github.com/gin-gonic/gin" "golang.org/x/oauth2" "golang.org/x/oauth2/google")
var ( googleOauthConfig *oauth2.Config oauthStateString string)
func init() { googleOauthConfig = &oauth2.Config{ RedirectURL: "http://localhost:8080/auth/google/callback", ClientID: os.Getenv("GOOGLE_CLIENT_ID"), ClientSecret: os.Getenv("GOOGLE_CLIENT_SECRET"), Scopes: []string{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"}, Endpoint: google.Endpoint, } // 生成随机 state 令牌以防止 CSRF b := make([]byte, 32) rand.Read(b) oauthStateString = base64.StdEncoding.EncodeToString(b)}
func main() { r := gin.Default()
r.GET("/auth/google/login", handleGoogleLogin) r.GET("/auth/google/callback", handleGoogleCallback)
r.Run(":8080")}
func handleGoogleLogin(c *gin.Context) { url := googleOauthConfig.AuthCodeURL(oauthStateString) c.Redirect(http.StatusTemporaryRedirect, url)}
func handleGoogleCallback(c *gin.Context) { state := c.Query("state") if state != oauthStateString { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid oauth state"}) return }
code := c.Query("code") token, err := googl