Go语言实现OAuth 2.0认证服务器
文章目录
- 1. 项目概述
- 1.1 OAuth2 流程
- 2. OAuth 2.0 Storage接口解析
- 2.1 基础方法
- 2.2 客户端管理相关方法
- 2.3 授权码相关方法
- 2.4 访问令牌相关方法
- 2.5 刷新令牌相关方法
- 2.6 方法调用时序
- 2.7 关键注意点
- 3. MySQL存储实现原理
- 3.1 数据库设计
- 3.2 核心实现
- 4. OAuth 2.0授权码流程时序图
- 5. 使用示例
- 5.1 初始化存储
- 5.2 创建OAuth服务器
- 5.3 实现授权端点
- 5.4 实现客户端令牌端点
- 5.5 Callback回调断点(code换access_token)
- 完整流程
- 6. 总结
1. 项目概述
在上一篇文章中,我们详细介绍了OAuth 2.0的基本概念、授权流程以及各种授权模式的应用场景。本文将使用Go语言实现一个完整的OAuth 2.0认证服务器。
我们选择了github.com/openshift/osin这个成熟的OAuth 2.0框架作为基础,重点实现了其MySQL 来作为storage的驱动。osin提供了OAuth 2.0服务器的核心功能,但它的存储接口需要我们自己实现。通过实现MySQL存储,我们可以将OAuth 2.0的授权数据持久化到数据库中,使得服务更加可靠和可扩展。
本文的完整代码:oauth2
1.1 OAuth2 流程
让我们通过一个流程图来说明这些方法在 OAuth2 授权码模式中的位置:

2. OAuth 2.0 Storage接口解析
osin库中的Storage接口是实现OAuth 2.0服务器的核心,它定义了所有必要的存储操作。让我们详细解析每个方法在OAuth 2.0流程中的作用:
2.1 基础方法
Clone() Storage // 克隆存储实例,用于处理并发访问
Close() // 关闭存储连接,释放资源
2.2 客户端管理相关方法
GetClient(id string) (Client, error)UpdateClient(c Client) errorCreateClient(c Client) errorRemoveClient(id string) error
这些方法负责OAuth客户端的CRUD操作:
GetClient: 根据客户端ID获取客户端信息,用于验证客户端身份UpdateClient: 更新客户端信息CreateClient: 创建新的客户端RemoveClient: 删除指定客户端
2.3 授权码相关方法
SaveAuthorize(data *AuthorizeData) errorLoadAuthorize(code string) (*AuthorizeData, error)RemoveAuthorize(code string) error
这些方法处理授权码授权流程:
SaveAuthorize: 保存授权码信息LoadAuthorize: 验证授权码有效性RemoveAuthorize: 使用后删除授权码
这组方法用于处理授权码的生命周期:

2.4 访问令牌相关方法
SaveAccess(data *AccessData) errorLoadAccess(token string) (*AccessData, error)RemoveAccess(token string) error
这些方法处理访问令牌的生命周期:
SaveAccess: 保存访问令牌LoadAccess: 验证访问令牌RemoveAccess: 撤销访问令牌
访问令牌的生命周期管理:

2.5 刷新令牌相关方法
LoadRefresh(token string) (*AccessData, error)RemoveRefresh(token string) error
这些方法处理刷新令牌:
LoadRefresh: 加载刷新令牌对应的访问令牌数据RemoveRefresh: 删除刷新令牌
刷新令牌的处理流程:

2.6 方法调用时序
在完整的 OAuth2 流程中,这些方法的调用顺序如下:

2.7 关键注意点
-
原子性:
- SaveAuthorize 和 SaveAccess 操作需要保证原子性
- RemoveAuthorize 和 SaveAccess 通常需要在同一事务中执行
-
安全性:
- 所有存储的令牌数据应该加密
- 实现适当的过期机制
-
性能考虑:
- LoadAccess 方法会频繁调用,应考虑缓存
- Clone 方法对并发性能很重要
-
数据一致性:
- 确保授权码只能使用一次
- 正确处理令牌过期
- 维护刷新令牌与访问令牌的关联
3. MySQL存储实现原理
3.1 数据库设计
项目使用了两个主要的数据表:
CREATE TABLE client (id varchar(255) NOT NULL PRIMARY KEY,secret varchar(255) NOT NULL,extra text,redirect_uri varchar(255) NOT NULL,created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
)CREATE TABLE token (id varchar(255) NOT NULL PRIMARY KEY,client_id varchar(255) NOT NULL,type varchar(20) NOT NULL, access_token varchar(255), refresh_token varchar(255), code varchar(255), expires_in int NOT NULL,scope varchar(255),redirect_uri varchar(255) NOT NULL,state varchar(255),extra text,created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,expires_at timestamp NULL
)
3.2 核心实现
我们的MySQL存储实现主要包含以下特点:
- 使用
go-zero框架的sqlx包进行数据库操作 - 实现了完整的事务支持
- 支持令牌过期检查
- 提供了表前缀支持,便于多租户场景
4. OAuth 2.0授权码流程时序图

5. 使用示例
5.1 初始化存储
func initStorage(svcCtx *svc.ServiceContext) *service.Storage {storage := service.NewStorage(svcCtx, "oauth2_")err := storage.CreateSchemas()if err != nil {panic(err)}return storage
}
5.2 创建OAuth服务器
// newOAuthServer 创建一个新的OAuth服务器实例
func newOAuthServer(svc *svc.ServiceContext) *osin.Server {config := osin.NewServerConfig()config.AllowedAuthorizeTypes = osin.AllowedAuthorizeType{osin.CODE}config.AllowedAccessTypes = osin.AllowedAccessType{osin.AUTHORIZATION_CODE,osin.REFRESH_TOKEN,}config.AuthorizationExpiration = 600 // 10分钟config.AccessExpiration = 3600 // 1小时config.AllowGetAccessRequest = trueconfig.ErrorStatusCode = 401storage := service.NewStorage(svc, "osin_")server := osin.NewServer(config, storage)return server
}
5.3 实现授权端点
func AuthorizeHandler(svc *svc.ServiceContext) http.HandlerFunc {return func(w http.ResponseWriter, r *http.Request) {server := newOAuthServer(svc)resp := server.NewResponse()defer resp.Close()if ar := server.HandleAuthorizeRequest(resp, r); ar != nil {// 验证客户端if ar.Client == nil {resp.SetError("unauthorized_client", "客户端未授权")osin.OutputJSON(resp, w, r)return}// 验证重定向URIif ar.RedirectUri == "" {resp.SetError("invalid_request", "缺少重定向URI")osin.OutputJSON(resp, w, r)return}ar.Authorized = true// 完成授权请求,这里只会返回授权码server.FinishAuthorizeRequest(resp, r, ar)// 如果没有错误,会重定向到客户端的redirect_uri,并带上授权码if !resp.IsError {resp.Type = osin.REDIRECT}}// 输出响应(可能是重定向或错误信息)osin.OutputJSON(resp, w, r)}
}
5.4 实现客户端令牌端点
func TokenHandler(svc *svc.ServiceContext) http.HandlerFunc {return func(w http.ResponseWriter, r *http.Request) {logger := logx.WithContext(r.Context())server := newOAuthServer(svc)resp := server.NewResponse()defer resp.Close()if ar := server.HandleAccessRequest(resp, r); ar != nil {// 验证客户端if ar.Client == nil {resp.SetError("unauthorized_client", "客户端未授权")osin.OutputJSON(resp, w, r)return}// 授权请求ar.Authorized = trueserver.FinishAccessRequest(resp, r, ar)}if resp.IsError {logger.Errorf("Token error: %v", resp.InternalError)} else {logger.Infof("Token granted: %s", resp.Output["access_token"])}osin.OutputJSON(resp, w, r)}
}
5.5 Callback回调断点(code换access_token)
func CallbackHandler(svc *svc.ServiceContext) http.HandlerFunc {return func(w http.ResponseWriter, r *http.Request) {// 获取授权码code := r.URL.Query().Get("code")if code == "" {// 检查是否有错误信息if error := r.URL.Query().Get("error"); error != "" {errorDesc := r.URL.Query().Get("error_description")http.Error(w, fmt.Sprintf("授权失败: %s - %s", error, errorDesc), http.StatusBadRequest)return}http.Error(w, "未收到授权码", http.StatusBadRequest)return}// 初始化 OAuth 服务器server := newOAuthServer(svc)// 先加载授权数据authData, err := server.Storage.LoadAuthorize(code)if err != nil {resp := server.NewResponse()resp.SetError("invalid_grant", "授权码无效或已过期")osin.OutputJSON(resp, w, r)return}// 创建访问令牌请求ar := &osin.AccessRequest{Type: osin.AUTHORIZATION_CODE,Code: code,Client: authData.Client,RedirectUri: authData.RedirectUri,Scope: authData.Scope,GenerateRefresh: true,Authorized: true,Expiration: server.Config.AccessExpiration,}// 处理访问令牌请求resp := server.NewResponse()defer resp.Close()if err := server.Storage.RemoveAuthorize(code); err != nil {resp.SetError("server_error", "无法删除授权码")osin.OutputJSON(resp, w, r)return}server.FinishAccessRequest(resp, r, ar)if resp.IsError {osin.OutputJSON(resp, w, r)return}// API 请求则返回 JSONosin.OutputJSON(resp, w, r)}
}
完整流程

关键流程说明
- 授权码获取:
- 客户端首先访问/oauth/authorize端点获取授权码
- 服务器生成授权码并保存到数据库
- 授权码换取令牌:
- 客户端带着授权码访问/oauth/callback端点
- CallbackHandler负责验证授权码并换取访问令牌
- 这一步通常在实际应用中是由前端页面完成的,但在我们的实现中直接由后端处理
- 令牌生成流程:
- 验证授权码有效性
- 删除已使用的授权码(确保一次性使用)
- 生成访问令牌和刷新令牌
- 将令牌信息返回给客户端
6. 总结
本项目实现了一个完整的OAuth 2.0认证服务器,通过实现osin的Storage接口,提供了可靠的MySQL存储层。主要特点包括:
- 完整实现OAuth 2.0规范
- 可靠的MySQL存储实现
- 支持授权码和刷新令牌流程
- 完善的错误处理和安全机制
- 易于扩展和定制
通过这个实现,我们可以快速搭建起一个生产级别的OAuth 2.0认证服务器,为各类应用提供标准的身份认证服务。
相关文章:
Go语言实现OAuth 2.0认证服务器
文章目录 1. 项目概述1.1 OAuth2 流程 2. OAuth 2.0 Storage接口解析2.1 基础方法2.2 客户端管理相关方法2.3 授权码相关方法2.4 访问令牌相关方法2.5 刷新令牌相关方法 2.6 方法调用时序2.7 关键注意点3. MySQL存储实现原理3.1 数据库设计3.2 核心实现 4. OAuth 2.0授权码流程…...
【2025年认证杯数学中国数学建模网络挑战赛】C题 数据预处理与问题一二求解
目录 【2025年认证杯数学建模挑战赛】C题数据预处理与问题一求解三、数据预处理及分析3.1 数据可视化3.2 滑动窗口相关系数统计与动态置信区间耦合分析模型3.3 耦合关系分析结果 四、问题一代码数据预处理问题一 【2025年认证杯数学建模挑战赛】C题 数据预处理与问题一求解 三…...
2025年最新Web安全(面试题)
活动发起人小虚竹 想对你说: 这是一个以写作博客为目的的创作活动,旨在鼓励大学生博主们挖掘自己的创作潜能,展现自己的写作才华。如果你是一位热爱写作的、想要展现自己创作才华的小伙伴,那么,快来参加吧!…...
利用Global.asax在ASP.NET Web应用中实现功能
Global.asax文件(也称为ASP.NET应用程序文件)是ASP.NET Web应用程序中的一个重要文件,它允许您处理应用程序级别和会话级别的事件。下面介绍如何利用Global.asax来实现各种功能。 Global.asax基本结构 <% Application Language"C#&…...
开源微调混合推理模型:cogito-v1-preview-qwen-32B
一、模型概述 1.1 模型特点 Cogito v1-preview-qwen-32B 是一款基于指令微调的生成式语言模型(LLM),具有以下特点: 支持直接回答(标准模式)和自我反思后再回答(推理模式)。使用 I…...
Golang|Channel 相关用法理解
文章目录 用 channel 作为并发小容器channel 的遍历channel 导致的死锁问题用 channel 传递信号用 channel 并行处理文件用channel 限制接口的并发请求量用 channel 限制协程的总数量 用 channel 作为并发小容器 注意这里的 ok 如果为 false,表示此时不仅channel为空…...
C++ - #命名空间 #输入、输出 #缺省参数 #函数重载 #引用 # const 引用 #inline #nullptr
文章目录 前言 一、实现C版本的hello world 二、命名空间 1、namespace 的价值 2、namespace 的定义 (1.域会影响一个编译器编译语法时的查找规则 (2、域会影响生命周期 (3、命名空间域只能定义在全局 (4、编译器会自动合并相同命名空间中的内容 (5、C标准库放在命名…...
Spring Boot 应用程序中配置使用consul
配置是 Spring Boot 应用程序中的一部分,主要用于配置服务端口、应用名称、Consul 服务发现以及健康检查等功能。以下是对每个部分的详细解释: 1. server.port server:port: 8080作用:指定 Spring Boot 应用程序运行的端口号。解释…...
JSON处理工具/框架的常见类型及详解,以Java语言为例
以下是JSON处理工具/框架的常见类型及详解,以Java语言为例: 一、主流JSON处理工具对比 Jackson(推荐) 特点:高性能、功能丰富,支持注解(如JsonProperty)、树形模型(Json…...
4. k8s核心概念 pod deployment service
以下是 Kubernetes 的核心概念详解,涵盖 Pod、Service、Deployment 和 Node,以及它们之间的关系和实际应用场景: 1. Pod 定义与作用 • 最小部署单元:Pod 是 Kubernetes 中可创建和管理的最小计算单元,包含一个或多个…...
c++中max函数怎么使用?
在C中,std::max函数是一个在 <algorithm> 标准库头文件中定义的函数模板,用于确定两个或更多个数值之间的最大值。下面是对std::max函数的基本介绍和使用示例: 函数原型: template <class T> constexpr const T&…...
使用Redis实现分布式限流
一、限流场景与算法选择 1.1 为什么需要分布式限流 在高并发系统中,API接口的突发流量可能导致服务雪崩。传统的单机限流方案在分布式环境下存在局限,需要借助Redis等中间件实现集群级流量控制。 1.2 令牌桶算法优势 允许突发流量:稳定速…...
中间件--ClickHouse-1--基础介绍(列式存储,MPP架构,分布式计算,SQL支持,向量化执行,亿万级数据秒级查询)
1、概述 ClickHouse是一个用于联机分析(OLAP)的列式数据库管理系统(DBMS)。它由俄罗斯的互联网巨头Yandex为解决其内部数据分析需求而开发,并于2016年开源。专为大规模数据分析,实时数据分析和复杂查询设计,具有高性能、实时数据和可扩展性等…...
Java中的经典排序算法:插入排序、希尔排序、选择排序、堆排序与冒泡排序(如果想知道Java中有关插入排序、希尔排序、选择排序、堆排序与冒泡排序的知识点,那么只看这一篇就足够了!)
前言:排序算法是计算机科学中的基础问题之一,它在数据处理、搜索算法以及各种优化问题中占有重要地位,本文将详细介绍几种经典的排序算法:插入排序、选择排序、堆排序和冒泡排序。 ✨✨✨这里是秋刀鱼不做梦的BLOG ✨✨✨想要了解…...
K8S+Prometheus+Consul+alertWebhook实现全链路服务自动发现与监控、告警配置实战
系列文章目录 k8s服务注册到consul prometheus监控标签 文章目录 系列文章目录前言一、环境二、Prometheus部署1.下载2.部署3.验证 三、kube-prometheus添加自定义监控项1.准备yaml文件2.创建新的secret并应用到prometheus3.将yaml文件应用到集群4.重启prometheus-k8s pod5.访…...
uniapp-商城-25-顶部模块高度计算
计算高度: 使用computed进行顶部模块的计算。 总高度:bartotalHeight log 介绍--收款码这一条目 也就是上一章节的title的高度计算 bodybarheight。 在该组件中: js部分的代码: 包含了导出的名字: shop-head…...
Proxmox VE 网络配置命令大全
如果对 Proxmox VE 全栈管理感兴趣,可以关注“Proxmox VE 全栈管理”专栏,后续文章将围绕该体系,从多个维度深入展开。 概要:Proxmox VE 网络配置灵活,满足虚拟化组网需求。基础靠桥接实现虚拟机与物理网络互联&#x…...
非关系型数据库(NoSQL)与 关系型数据库(RDBMS)的比较
非关系型数据库(NoSQL)与 关系型数据库(RDBMS)的比较 一、引言二、非关系型数据库(NoSQL)2.1 优势 三、关系型数据库(RDBMS)3.1 优势 四、结论 💖The Begin💖…...
WPF 图标原地旋转
如何使元素原地旋转 - WPF .NET Framework | Microsoft Learn <ButtonRenderTransformOrigin"0.5,0.5"HorizontalAlignment"Left">Hello,World<Button.RenderTransform><RotateTransform x:Name"MyAnimatedTransform" Angle"…...
蓝桥杯2024国B数星星
小明正在一棵树上数星星,这棵树有 n 个结点 1,2,⋯,n。他定义树上的一个子图 G 是一颗星星,当且仅当 G 同时满足: G 是一棵树。G 中存在某个结点,其度数为 ∣VG∣−1。其中 ∣VG∣ 表示这个子图含有的结点数。 两颗星星不相…...
Ubuntu 系统上通过终端安装 Google Chrome 浏览器
使用终端安装前,需要配置好终端使用了代理。 参考文章:https://blog.csdn.net/yangshuo1281/article/details/147262633?spm1011.2415.3001.5331 转自 风车 首先,添加 Google Chrome 的软件源和密钥: # 下载并添加 Google 的签…...
中科院1区顶刊Expert Systems with Applications ESO:增强型蛇形算法,性能不错
Snake Optimizer(SO)是一种优化效果良好的新颖算法,但由于自然规律的限制,在探索和开发阶段参数更多是固定值,因此SO算法很快陷入局部优化并慢慢收敛。本文通过引入新颖的基于对立的学习策略和新的动态更新机制&#x…...
zk(Zookeeper)实现分布式锁
Zookeeper实现分布式锁 1,zk中锁的种类: 读锁:大家都可以读,要想上读锁的前提:之前的锁没有写锁 写锁:只有得到写锁的才能写。要想上写锁的前提是:之前没有任何锁 2,zk如何上读锁 创…...
自我生成,自我训练:大模型用合成数据实现“自我学习”机制实战解析
目录 自我生成,自我训练:大模型用合成数据实现“自我学习”机制实战解析 一、什么是自我学习机制? 二、实现机制:如何用合成数据实现自我训练? ✅ 方式一:Prompt强化生成 → 自我采样再训练 ✅ 方式二…...
【Vue】从 MVC 到 MVVM:前端架构演变与 Vue 的实践之路
个人博客:haichenyi.com。感谢关注 一. 目录 一–目录二–架构模式的演变背景三–MVC:经典的分层起点四–MVP:面向接口的解耦尝试五–MVVM:数据驱动的终极形态六–Vue:MVVM 的现代化实践 二. 架构模…...
prototype`和`__proto__`有什么区别?如何手动修改一个对象的原型?
在 JavaScript 中,prototype 和 __proto__ 都与原型链相关,但它们的角色和用途有本质区别: 1. prototype 和 __proto__ 的区别 特性prototype__proto__归属对象仅函数对象拥有(如构造函数)所有对象默认拥有࿰…...
Flask+Influxdb+grafna构建电脑性能实时监控系统
Influx下载地址,这里下载了以下版本influxdb-1.8.5_windows_amd64.zip 运行前需要先启动Influx数据库: 管理员方式运行cmd->F:->cd F:\influxdb\influxdb-1.8.5-1->influxd -config influxdb.conf,以influxdb.conf配置文件启动数…...
关于链接库
在 C# 中,链接库主要分为两种类型:托管链接库和非托管链接库,以下为你详细介绍它们的特点和导入方式: 托管链接库 特点 托管链接库通常是用 .NET 兼容的语言(如 C#、VB.NET 等)编写的,运行在…...
若伊微服务版本教程(自参)
第一步 若伊官网下载源码 https://ruoyi.vip/ RuoYi-Cloud: 🎉 基于Spring Boot、Spring Cloud & Alibaba的分布式微服务架构权限管理系统,同时提供了 Vue3 的版本 git clone 到 本地 目录如下: 第二部 参考官网 运行部署说明 环境部署…...
数据库性能优化(sql优化)_分布式优化思路01_yxy
数据库性能优化_分布式优化思路01 1 分布式数据库的独特挑战2 分布式新增操作符介绍2.1 数据交换操作符(ESEND/ERECV):2.2 数据迭代操作符GI:3 核心优化策略(一)_分区裁剪优化3.1 普通分区裁剪3.2 动态分区裁剪1 分布式数据库的独特挑战 在分布式数据库系统中,核心为数据被…...
