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协议的大部分复杂性。

回到顶部