golang实现OAuth 2.0认证与JWT支持的插件库oauth2的使用
Golang实现OAuth 2.0认证与JWT支持的插件库oauth2的使用
OAuth2 for Go
oauth2包提供了OAuth 2.0规范的客户端实现。
基本使用示例
下面是一个使用Golang oauth2库实现OAuth 2.0认证的完整示例:
package main
import (
"context"
"fmt"
"log"
"net/http"
"golang.org/x/oauth2"
)
var (
// 配置OAuth2客户端
oauth2Config = oauth2.Config{
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
RedirectURL: "http://localhost:8080/callback",
Scopes: []string{"profile", "email"},
Endpoint: oauth2.Endpoint{
AuthURL: "https://provider.com/o/oauth2/auth",
TokenURL: "https://provider.com/o/oauth2/token",
},
}
// 用于保存状态,防止CSRF攻击
randomState = "random"
)
func main() {
http.HandleFunc("/", handleHome)
http.HandleFunc("/login", handleLogin)
http.HandleFunc("/callback", handleCallback)
fmt.Println("Server is running on port 8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
func handleHome(w http.ResponseWriter, r *http.Request) {
var html = `<html><body><a href="/login">Google Log In</a></body></html>`
fmt.Fprintf(w, html)
}
func handleLogin(w http.ResponseWriter, r *http.Request) {
// 重定向到OAuth提供商的授权页面
url := oauth2Config.AuthCodeURL(randomState)
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}
func handleCallback(w http.ResponseWriter, r *http.Request) {
// 验证状态
if r.FormValue("state") != randomState {
fmt.Println("state is not valid")
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
}
// 交换授权码获取令牌
token, err := oauth2Config.Exchange(context.Background(), r.FormValue("code"))
if err != nil {
fmt.Printf("could not get token: %s\n", err.Error())
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
}
// 使用令牌访问受保护的资源
client := oauth2Config.Client(context.Background(), token)
resp, err := client.Get("https://provider.com/api/userinfo")
if err != nil {
fmt.Printf("could not get user info: %s\n", err.Error())
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
}
defer resp.Body.Close()
// 处理用户信息...
fmt.Fprintf(w, "Login successful!")
}
支持JWT的OAuth2示例
下面是一个结合JWT的OAuth2认证示例:
package main
import (
"context"
"fmt"
"log"
"net/http"
"time"
"golang.org/x/oauth2"
"golang.org/x/oauth2/jwt"
)
func main() {
// JWT配置
conf := &jwt.Config{
Email: "your-service-account@your-project.iam.gserviceaccount.com",
PrivateKey: []byte("YOUR_PRIVATE_KEY"),
PrivateKeyID: "YOUR_PRIVATE_KEY_ID",
Scopes: []string{"https://www.googleapis.com/auth/cloud-platform"},
TokenURL: "https://oauth2.googleapis.com/token",
Subject: "user@example.com", // 可选:模拟用户
}
// 获取令牌
token, err := conf.TokenSource(context.Background()).Token()
if err != nil {
log.Fatalf("Could not get token: %v", err)
}
fmt.Printf("Token: %v\n", token.AccessToken)
fmt.Printf("Expires in: %v\n", token.Expiry.Sub(time.Now()))
// 使用令牌创建客户端
client := conf.Client(context.Background())
resp, err := client.Get("https://www.googleapis.com/storage/v1/b")
if err != nil {
log.Fatalf("Request failed: %v", err)
}
defer resp.Body.Close()
fmt.Println("Response status:", resp.Status)
}
Google OAuth2示例
对于Google OAuth2,可以使用专门的google包:
package main
import (
"context"
"fmt"
"log"
"net/http"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
)
var (
googleOauthConfig = &oauth2.Config{
RedirectURL: "http://localhost:8080/callback",
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
Scopes: []string{"https://www.googleapis.com/auth/userinfo.email"},
Endpoint: google.Endpoint,
}
randomState = "random"
)
func handleGoogleLogin(w http.ResponseWriter, r *http.Request) {
url := googleOauthConfig.AuthCodeURL(randomState)
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}
func handleGoogleCallback(w http.ResponseWriter, r *http.Request) {
if r.FormValue("state") != randomState {
fmt.Println("state is not valid")
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
}
token, err := googleOauthConfig.Exchange(context.Background(), r.FormValue("code"))
if err != nil {
fmt.Printf("could not get token: %s\n", err.Error())
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
}
// 使用令牌获取用户信息
resp, err := http.Get("https://www.googleapis.com/oauth2/v2/userinfo?access_token=" + token.AccessToken)
if err != nil {
fmt.Printf("could not create get request: %s\n", err.Error())
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
}
defer resp.Body.Close()
// 处理用户信息...
fmt.Fprintf(w, "Google login successful!")
}
注意事项
- 我们不再接受仅添加单个端点变量的新提供商特定包
- 如果要添加单个端点,请将其添加到
golang.org/x/oauth2/endpoints
包中 - 所有非琐碎的更改都应连接到现有问题
- API更改必须经过变更提案流程才能被接受
以上示例展示了如何使用Golang的oauth2库实现OAuth 2.0认证流程,包括基本的OAuth2流程、JWT支持以及针对Google的特殊处理。
更多关于golang实现OAuth 2.0认证与JWT支持的插件库oauth2的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang实现OAuth 2.0认证与JWT支持的插件库oauth2的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Golang实现OAuth 2.0认证与JWT支持
在Go语言中,我们可以使用golang.org/x/oauth2
标准库来实现OAuth 2.0认证流程,并结合JWT(JSON Web Token)进行身份验证。下面我将详细介绍如何使用这些技术。
OAuth2基础实现
首先,让我们看看如何使用golang.org/x/oauth2
包实现基本的OAuth2流程:
package main
import (
"context"
"fmt"
"log"
"net/http"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
)
var (
oauthConfig = &oauth2.Config{
ClientID: "your-client-id",
ClientSecret: "your-client-secret",
RedirectURL: "http://localhost:8080/callback",
Scopes: []string{"https://www.googleapis.com/auth/userinfo.email"},
Endpoint: google.Endpoint,
}
// 随机字符串,用于防止CSRF攻击
oauthStateString = "random"
)
func main() {
http.HandleFunc("/", handleMain)
http.HandleFunc("/login", handleLogin)
http.HandleFunc("/callback", handleCallback)
fmt.Println("Server started at http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
func handleMain(w http.ResponseWriter, r *http.Request) {
html := `<html><body><a href="/login">Google Log In</a></body></html>`
fmt.Fprintf(w, html)
}
func handleLogin(w http.ResponseWriter, r *http.Request) {
url := oauthConfig.AuthCodeURL(oauthStateString)
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}
func handleCallback(w http.ResponseWriter, r *http.Request) {
state := r.FormValue("state")
if state != oauthStateString {
fmt.Printf("invalid oauth state, expected '%s', got '%s'\n", oauthStateString, state)
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
}
code := r.FormValue("code")
token, err := oauthConfig.Exchange(context.Background(), code)
if err != nil {
fmt.Printf("oauthConf.Exchange() failed with '%s'\n", err)
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
}
// 使用token获取用户信息
client := oauthConfig.Client(context.Background(), token)
resp, err := client.Get("https://www.googleapis.com/oauth2/v2/userinfo")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer resp.Body.Close()
// 这里可以解析resp.Body获取用户信息
fmt.Fprintf(w, "Login successful! Token: %+v", token)
}
JWT支持
接下来,我们使用github.com/golang-jwt/jwt
库来实现JWT的生成和验证:
package main
import (
"fmt"
"net/http"
"time"
"github.com/golang-jwt/jwt/v5"
)
var jwtKey = []byte("your-secret-key")
type Claims struct {
Username string `json:"username"`
jwt.RegisteredClaims
}
func generateJWT(username string) (string, error) {
expirationTime := time.Now().Add(24 * time.Hour)
claims := &Claims{
Username: username,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expirationTime),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(jwtKey)
}
func verifyJWT(tokenString string) (*Claims, error) {
claims := &Claims{}
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
return jwtKey, nil
})
if err != nil {
return nil, err
}
if !token.Valid {
return nil, fmt.Errorf("invalid token")
}
return claims, nil
}
func protectedHandler(w http.ResponseWriter, r *http.Request) {
tokenString := r.Header.Get("Authorization")
if tokenString == "" {
w.WriteHeader(http.StatusUnauthorized)
return
}
claims, err := verifyJWT(tokenString)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
fmt.Fprintf(w, "Welcome %s!", claims.Username)
}
func loginHandler(w http.ResponseWriter, r *http.Request) {
// 假设用户已通过OAuth2认证
username := "example_user"
token, err := generateJWT(username)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Write([]byte(token))
}
结合OAuth2和JWT
现在我们将两者结合起来,在OAuth2认证成功后生成JWT:
func handleCallback(w http.ResponseWriter, r *http.Request) {
// ...之前的OAuth2验证代码...
// 假设我们从OAuth2响应中获取了用户名
username := "user_from_oauth2"
// 生成JWT
tokenString, err := generateJWT(username)
if err != nil {
http.Error(w, "Failed to generate token", http.StatusInternalServerError)
return
}
// 返回JWT给客户端
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{"token": "%s"}`, tokenString)
}
使用中间件验证JWT
创建一个中间件来验证JWT:
func authMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
tokenString := r.Header.Get("Authorization")
if tokenString == "" {
w.WriteHeader(http.StatusUnauthorized)
return
}
_, err := verifyJWT(tokenString)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
next(w, r)
}
}
// 使用中间件
http.HandleFunc("/protected", authMiddleware(protectedHandler))
完整示例
以下是一个结合了OAuth2和JWT的完整示例:
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"time"
"github.com/golang-jwt/jwt/v5"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
)
var (
oauthConfig = &oauth2.Config{
ClientID: "your-client-id",
ClientSecret: "your-client-secret",
RedirectURL: "http://localhost:8080/callback",
Scopes: []string{"https://www.googleapis.com/auth/userinfo.email"},
Endpoint: google.Endpoint,
}
oauthStateString = "random"
jwtKey = []byte("your-secret-key")
)
type Claims struct {
Email string `json:"email"`
jwt.RegisteredClaims
}
func main() {
http.HandleFunc("/", handleMain)
http.HandleFunc("/login", handleLogin)
http.HandleFunc("/callback", handleCallback)
http.HandleFunc("/protected", authMiddleware(protectedHandler))
fmt.Println("Server started at http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
func handleMain(w http.ResponseWriter, r *http.Request) {
html := `<html><body><a href="/login">Google Log In</a></body></html>`
fmt.Fprintf(w, html)
}
func handleLogin(w http.ResponseWriter, r *http.Request) {
url := oauthConfig.AuthCodeURL(oauthStateString)
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}
func handleCallback(w http.ResponseWriter, r *http.Request) {
state := r.FormValue("state")
if state != oauthStateString {
http.Error(w, "Invalid state", http.StatusBadRequest)
return
}
code := r.FormValue("code")
token, err := oauthConfig.Exchange(context.Background(), code)
if err != nil {
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
return
}
// 获取用户信息
client := oauthConfig.Client(context.Background(), token)
resp, err := client.Get("https://www.googleapis.com/oauth2/v2/userinfo")
if err != nil {
http.Error(w, "Failed to get user info", http.StatusInternalServerError)
return
}
defer resp.Body.Close()
var userInfo struct {
Email string `json:"email"`
}
if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil {
http.Error(w, "Failed to decode user info", http.StatusInternalServerError)
return
}
// 生成JWT
jwtToken, err := generateJWT(userInfo.Email)
if err != nil {
http.Error(w, "Failed to generate JWT", http.StatusInternalServerError)
return
}
// 返回JWT
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"token": jwtToken,
})
}
func generateJWT(email string) (string, error) {
expirationTime := time.Now().Add(24 * time.Hour)
claims := &Claims{
Email: email,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expirationTime),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(jwtKey)
}
func verifyJWT(tokenString string) (*Claims, error) {
claims := &Claims{}
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
return jwtKey, nil
})
if err != nil {
return nil, err
}
if !token.Valid {
return nil, fmt.Errorf("invalid token")
}
return claims, nil
}
func authMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
tokenString := r.Header.Get("Authorization")
if tokenString == "" {
w.WriteHeader(http.StatusUnauthorized)
return
}
claims, err := verifyJWT(tokenString)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
// 可以将claims信息添加到请求上下文中
ctx := context.WithValue(r.Context(), "claims", claims)
next(w, r.WithContext(ctx))
}
}
func protectedHandler(w http.ResponseWriter, r *http.Request) {
claims, ok := r.Context().Value("claims").(*Claims)
if !ok {
w.WriteHeader(http.StatusUnauthorized)
return
}
fmt.Fprintf(w, "Hello, %s! This is a protected route.", claims.Email)
}
总结
- 使用
golang.org/x/oauth2
实现OAuth2认证流程 - 使用
github.com/golang-jwt/jwt
生成和验证JWT - 在OAuth2认证成功后生成JWT返回给客户端
- 使用中间件保护需要认证的路由
- 客户端需要在请求头中添加
Authorization: Bearer <token>
来访问受保护的路由
这种组合方式既利用了OAuth2的强大认证能力,又通过JWT实现了无状态的认证机制,非常适合现代Web应用和API服务。