golang实现OpenID Connect认证的客户端与服务端插件库oidc的使用

Golang实现OpenID Connect认证的客户端与服务端插件库oidc的使用

什么是oidc库

这是一个为Go语言编写的易于使用的OIDC(OpenID Connect)标准的客户端(RP)和服务端(OP)实现。该库已通过OpenID Connect基础配置文件的认证。

基本概述

库中最重要的包:

/pkg
    /client            使用OP获取、交换和验证令牌的客户端
        /rp            OIDC依赖方(客户端)的定义和实现
        /rs            OAuth资源服务器(API)的定义和实现
    /op                OIDC OpenID提供者(服务端)的定义和实现
    /oidc              客户端和服务端共享的定义

/example
    /client/api        使用令牌自省的API/资源服务器实现示例
    /client/app        使用各种认证方法(代码、PKCE、JWT profile)演示授权码流程的Web应用/RP
    /client/github     扩展OAuth2库的示例,提供带有重用令牌源的HTTP客户端
    /client/service    JWT Profile授权授予的演示
    /server            包含一些基本登录UI的OpenID提供者实现示例(包括动态)

如何使用

快速开始示例

// 启动OIDC OP服务器
// OIDC发现地址: http://localhost:9998/.well-known/openid-configuration
go run github.com/zitadel/oidc/v3/example/server

// 启动OIDC Web客户端(在新终端中)
CLIENT_ID=web CLIENT_SECRET=secret ISSUER=http://localhost:9998/ SCOPES="openid profile" PORT=9999 go run github.com/zitadel/oidc/v3/example/client/app

操作步骤:

  1. 在浏览器中打开 http://localhost:9999/login
  2. 你将被重定向到OP服务器和登录UI
  3. 使用用户 test-user@localhost 和密码 verysecure 登录
  4. OP会将你重定向回客户端应用,显示用户信息

客户端代码示例

package main

import (
	"context"
	"log"
	"net/http"
	
	"github.com/zitadel/oidc/v3/pkg/client/rp"
	"github.com/zitadel/oidc/v3/pkg/oidc"
)

func main() {
	// 配置依赖方(RP)
	issuer := "http://localhost:9998/"
	clientID := "web"
	clientSecret := "secret"
	redirectURI := "http://localhost:9999/callback"
	scopes := []string{"openid", "profile"}

	// 创建依赖方
	provider, err := rp.NewRelyingPartyOIDC(
		context.Background(),
		issuer,
		clientID,
		clientSecret,
		redirectURI,
		scopes,
	)
	if err != nil {
		log.Fatal(err)
	}

	// 创建授权码流程处理器
	http.Handle("/login", rp.AuthURLHandler(
		rp.GeneratePKCEVerifier(), // 使用PKCE
		provider,
		[]oidc.AddAuthURLOpt{},
	))

	// 创建回调处理器
	http.Handle("/callback", rp.CodeExchangeHandler(
		func(w http.ResponseWriter, r *http.Request, tokens *oidc.Tokens[*oidc.IDTokenClaims], state string, rp rp.RelyingParty) {
			// 在这里处理令牌和用户信息
			log.Printf("ID Token: %v", tokens.IDToken)
			log.Printf("Access Token: %v", tokens.AccessToken)
			log.Printf("Refresh Token: %v", tokens.RefreshToken)
		},
		provider,
	))

	// 启动服务器
	log.Println("Server listening on http://localhost:9999")
	log.Fatal(http.ListenAndServe(":9999", nil))
}

服务端配置

示例服务器允许使用环境变量进行额外配置,可用于服务的端到端测试。

名称 格式 描述
PORT 1-65535之间的数字 OIDC监听端口
REDIRECT_URI 逗号分隔的URI 允许的重定向URI列表
USERS_FILE 本地文件系统中的json路径 用户及其数据和凭据

用户JSON示例:

{
  "id2": {
    "ID": "id2",
    "Username": "test-user2",
    "Password": "verysecure",
    "FirstName": "Test",
    "LastName": "User2",
    "Email": "test-user2@zitadel.ch",
    "EmailVerified": true,
    "Phone": "",
    "PhoneVerified": false,
    "PreferredLanguage": "DE",
    "IsAdmin": false
  }
}

特性支持

特性 依赖方 OpenID提供者 规范
代码流 OpenID Connect Core 1.0, 3.1节
隐式流 OpenID Connect Core 1.0, 3.2节
混合流 尚未 OpenID Connect Core 1.0, 3.3节
客户端凭据 OpenID Connect Core 1.0, 9节
刷新令牌 OpenID Connect Core 1.0, 12节
发现 OpenID Connect Discovery 1.0
JWT Profile RFC 7523
PKCE RFC 7636
令牌交换 RFC 8693
设备授权 RFC 8628
mTLS 尚未 尚未 RFC 8705
后端通道注销 尚未 OpenID Connect Back-Channel Logout 1.0

支持的Go版本

出于安全原因,我们仅支持和推荐使用最新的两个Go版本(✅)。 也能构建的版本标记为⚠️。

版本 支持
<1.23
1.23
1.24

为什么选择这个库

截至2020年,Go中没有很多可以处理服务端和客户端实现的OIDC库。ZITADEL致力于IAM(身份和访问管理)领域,因此我们需要可靠的框架来实现服务。

目标

  • 将此库认证为OP

其他Go OpenID Connect库

许可证

该库的全部功能现在是并将保持开源,任何人都可以免费使用。访问我们的网站并取得联系。

除非适用法律要求或书面同意,否则根据许可证分发的软件按"原样"分发,不附带任何明示或暗示的担保或条件。有关权限和限制的具体语言,请参阅许可证。


更多关于golang实现OpenID Connect认证的客户端与服务端插件库oidc的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang实现OpenID Connect认证的客户端与服务端插件库oidc的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


使用Golang实现OpenID Connect认证:oidc库指南

OpenID Connect (OIDC) 是构建在OAuth 2.0之上的身份认证层,允许客户端验证用户的身份并获取基本用户信息。在Go语言中,coreos/go-oidc库提供了实现OIDC客户端和服务端的便捷方式。

安装oidc库

go get github.com/coreos/go-oidc/v3/oidc

客户端实现

1. 基本配置

package main

import (
	"context"
	"log"
	"net/http"

	"github.com/coreos/go-oidc/v3/oidc"
	"golang.org/x/oauth2"
)

var (
	clientID     = "your-client-id"
	clientSecret = "your-client-secret"
	redirectURL  = "http://localhost:8080/auth/callback"
	
	// 例如: https://accounts.google.com
	issuerURL    = "your-oidc-provider-url"
)

func main() {
	ctx := context.Background()
	
	// 初始化provider
	provider, err := oidc.NewProvider(ctx, issuerURL)
	if err != nil {
		log.Fatal(err)
	}

	// 配置OAuth2
	oauth2Config := oauth2.Config{
		ClientID:     clientID,
		ClientSecret: clientSecret,
		RedirectURL:  redirectURL,
		Endpoint:     provider.Endpoint(),
		Scopes:       []string{oidc.ScopeOpenID, "profile", "email"},
	}

	// 初始化oidc配置
	oidcConfig := &oidc.Config{
		ClientID: clientID,
	}

	verifier := provider.Verifier(oidcConfig)

	// 设置HTTP路由
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		// 生成随机state防止CSRF攻击
		state := "random-state"
		http.Redirect(w, r, oauth2Config.AuthCodeURL(state), http.StatusFound)
	})

	http.HandleFunc("/auth/callback", func(w http.ResponseWriter, r *http.Request) {
		// 验证state
		if r.URL.Query().Get("state") != "random-state" {
			http.Error(w, "Invalid state", http.StatusBadRequest)
			return
		}

		// 交换授权码获取token
		oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
		if err != nil {
			http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
			return
		}

		// 提取ID Token
		rawIDToken, ok := oauth2Token.Extra("id_token").(string)
		if !ok {
			http.Error(w, "No id_token field in oauth2 token.", http.StatusInternalServerError)
			return
		}

		// 验证ID Token
		idToken, err := verifier.Verify(ctx, rawIDToken)
		if err != nil {
			http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
			return
		}

		// 提取用户信息
		var claims struct {
			Email    string `json:"email"`
			Name     string `json:"name"`
			Picture  string `json:"picture"`
		}
		if err := idToken.Claims(&claims); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		// 在这里处理用户认证逻辑...
		w.Write([]byte("Hello, " + claims.Name))
	})

	log.Fatal(http.ListenAndServe(":8080", nil))
}

服务端实现

虽然OIDC服务端通常由专门的身份提供商(如Keycloak, Auth0等)实现,但我们可以使用oidc库来验证令牌:

package main

import (
	"context"
	"log"
	"net/http"

	"github.com/coreos/go-oidc/v3/oidc"
)

var (
	issuerURL = "your-oidc-provider-url"
	clientID  = "your-client-id"
)

func main() {
	ctx := context.Background()
	
	provider, err := oidc.NewProvider(ctx, issuerURL)
	if err != nil {
		log.Fatal(err)
	}

	verifier := provider.Verifier(&oidc.Config{ClientID: clientID})

	http.HandleFunc("/protected", func(w http.ResponseWriter, r *http.Request) {
		// 从Authorization头获取token
		rawToken := r.Header.Get("Authorization")
		if rawToken == "" {
			http.Error(w, "Authorization header required", http.StatusUnauthorized)
			return
		}

		// 验证token
		token, err := verifier.Verify(ctx, rawToken)
		if err != nil {
			http.Error(w, "Invalid token: "+err.Error(), http.StatusUnauthorized)
			return
		}

		// 提取claims
		var claims struct {
			Email    string `json:"email"`
			Name     string `json:"name"`
		}
		if err := token.Claims(&claims); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		// 在这里处理授权逻辑...
		w.Write([]byte("Protected resource accessed by: " + claims.Name))
	})

	log.Fatal(http.ListenAndServe(":8081", nil))
}

高级用法

1. 自定义claims验证

type customClaims struct {
	Email         string `json:"email"`
	EmailVerified bool   `json:"email_verified"`
}

func verifyToken(ctx context.Context, verifier *oidc.IDTokenVerifier, rawToken string) (*customClaims, error) {
	token, err := verifier.Verify(ctx, rawToken)
	if err != nil {
		return nil, err
	}

	var claims customClaims
	if err := token.Claims(&claims); err != nil {
		return nil, err
	}

	if !claims.EmailVerified {
		return nil, fmt.Errorf("email not verified")
	}

	return &claims, nil
}

2. 刷新令牌

func refreshToken(ctx context.Context, oauth2Config oauth2.Config, refreshToken string) (*oauth2.Token, error) {
	token := &oauth2.Token{
		RefreshToken: refreshToken,
		Expiry:       time.Now().Add(-time.Hour), // 强制过期
	}
	
	return oauth2Config.TokenSource(ctx, token).Token()
}

安全注意事项

  1. 始终验证state参数以防止CSRF攻击
  2. 使用HTTPS保护所有通信
  3. 安全存储客户端密钥和令牌
  4. 设置适当的令牌有效期
  5. 实现令牌撤销机制

通过go-oidc库,我们可以相对容易地在Go应用中实现OpenID Connect认证流程。无论是构建客户端还是验证令牌的服务端组件,该库都提供了简洁的API来处理OIDC协议的大部分复杂性。

回到顶部