SkillAgentSearch skills...

Oauth2

A lightweight and extensible SAAS OAuth 2.0 server and client written in Go. Supports authorization code, client credentials, and password grants. Ideal for modern auth systems and microservices.

Install / Use

/learn @nilorg/Oauth2

README

oauth2

OAuth 2.0 授权框架的 Go 语言实现,支持 SaaS 多租户场景。

Go implementation of OAuth 2.0 Authorization Framework with SaaS multi-tenant support.

Features / 特性

  • ✅ 授权码模式 (Authorization Code)
  • ✅ 简化模式 (Implicit)
  • ✅ 密码模式 (Resource Owner Password Credentials)
  • ✅ 客户端凭证模式 (Client Credentials)
  • ✅ 设备授权模式 (Device Code) - RFC 8628
  • ✅ 令牌内省 (Token Introspection) - RFC 7662
  • ✅ 令牌撤销 (Token Revocation) - RFC 7009
  • PKCE 支持 - RFC 7636 增强公开客户端安全性
  • SaaS 多租户动态 Issuer - 根据请求域名动态生成 JWT issuer
  • SaaS 多租户动态 JWT 密钥 - 每个租户使用独立的签名密钥
  • 反向代理支持 - 支持 X-Forwarded-* 头部

Installation / 安装

go get -u github.com/nilorg/oauth2

Import / 导入

import "github.com/nilorg/oauth2"

Quick Start / 快速开始

基础用法 / Basic Usage

srv := oauth2.NewServer()
// 配置回调函数...
srv.InitWithError()

SaaS 多租户场景 / SaaS Multi-tenant Scenario

srv := oauth2.NewServer(
    // 动态 Issuer:根据请求 Host 自动生成 JWT 的 iss 字段
    oauth2.ServerIssuerFunc(func(ctx context.Context, req oauth2.IssuerRequest) string {
        // req.Host   = "tenant1.example.com"
        // req.Scheme = "https"
        return fmt.Sprintf("%s://%s", req.Scheme, req.Host)
    }),
)

反向代理场景 / Reverse Proxy Scenario

srv := oauth2.NewServer(
    // 使用内置的反向代理支持,从 X-Forwarded-* 头部获取信息
    oauth2.ServerIssuerRequestFunc(oauth2.ProxyIssuerRequestFunc),
    oauth2.ServerIssuerFunc(func(ctx context.Context, req oauth2.IssuerRequest) string {
        return fmt.Sprintf("%s://%s", req.Scheme, req.Host)
    }),
)

自定义 IssuerRequest 提取 / Custom IssuerRequest Extraction

srv := oauth2.NewServer(
    oauth2.ServerIssuerRequestFunc(func(r *http.Request) oauth2.IssuerRequest {
        return oauth2.IssuerRequest{
            Host:   r.Header.Get("X-Tenant-Domain"),
            Scheme: r.Header.Get("X-Forwarded-Proto"),
        }
    }),
    oauth2.ServerIssuerFunc(func(ctx context.Context, req oauth2.IssuerRequest) string {
        return fmt.Sprintf("%s://%s", req.Scheme, req.Host)
    }),
)

静态 Issuer(单租户)/ Static Issuer (Single Tenant)

srv := oauth2.NewServer(
    oauth2.ServerIssuer("https://auth.example.com"),
)

多租户 JWT 密钥 / Multi-tenant JWT Key

// 每个租户使用独立的 JWT 签名密钥
srv.AccessToken = oauth2.NewMultiTenantAccessToken(func(ctx context.Context, issuer string) []byte {
    // issuer = "https://tenant1.example.com"
    // 根据 issuer 从数据库/配置中获取对应租户的密钥
    return getTenantJwtKey(issuer)
})

PKCE Support / PKCE 支持

PKCE (Proof Key for Code Exchange) 是 RFC 7636 定义的扩展,用于增强公开客户端(如移动应用、单页应用)的安全性。

客户端实现 / Client Implementation

// 1. 生成 code_verifier 和 code_challenge
codeVerifier := oauth2.RandomCodeVerifier()
codeChallenge := oauth2.GenerateCodeChallenge(codeVerifier, oauth2.CodeChallengeMethodS256)

// 2. 授权请求中包含 code_challenge
// GET /authorize?response_type=code&client_id=xxx&redirect_uri=xxx
//     &code_challenge=xxx&code_challenge_method=S256

// 3. Token 请求中包含 code_verifier
// POST /token
//     grant_type=authorization_code&code=xxx&code_verifier=xxx

服务端实现 / Server Implementation

// GenerateCode 需要存储 PKCE 参数
srv.GenerateCode = func(ctx context.Context, clientID, openID, redirectURI string, 
    scope []string, codeChallenge, codeChallengeMethod string) (string, error) {
    code := oauth2.RandomCode()
    // 存储: code -> {clientID, openID, redirectURI, scope, codeChallenge, codeChallengeMethod}
    return code, nil
}

// VerifyCode 需要返回 PKCE 参数
srv.VerifyCode = func(ctx context.Context, code, clientID, redirectURI string) (*oauth2.CodeValue, error) {
    // 从存储中获取
    return &oauth2.CodeValue{
        ClientID:            clientID,
        RedirectURI:         redirectURI,
        Scope:               []string{"read", "write"},
        CodeChallenge:       savedCodeChallenge,       // PKCE
        CodeChallengeMethod: savedCodeChallengeMethod, // PKCE
    }, nil
}

Examples / 示例

oauth2-server

server/client examples

Documentation / 文档参考

  1. 《理解OAuth 2.0》阮一峰
  2. RFC 6749 - The OAuth 2.0 Authorization Framework
  3. RFC 7636 - Proof Key for Code Exchange (PKCE)
  4. RFC 8628 - OAuth 2.0 Device Authorization Grant
  5. RFC 7662 - OAuth 2.0 Token Introspection
  6. RFC 7009 - OAuth 2.0 Token Revocation

Grant Types / 授权模式

Authorization Code / 授权码模式

授权码模式是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与"服务提供商"的认证服务器进行互动。

Implicit / 简化模式

简化模式不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤。

Resource Owner Password Credentials / 密码模式

用户向客户端提供自己的用户名和密码,客户端使用这些信息向"服务商提供商"索要授权。

Client Credentials / 客户端凭证模式

客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行认证。

Device Code / 设备模式

设备授权模式用于无法输入的设备(如智能电视、IoT设备等)。

Server Configuration / 服务器配置

Server Options / 服务器选项

| Option | Description | |--------|-------------| | ServerLogger(log) | 设置日志记录器 | | ServerIssuer(issuer) | 设置静态 JWT issuer | | ServerIssuerFunc(fn) | 设置动态 JWT issuer 函数(SaaS多租户) | | ServerIssuerRequestFunc(fn) | 设置从HTTP请求提取信息的函数 | | ServerDeviceAuthorizationEndpointEnabled(bool) | 启用设备授权端点 | | ServerIntrospectEndpointEnabled(bool) | 启用令牌内省端点 | | ServerTokenRevocationEnabled(bool) | 启用令牌撤销端点 |

AccessToken 配置 / AccessToken Configuration

| Function | Description | |----------|-------------| | NewDefaultAccessToken(key) | 创建静态密钥的 AccessToken 处理器(单租户) | | NewMultiTenantAccessToken(fn) | 创建动态密钥的 AccessToken 处理器(多租户) |

PKCE 工具函数 / PKCE Utility Functions

| Function | Description | |----------|-------------| | RandomCodeVerifier() | 生成随机的 code_verifier (43字符) | | GenerateCodeChallenge(verifier, method) | 根据 verifier 生成 code_challenge | | VerifyCodeChallenge(challenge, method, verifier) | 验证 code_verifier 是否匹配 |

PKCE 常量 / PKCE Constants

| Constant | Description | |----------|-------------| | CodeChallengeMethodPlain | PKCE plain 方法 | | CodeChallengeMethodS256 | PKCE S256 方法 (推荐) |

Complete Server Example / 完整服务器示例

package main

import (
    "context"
    "fmt"
    "net/http"

    "github.com/gin-gonic/gin"
    "github.com/nilorg/oauth2"
)

var clients = map[string]string{
    "oauth2_client": "password",
}

func main() {
    srv := oauth2.NewServer(
        // SaaS多租户:动态Issuer
        oauth2.ServerIssuerFunc(func(ctx context.Context, req oauth2.IssuerRequest) string {
            return fmt.Sprintf("%s://%s", req.Scheme, req.Host)
        }),
        oauth2.ServerDeviceAuthorizationEndpointEnabled(true),
    )

    srv.VerifyClient = func(ctx context.Context, basic *oauth2.ClientBasic) (err error) {
        pwd, ok := clients[basic.ID]
        if !ok || basic.Secret != pwd {
            return oauth2.ErrInvalidClient
        }
        return nil
    }

    srv.VerifyClientID = func(ctx context.Context, clientID string) (err error) {
        if _, ok := clients[clientID]; !ok {
            return oauth2.ErrInvalidClient
        }
        return nil
    }

    srv.VerifyCode = func(ctx context.Context, code, clientID, redirectURI string) (*oauth2.CodeValue, error) {
        return &oauth2.CodeValue{
            ClientID:    clientID,
            RedirectURI: redirectURI,
            Scope:       []string{"read", "write"},
            // PKCE: 如果启用 PKCE,需要从存储中返回 CodeChallenge 和 CodeChallengeMethod
        }, nil
    }

    srv.GenerateCode = func(ctx context.Context, clientID, openID, redirectURI string, 
        scope []string, codeChallenge, codeChallengeMethod string) (string, error) {
        code := oauth2.RandomCode()
        // 存储 code 信息,包括 PKCE 参数
        return code, nil
    }

    srv.VerifyRedirectURI = func(ctx context.Context, clientID, redirectURI string) error {
        return nil
    }

    srv.VerifyPassword = func(ctx context.Context, clientID, username, password string) (string, error) {
        if username == "admin" && password == "123456" {
            return "user_001", nil
        }
        return "", oauth2.ErrUnauthorizedClient
    }

    srv.VerifyScope = func(ctx context.Context, scopes []string, clientID string) error {
        return nil
    }

    srv.VerifyGrantType = func(ctx context.Context, clientID, grantType string) error {
        return nil
    }

    srv.AccessToken = oauth2.NewDefaultAccessToken([]byte("your-jwt-secret"))

    srv.GenerateDeviceAuthorization = func(ctx context.Context, issuer, verificationURI, clientID string, scope []string) (*oauth2.DeviceAuthorizationResponse, error) {
        return &oauth2.DeviceAuthorizationResponse{
            DeviceCode:      oauth2.RandomCode(),
            UserCode:        oauth2.RandomUserCode(),
            VerificationURI: issuer + verificationURI,
            ExpiresIn:       1800,
            Interval:        5,
        }, nil
    }

    srv.VerifyDeviceCode = func(ctx context.Context, deviceCode, clientID string) (*oauth2.DeviceCodeValue, error) {
        return nil, nil
    }

    if err := srv.InitWithError(); err != nil {
        panic(err)
    }

    r := gin.Default()
    oauth2Group := r.Group("/oauth2")
    {
        oauth2Group.GET("/authorize", func(c *gin.Context) {
            srv.HandleAuthorize(c.Writer, c.Request)
        })
        oauth2Group.POST("/token", func(c *gin.Context) {
            srv.HandleToken(c.Writer, c.Request)
        })
        oauth2Group.POST("/device_authorization", func(c *gin.Context) {
            srv.HandleDeviceAuthorization(c.Writer, c.Request)
        })
    }

    http.ListenAndServe(":8003", r)
}

Test / 测试

# Password Grant
curl -X POST http://localhost:8003/oauth2/token \

Related Skills

View on GitHub
GitHub Stars13
CategoryCustomer
Updated17d ago
Forks2

Languages

Go

Security Score

95/100

Audited on Mar 20, 2026

No findings