一个通用的居于 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 提供商的实际需求、疑问或问题,请随时在评论区留言,我们将一起探讨解决方案! 🌟

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/945009.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

小程序配置文件 —— 15 页面配置

页面配置 小程序的页面配置&#xff0c;也称为局部配置&#xff0c;每一个小程序页面也可以使用自己的 .json 文件来对页面的窗口表现进行配置&#xff1b; 需要注意的是&#xff1a;页面配置文件的属性和全局配置文件中的 window 属性几乎一致&#xff0c;只不过这里不需要额…

【从零开始入门unity游戏开发之——C#篇37】进程、线程和C# 中实现多线程有多种方案

文章目录 进程、线程和C#多线程一、进程的基本概念二、线程的基本概念三、C#中的多线程1、为什么需要多线程&#xff1f;2、*C# 中如何实现多线程**2.1 **使用 Thread 类**&#xff08;1&#xff09;示例&#xff08;2&#xff09;线程休眠&#xff08;3&#xff09;设置为后台…

评分模型在路网通勤习惯分析中的应用——提出问题(1)

1、问题的由来、目标和意义 最近一段时间和公司其它业务部门讨论时&#xff0c;发现一个有趣的交通路网问题&#xff0c;车辆从S点行驶到V点共用时40分钟&#xff0c;这段时间内路网中的卡口摄像头识别到了车辆通过的信息。如下图所示&#xff1a; 设计师需要通过这些有限的路…

机器学习DAY7: 特征工程和特征选择(数据预处理)(完)

本文通过特征提取、特征转换、特征选择三个过程介绍数据预处理方法&#xff0c;特征提取将原始数据转换为适合建模的特征&#xff0c;特征转换将数据进行变换以提高算法的准确性&#xff0c;特征选择用来删除无用的特征。 知识点 特征提取特征转换特征选择 本次实验的一些示…

【Unity3D】Jobs、Burst并行计算裁剪Texture3D物体

版本&#xff1a;Unity2019.4.0f1 PackageManager下载Burst插件(1.2.3版本) 利用如下代码&#xff0c;生成一个Texture3D资源&#xff0c;它只能脚本生成&#xff0c;是一个32*32*32的立方体&#xff0c;导出路径记得改下&#xff0c;不然报错。 using UnityEditor; using Uni…

紫光同创-盘古200pro+开发板

本原创文章由深圳市小眼睛科技有限公司创作&#xff0c;版权归本公司所有&#xff0c;如需转载&#xff0c;需授权并注明出处&#xff08;www.meyesemi.com) 一、开发系统介绍 开发系统概述 MES2L676-200HP 开发板采用紫光同创 logos2 系列 FPGA&#xff0c;型号&#xff1a;…

【后端】LNMP环境搭建

长期更新各种好文&#xff0c;建议关注收藏&#xff01; 本文近期更新完毕。 LNMPlinuxnginxmysqlphp 需要的资源 linux服务器 web服务软件nginx 对应的语言编译器代码文件 数据库mysql安装 tar.gz包或者命令行安装 进入root&#xff1a; sodu 或su mkdir path/{server,soft}…

VSCode设置Playwright教程

1.安装扩展 打开VS Code&#xff0c;在扩展—>搜索"Playwright Test for VSCode"&#xff0c;点击安装 按快捷键CommandShiftP&#xff0c;输入install playwright&#xff0c;点击安装Playwright 安装成功会有如下提示 2.调试脚本 打开tests/example.spec.ts文…

RK3566和Robo_C的EMC防护设计细节

USB部分的防护细节&#xff1a; ROBO C的USB接口&#xff1a; PF级别的电容滤波&#xff1a; TVS电容&#xff08;TVS Capacitor&#xff09;&#xff1a;用于与TVS二极管配合&#xff0c;保护电路免受瞬态电压冲击。电容一般较小&#xff0c;通常为几十皮法&#xff08;pF&am…

MicroDiffusion——采用新的掩码方法和改进的 Transformer 架构,实现了低预算的扩散模型

介绍 论文地址&#xff1a;https://arxiv.org/abs/2407.15811 现代图像生成模型擅长创建自然、高质量的内容&#xff0c;每年生成的图像超过十亿幅。然而&#xff0c;从头开始训练这些模型极其昂贵和耗时。文本到图像&#xff08;T2I&#xff09;扩散模型降低了部分计算成本&a…

使用 Three.js 创建一个 3D 人形机器人仿真系统

引言 在这篇文章中&#xff0c;我们将探讨如何使用 Three.js 创建一个简单但有趣的 3D 人形机器人仿真系统。这个机器人可以通过键盘控制进行行走和转向&#xff0c;并具有基本的动画效果。 技术栈 HTML5Three.jsJavaScript 实现步骤 1. 基础设置 首先&#xff0c;我们需要…

【c++高阶DS】最小生成树

&#x1f525;个人主页&#xff1a;Quitecoder &#x1f525;专栏&#xff1a;c笔记仓 目录 01.最小生成树Kruskal算法Prim算法 01.最小生成树 连通图中的每一棵生成树&#xff0c;都是原图的一个极大无环子图&#xff0c;即&#xff1a;从其中删去任何一条边&#xff0c;生成…

自学记录鸿蒙API 13:实现人脸比对Core Vision Face Comparator

完成了文本识别和人脸检测的项目后&#xff0c;我发现人脸比对是一个更有趣的一个小技术玩意儿。我决定整一整&#xff0c;也就是对HarmonyOS Next最新版本API 13中的Core Vision Face Comparator API的学习&#xff0c;这项技术能够对人脸进行高精度比对&#xff0c;并给出相似…

2024/12/29 黄冈师范学院计算机学院网络工程《路由期末复习作业一》

一、选择题 1.某公司为其一些远程小站点预留了网段 172.29.100.0/26&#xff0c;每一个站点有10个IP设备接到网络&#xff0c;下面那个VLSM掩码能够为该需求提供最小数量的主机数目 &#xff08; &#xff09; A./27 B./28 C./29 D./30 -首先审题我们需要搞清楚站点与网…

redis cluster集群

华子目录 什么是redis集群redis cluster的体系架构什么是数据sharding&#xff1f;什么是hash tag集群中删除或新增节点&#xff0c;数据如何迁移&#xff1f;redis集群如何使用gossip通信?定义meet信息ping消息pong消息fail消息&#xff08;不是用gossip协议实现的&#xff0…

PrimeVue菜单模块(Menu),看api的重要性

以下是对PrimeVue菜单模块&#xff08;Menu&#xff09;的API属性的中文详解&#xff1a; 一、整体概述 PrimeVue的菜单&#xff08;Menu&#xff09;是一个支持动态和静态定位的导航/命令组件&#xff0c;其API通过定义一些辅助的属性&#xff08;props&#xff09;、事件等&…

STM32中断详解

STM32中断详解 NVIC 中断系统中断向量表相关寄存器中断优先级中断配置 外部中断实验EXTI框图外部中断/事件线映射中断步骤初始化代码实现 定时器中断通用定时器相关功能标号1&#xff1a;时钟源标号 2&#xff1a;控制器标号 3&#xff1a;时基单元 代码实现 NVIC 中断系统 STM…

从零开始开发纯血鸿蒙应用之逻辑封装

从零开始开发纯血鸿蒙应用 一、前言二、逻辑封装的原则三、实现 FileUtil1、统一的存放位置2、文件的增删改查2.1、文件创建与文件保存2.2、文件读取2.2.1、读取内部文件2.2.2、读取外部文件 3、文件删除 四、总结 一、前言 应用的动态&#xff0c;借助 UI 响应完成&#xff0…

《机器学习》——线性回归模型

文章目录 线性回归模型简介一元线性回归模型多元线性回归模型误差项分析一元线性模型实例完整代码 多元线性模型实例完整代码 线性回归模型简介 线性回归是利用数理统计中回归分析&#xff0c;来确定两种或两种以上变量间相互依赖的定量关系的一种统计分析方法。 相关关系&…

【深度学习环境】NVIDIA Driver、Cuda和Pytorch(centos9机器,要用到显示器)

文章目录 一 、Anaconda install二、 NIVIDIA driver install三、 Cuda install四、Pytorch install 一 、Anaconda install Step 1 Go to the official website: https://www.anaconda.com/download Input your email and submit. Step 2 Select your version, and click i…