一个通用的居于 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() 语句来进行文件操作。本文将…...
强制脑机接口:某公司用神经监测防员工摸鱼
在科技伦理与管理方式交织的灰色地带,一则关于某公司计划引入脑机接口技术用于监测员工注意力、防止“摸鱼”的传闻,正在引发轩然大波。这并非科幻电影中的场景,而是随着神经技术快速商业化,正悄然逼近的现实可能。对于身处科技行…...
高效掌控Mem Reduct:智能多语言界面切换完全指南
高效掌控Mem Reduct:智能多语言界面切换完全指南 【免费下载链接】memreduct Lightweight real-time memory management application to monitor and clean system memory on your computer. 项目地址: https://gitcode.com/gh_mirrors/me/memreduct 你是否曾…...
如何用clawPDF高效解决日常办公中的5大文档处理难题?
如何用clawPDF高效解决日常办公中的5大文档处理难题? 【免费下载链接】clawPDF Open Source Virtual (Network) Printer for Windows that allows you to create PDFs, OCR text, and print images, with advanced features usually available only in enterprise s…...
告别手动造数据!用JMeter JSR223预处理程序+Groovy脚本,5分钟搞定接口签名和AES加密
告别手动造数据!用JMeter JSR223预处理程序Groovy脚本,5分钟搞定接口签名和AES加密 性能测试工程师最头疼的莫过于每次执行测试前,都要手动计算接口签名、拼接参数、加密敏感数据。这种重复性工作不仅耗时耗力,还容易出错。想象一…...
如何将网页转化为可编辑设计稿?3大核心场景与实现方案
如何将网页转化为可编辑设计稿?3大核心场景与实现方案 【免费下载链接】figma-html Convert any website to editable Figma designs 项目地址: https://gitcode.com/gh_mirrors/fi/figma-html 你是否曾遇到过看到优秀网页设计却无法直接复用的困境ÿ…...
2025_NIPS_CELLVERSE: Do Large Language Models Really Understand Cell Biology?
一、文章主要内容总结 该研究聚焦于大语言模型(LLMs)在细胞生物学领域的应用能力评估,核心贡献是构建了首个统一的语言中心型基准数据集CELLVERSE,并通过系统实验揭示了LLMs在单细胞分析任务中的表现与局限: 背景与问题:现有单细胞分析方法存在缺乏统一性(需为不同多组…...
海外SEO优化有哪些注意事项
海外SEO优化有哪些注意事项 在全球化的今天,越来越多的企业意识到海外市场的重要性,而海外SEO优化成为了其数字营销策略中的重要组成部分。海外SEO优化并非简单地将国内SEO策略直接应用到国外市场就能顺利实现。在这篇文章中,我们将探讨海外…...
2024最新版微信聊天记录提取工具部署指南:永久保存+数据分析全流程
2024最新版微信聊天记录提取工具部署指南:永久保存数据分析全流程 【免费下载链接】WeChatMsg 提取微信聊天记录,将其导出成HTML、Word、CSV文档永久保存,对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Trendi…...
提高网站权重的SEO技巧有哪些
提高网站权重的SEO技巧有哪些 在当今的数字化时代,网站的权重直接影响着它在搜索引擎上的排名。提升网站权重不仅能吸引更多的流量,还能提高用户的参与度和转化率。提高网站权重的SEO技巧有哪些呢?本文将从多个方面详细探讨,帮助…...
Listen1音乐聚合工具:打破平台壁垒的无缝听歌解决方案
Listen1音乐聚合工具:打破平台壁垒的无缝听歌解决方案 【免费下载链接】listen1_chrome_extension one for all free music in china (chrome extension, also works for firefox) 项目地址: https://gitcode.com/gh_mirrors/li/listen1_chrome_extension 你…...
