一个通用的居于 OAuth2的API集成方案
在现代 web 应用程序中,OAuth 协议是授权和认证的主流选择。为了与多个授权提供商进行无缝对接,我们需要一个易于扩展和维护的 OAuth 解决方案。本文将介绍如何构建一个灵活的、支持多提供商的 OAuth 系统,包括动态 API 调用、路径参数替换、查询参数处理等功能。

目录结构
我们将创建一个简单而清晰的目录结构,以便于管理我们的代码和配置。以下是本解决方案的整体目录结构:

核心模块:OAuth.Core
ProviderConfiguration 数据模型
在 OAuth.Core 模块,我们定义了 ProviderConfiguration 类,该类用于配置各个 OAuth 提供商的基本信息和 API 详情。
using System.Collections.Generic; namespace OAuth.Core.Models
{ /// <summary> /// 单个 OAuth 提供商的配置 /// </summary> public class ProviderConfiguration { public string ProviderName { get; set; } // 提供商名称 public string BaseUrl { get; set; } // 提供商 API 基础路径 public string AuthorizationUrl { get; set; } // 授权 URL public string TokenUrl { get; set; } // 获取 AccessToken URL public string ClientId { get; set; } // 应用的 Client ID public string ClientSecret { get; set; } // 应用的 Client Secret public string RedirectUri { get; set; } // 应用回调的重定向 URL public List<string> Scopes { get; set; } = new(); // 授权范围 public Dictionary<string, string> CommonHeaders { get; set; } = new(); // 公共 Headers public Dictionary<string, ApiConfig> Apis { get; set; } = new(); // 配置的 API 集合 } /// <summary> /// 单个 API 的动态配置 /// </summary> public class ApiConfig { public string Url { get; set; } // 动态路径 URL,例如 /user/{userId} public string Method { get; set; } = "GET"; // HTTP 请求方法 public bool RequiresAuthentication { get; set; } = true; // 是否需要 AccessToken public Dictionary<string, string> Headers { get; set; } = new(); // API 专属 Header 配置 public string BodyFormat { get; set; } = "application/json"; // Body 格式,默认 JSON public List<ApiParameterConfig> Parameters { get; set; } = new(); // 参数配置 } /// <summary> /// API 参数配置 /// </summary> public class ApiParameterConfig { public string Name { get; set; } // 参数名称 public string Location { get; set; } // 参数位置(path, query, body, header) public string DefaultValue { get; set; } // 参数默认值(若需要) }
}
TokenInfo 管理类
TokenInfo 类用于管理和存储 Access Token 和 Refresh Token 的生命周期信息。
using System; namespace OAuth.Core.Models
{ public class TokenInfo { public string AccessToken { get; set; } public string RefreshToken { get; set; } public DateTime ExpiresAt { get; set; } public bool IsValid => DateTime.UtcNow < ExpiresAt; }
}
结果封装类 Result
通过 Result<T> 类封装 API 调用的结果,以便于处理成功和失败的状态。
namespace OAuth.Core.Models
{ public class Result<T> { public T Data { get; } public bool IsSuccess { get; } public string Error { get; } private Result(T data, bool isSuccess, string error) { Data = data; IsSuccess = isSuccess; Error = error; } public static Result<T> Success(T data) => new(data, true, null); public static Result<T> Failure(string error) => new(default, false, error); }
}
核心逻辑实现:OAuth.Infrastructure
AuthProvider 类
这是实施 OAuth 逻辑的核心类,负责发送请求、处理响应,以及替换路径参数。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using OAuth.Core.Models; namespace OAuth.Infrastructure
{ public class AuthProvider { private readonly HttpClient _httpClient; private readonly ProviderConfiguration _config; public AuthProvider(HttpClient httpClient, ProviderConfiguration config) { _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); _config = config ?? throw new ArgumentNullException(nameof(config)); } /// <summary> /// 获取 AccessToken /// </summary> public async Task<Result<TokenInfo>> GetAccessTokenAsync(string code) { var url = $"{_config.BaseUrl}{_config.TokenUrl}"; var payload = new Dictionary<string, string> { { "client_id", _config.ClientId }, { "client_secret", _config.ClientSecret }, { "code", code }, { "grant_type", "authorization_code" }, { "redirect_uri", _config.RedirectUri } }; var response = await _httpClient.PostAsync(url, new FormUrlEncodedContent(payload)); if (!response.IsSuccessStatusCode) return Result<TokenInfo>.Failure(await response.Content.ReadAsStringAsync()); var content = await response.Content.ReadAsStringAsync(); var tokenResponse = JsonSerializer.Deserialize<Dictionary<string, object>>(content); return Result<TokenInfo>.Success(new TokenInfo { AccessToken = tokenResponse["access_token"].ToString(), RefreshToken = tokenResponse.ContainsKey("refresh_token") ? tokenResponse["refresh_token"].ToString() : null, ExpiresAt = DateTime.UtcNow.AddSeconds(Convert.ToDouble(tokenResponse["expires_in"])) }); } /// <summary> /// 执行 API 调用 /// </summary> public async Task<Result<T>> CallApiAsync<T>(string apiName, Dictionary<string, object> parameters = null) { if (!_config.Apis.TryGetValue(apiName, out var apiConfig)) return Result<T>.Failure($"未找到 API '{apiName}' 的配置"); // 动态替换路径参数 var url = ReplacePathParameters($"{_config.BaseUrl}{apiConfig.Url}", parameters); // 构建 HTTP 请求 var request = new HttpRequestMessage(new HttpMethod(apiConfig.Method), url); // 添加 Query 参数 url = AppendQueryParameters(url, apiConfig, parameters); // 添加 Body (如果是 POST/PUT 请求) if (apiConfig.Method == "POST" || apiConfig.Method == "PUT") { request.Content = CreateBodyContent(apiConfig, parameters); } // 设置公共和 API 专属 Header foreach (var header in _config.CommonHeaders) request.Headers.TryAddWithoutValidation(header.Key, header.Value); foreach (var header in apiConfig.Headers) request.Headers.TryAddWithoutValidation(header.Key, header.Value); // 添加 Authentication Token if (apiConfig.RequiresAuthentication) { var token = await GetValidAccessTokenAsync(); if (token == null) return Result<T>.Failure("未能获取有效令牌"); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.AccessToken); } // 执行请求 var response = await _httpClient.SendAsync(request); if (!response.IsSuccessStatusCode) return Result<T>.Failure(await response.Content.ReadAsStringAsync()); var responseContent = await response.Content.ReadAsStringAsync(); return Result<T>.Success(JsonSerializer.Deserialize<T>(responseContent)); } /// <summary> /// 替换路径中的动态参数 /// </summary> private string ReplacePathParameters(string url, Dictionary<string, object> parameters) { if (parameters is null) return url; foreach (var param in parameters) { var placeholder = $"{{{param.Key}}}"; if (url.Contains(placeholder)) { url = url.Replace(placeholder, param.Value.ToString()); } } return url; } /// <summary> /// 追加查询参数 /// </summary> private string AppendQueryParameters(string url, ApiConfig apiConfig, Dictionary<string, object> parameters) { var queryParams = new List<string>(); foreach (var paramConfig in apiConfig.Parameters) { if (paramConfig.Location == "query" && parameters.ContainsKey(paramConfig.Name)) { queryParams.Add($"{paramConfig.Name}={parameters[paramConfig.Name]}"); } } if (queryParams.Any()) { var separator = url.Contains("?") ? "&" : "?"; url += separator + string.Join("&", queryParams); } return url; } /// <summary> /// 创建请求体数据(Body) /// </summary> private HttpContent CreateBodyContent(ApiConfig apiConfig, Dictionary<string, object> parameters) { var bodyParams = parameters? .Where(p => apiConfig.Parameters.Any(pc => pc.Location == "body" && pc.Name == p.Key)) .ToDictionary(kv => kv.Key, kv => kv.Value); if (apiConfig.BodyFormat == "application/json") { return new StringContent(JsonSerializer.Serialize(bodyParams), Encoding.UTF8, "application/json"); } else if (apiConfig.BodyFormat == "application/x-www-form-urlencoded") { return new FormUrlEncodedContent(bodyParams.ToDictionary(k => k.Key, k => k.Value.ToString())); } throw new NotSupportedException("不支持的 Body 格式"); } /// <summary> /// 模拟获取有效的 AccessToken /// </summary> private async Task<TokenInfo> GetValidAccessTokenAsync() { // 这里可以实现从缓存或数据库中获取 token 的逻辑 return await Task.FromResult(new TokenInfo { AccessToken = "mocked_access_token", ExpiresAt = DateTime.UtcNow.AddHours(1) }); } }
}
AuthProviderFactory 类
AuthProviderFactory 负责创建 AuthProvider 的实例,简化多提供商的管理。
using System;
using System.Collections.Generic;
using System.Net.Http;
using OAuth.Core.Models; namespace OAuth.Infrastructure
{ public class AuthProviderFactory { private readonly IDictionary<string, ProviderConfiguration> _configurations; private readonly IHttpClientFactory _httpClientFactory; public AuthProviderFactory(IDictionary<string, ProviderConfiguration> configurations, IHttpClientFactory httpClientFactory) { _configurations = configurations ?? throw new ArgumentNullException(nameof(configurations)); _httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory)); } public AuthProvider Create(string providerName) { if (!_configurations.TryGetValue(providerName, out var config)) throw new KeyNotFoundException($"未找到提供商:{providerName}"); return new AuthProvider(_httpClientFactory.CreateClient(), config); } }
}
Web API 层:OAuth.WebApi
API 控制器
在 OAuth.WebApi 模块中,我们实现了 OAuthController,提供 API 的行为。
using Microsoft.AspNetCore.Mvc;
using OAuth.Infrastructure; namespace OAuth.WebApi.Controllers
{ [Route("api/oauth")] [ApiController] public class OAuthController : ControllerBase { private readonly AuthProviderFactory _factory; public OAuthController(AuthProviderFactory factory) { _factory = factory; } [HttpGet("{provider}/{api}")] public async Task<IActionResult> ExecuteApi(string provider, string api, [FromQuery] Dictionary<string, object> parameters) { var providerInstance = _factory.Create(provider); var result = await providerInstance.CallApiAsync<object>(api, parameters); if (!result.IsSuccess) return BadRequest(result.Error); return Ok(result.Data); } }
}
配置文件示例
appsettings.json 文件配置所有可使用的 OAuth 提供商的信息。
{ "OAuthProviders": { "WeCom": { "ProviderName": "WeCom", "BaseUrl": "https://qyapi.weixin.qq.com", "AuthorizationUrl": "/cgi-bin/authorize", "TokenUrl": "/cgi-bin/gettoken", "ClientId": "your-client-id", "ClientSecret": "your-client-secret", "RedirectUri": "https://your.app/wecom/callback", "CommonHeaders": { "User-Agent": "OAuthSolution Client/1.0" }, "Apis": { "GetUserInfo": { "Url": "/cgi-bin/user/get/{userid}", "Method": "GET", "RequiresAuthentication": true, "Parameters": [ { "Name": "userid", "Location": "path" } ] } } } }
}
应用程序启动和配置
最后,在 Program.cs 中,设置 ASP.NET Core 中的依赖注入以支持我们的架构。
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using OAuth.Core.Models;
using OAuth.Infrastructure; var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;
var oauthProviders = configuration.GetSection("OAuthProviders").Get<Dictionary<string, ProviderConfiguration>>(); builder.Services.AddHttpClient();
builder.Services.AddSingleton(oauthProviders);
builder.Services.AddSingleton<AuthProviderFactory>();
builder.Services.AddControllers(); var app = builder.Build();
app.MapControllers();
app.Run();
结论
通过上述方式,我们构建了一个可扩展的 OAuth2 API集成方案。这种设计不仅支持多种 OAuth 提供商的接入,还允许动态配置 API 的调用方式,使得代码简洁明了、易于维护。未来如需引入新的提供商或 API,只需在配置文件中新增相应信息即可,无需对现有逻辑进行修改。
若您遇到不同 OAuth 提供商的实际需求、疑问或问题,请随时在评论区留言,我们将一起探讨解决方案! 🌟
相关文章:
一个通用的居于 OAuth2的API集成方案
在现代 web 应用程序中,OAuth 协议是授权和认证的主流选择。为了与多个授权提供商进行无缝对接,我们需要一个易于扩展和维护的 OAuth 解决方案。本文将介绍如何构建一个灵活的、支持多提供商的 OAuth 系统,包括动态 API 调用、路径参数替换、…...
STM32配合可编程加密芯片SMEC88ST的防抄板加密方案设计
SMEC88ST SDK开发包下载 目前市场上很多嵌入式产品方案都是可以破解复制的,主要是因为方案主芯片不具备防破解的功能,这就导致开发者投入大量精力、财力开发的新产品一上市就被别人复制,到市场上的只能以价格竞争,最后工厂复制的产…...
QML学习(五) 做出第一个简单的应用程序
通过前面四篇对QML已经有了基本的了解,今天先尝试做出第一个单页面的桌面应用程序。 1.首先打开Qt,创建项目,选择“QtQuick Application - Empty” 空工程。 2.设置项目名称和项目代码存储路径 3.这里要注意选择你的编译器类型,以及输出的程…...
深入解析Android Framework中的android.location包:架构设计、设计模式与系统定制
深入解析Android Framework中的android.location包:架构设计、设计模式与系统定制 目录 引言android.location包概述核心类解析 LocationManagerLocationProviderLocationCriteriaGpsStatusGpsStatus.ListenerLocationListener位置服务的工作原理位置信息的获取与处理GPS状态…...
【C++11】类型分类、引用折叠、完美转发
目录 一、类型分类 二、引用折叠 三、完美转发 一、类型分类 C11以后,进一步对类型进行了划分,右值被划分纯右值(pure value,简称prvalue)和将亡值 (expiring value,简称xvalue)。 纯右值是指那些字面值常量或求值结果相当于…...
mongodb(6.0.15)安装注意事项,重装系统后数据恢复
window10系统 上周重装了系统,环境变量之类的都没有了。现在要恢复。 我电脑里之前的安装包没有删除(虽然之前也没在C盘安装,但是找不到了,所以需要重新下载安装),长下图这样。这个不是最新版本࿰…...
union的实际使用
记录一下,免得忘记: 1、定义一个共用体变量 这里定义一个64位变量 i2creg_rev,然后通过共用体定义两个位变量bits和bits_reverse,通过bit可以访问指定位的值大小,不需要自己再左移右移转换。 bits_reverse是bits的对…...
EKF 自动匹配维度 MATLAB代码
该 M A T L A B MATLAB MATLAB代码实现了扩展卡尔曼滤波( E...
Oracle复合索引规则指南
在Oracle中可以创建组合索引,即同时包含两个或两个以上列的索引。在组合索引的使用方面,Oracle有以下特点: 1、 当使用基于规则的优化器(RBO)时,只有当组合索引的前导列出现在SQL语句的where子句中时&#…...
JS - Array Api
判断一个对象是否为数组 /* 语法: Array.isArray(object); 参数:object 必需,要测试的对象。返回值 如果 object 是数组,则为 true;否则为 false。 如果 object 参数不是对象,则返回 false。 */ 一、改…...
【JS】for-in 和 for-of遍历对象的区别
【介绍】 for-in 和 for-of 都是 JavaScript 中用于遍历数据结构的循环语句,但它们的工作原理和适用场景有所不同。特别是它们在遍历对象时的行为是不同的。 【区别】 for-in 遍历对象 for-in 是用于遍历对象的 可枚举属性的键名(属性名)…...
【每日学点鸿蒙知识】ets匿名类、获取控件坐标、Web显示iframe标签、软键盘导致上移、改变Text的背景色
1、HarmonyOS ets不支持匿名类吗? 不支持,需要显式标注对象字面量的类型,可以参考以下文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/typescript-to-arkts-migration-guide-V5#%E9%9C%80%E8%A6%81%E6%…...
深度学习blog- 数学基础(全是数学)
矩阵:矩阵是一个二维数组,通常由行和列组成,每个元素可以通过行索引和列索引进行访问。 张量:张量是一个多维数组的抽象概念,可以具有任意数量的维度。除了标量(0D张量)、向量(…...
最后100米配送
1. 项目概述 1.1 项目目标 集成无人机与电动车:设计并实现将无人机固定在电动车上,利用电动车的电源进行飞行,实现高楼内部从电动车位置到用户办公/居住地点的最后100米精准配送。低成本实现:通过利用电动车现有的电源和结构&am…...
Linux的进程替换以及基础IO
进程替换 上一篇草率的讲完了进程地址空间的组成结构和之间的关系,那么我们接下来了解一下程序的替换。 首先,在进程部分我们提过了,其实文件可以在运行时变成进程,而我们使用的Linux软件其实也是一个进程,所以进一步…...
《计算机网络A》单选题-复习题库
1. 计算机网络最突出的优点是(D) A、存储容量大B、将计算机技术与通信技术相结合C、集中计算D、资源共享 2. RIP 路由协议的最大跳数是(C) A、13B、14C、15D、16 3. 下面哪一个网络层次不属于 TCP/IP 体系模型(D&a…...
闲谭Scala(2)--安装与环境配置
1. 概述 Java开发环境安装,需要两步,第一安装JDK,第二配置环境变量。 Scala的话,也是两步,第一安装Scale环境,第二配置环境变量。 需要注意的是,配置环境变量,主要是想让windows操…...
Python基于卷积神经网络的车牌识别系统开发与实现
1. 简介 车牌识别是人工智能在交通领域的重要应用,广泛用于高速违章检测、停车场管理和智能交通系统等场景。本系统通过基于卷积神经网络(CNN)的深度学习算法,结合 Python 和 MySQL 实现车牌的快速识别与管理。 系统特点&#x…...
Spring Boot集成Netty创建一个TCP服务器,接收16进制数据(自定义解码器和编码器)
Netty Netty是一个高性能、异步事件驱动的网络应用程序框架,它提供了对并发和异步编程的抽象,使得开发网络应用程序变得更加简单和高效。 在Netty中,EventLoopGroup是处理I/O操作的多线程事件循环器。在上面的示例中,我们创建了两个EventLoopGroup实例:bossGroup和worker…...
Python 中的 with open:文件操作的最佳实践
在 Python 中,文件操作是最常用的一项任务,无论是读取文件内容,还是将数据写入文件。传统的文件操作方式使用 open() 和 close() 函数来处理文件,但在实际开发中,我们推荐使用 with open() 语句来进行文件操作。本文将…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...
MODBUS TCP转CANopen 技术赋能高效协同作业
在现代工业自动化领域,MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步,这两种通讯协议也正在被逐步融合,形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...
项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...
