一个通用的居于 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() 语句来进行文件操作。本文将…...
日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...
内存分配函数malloc kmalloc vmalloc
内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...
7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...
《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
解锁数据库简洁之道:FastAPI与SQLModel实战指南
在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可…...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
破解路内监管盲区:免布线低位视频桩重塑停车管理新标准
城市路内停车管理常因行道树遮挡、高位设备盲区等问题,导致车牌识别率低、逃费率高,传统模式在复杂路段束手无策。免布线低位视频桩凭借超低视角部署与智能算法,正成为破局关键。该设备安装于车位侧方0.5-0.7米高度,直接规避树枝遮…...
libfmt: 现代C++的格式化工具库介绍与酷炫功能
libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库,提供了高效、安全的文本格式化功能,是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全:…...
嵌入式学习之系统编程(九)OSI模型、TCP/IP模型、UDP协议网络相关编程(6.3)
目录 一、网络编程--OSI模型 二、网络编程--TCP/IP模型 三、网络接口 四、UDP网络相关编程及主要函数 编辑编辑 UDP的特征 socke函数 bind函数 recvfrom函数(接收函数) sendto函数(发送函数) 五、网络编程之 UDP 用…...
