使用 Asp.net 核心创建另一个 web API 的代理

我正在开发一个 ASP.Net 核心 Web 应用程序,我需要创建一种“认证代理”到另一个(外部) Web 服务。

我所说的身份验证代理是指我将通过我的 web 应用程序的特定路径接收请求,并且必须检查这些请求的头部以获得我之前发出的身份验证令牌,然后将所有具有相同请求字符串/内容的请求重定向到一个外部 web API,我的应用程序将通过 HTTP Basic 认证进行身份验证。

下面是伪代码的整个过程

  • 客户端通过对我之前发送给他的唯一 URL 进行 POST 来请求令牌
  • 我的应用程序给他发送了一个独特的令牌来回应这个 POST
  • 客户端向我的应用程序的特定 URL (比如 /extapi)发出 GET 请求,并在 HTTP 头中添加认证令牌
  • 我的应用程序收到请求,检查认证令牌是否存在并有效
  • 我的应用程序对外部 web API 执行同样的请求,并使用 BASIC 认证对请求进行身份验证
  • 我的应用程序接收来自请求的结果并将其发送回客户端

这是我目前掌握的情况。它似乎工作得很好,但我想知道这是否真的应该这样做,或者如果没有一个更优雅或更好的解决方案吗?从长远来看,这种解决方案是否会对扩展应用程序造成问题?

[HttpGet]
public async Task GetStatement()
{
//TODO check for token presence and reject if issue


var queryString = Request.QueryString;
var response = await _httpClient.GetAsync(queryString.Value);
var content = await response.Content.ReadAsStringAsync();


Response.StatusCode = (int)response.StatusCode;
Response.ContentType = response.Content.Headers.ContentType.ToString();
Response.ContentLength = response.Content.Headers.ContentLength;


await Response.WriteAsync(content);
}


[HttpPost]
public async Task PostStatement()
{
using (var streamContent = new StreamContent(Request.Body))
{
//TODO check for token presence and reject if issue


var response = await _httpClient.PostAsync(string.Empty, streamContent);
var content = await response.Content.ReadAsStringAsync();


Response.StatusCode = (int)response.StatusCode;


Response.ContentType = response.Content.Headers.ContentType?.ToString();
Response.ContentLength = response.Content.Headers.ContentLength;


await Response.WriteAsync(content);
}
}

_httpClient是一个在其他地方实例化的 HttpClient类,是一个单例类,具有 http://someexternalapp.com/api/BaseAddress

另外,是否有比手动创建/检查令牌更简单的方法?

104838 次浏览

我最终实现了一个受 Asp.Net 的 GitHub 中的一个项目启发的代理中间件。

它基本上实现了一个中间件,该中间件读取接收到的请求,从中创建一个副本并将其发送回配置的服务,从服务中读取响应并将其发送回调用者。

下面是 ASP.NET 核心的代理库的一个基本实现:

这并不实现授权,但是对于寻找具有 ASP.NET Core 的简单反向代理的人可能很有用。我们只在开发阶段使用它。

using System;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;


namespace Sample.Proxy
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(options =>
{
options.AddDebug();
options.AddConsole(console =>
{
console.IncludeScopes = true;
});
});


services.AddProxy(options =>
{
options.MessageHandler = new HttpClientHandler
{
AllowAutoRedirect = false,
UseCookies = true
};


options.PrepareRequest = (originalRequest, message) =>
{
var host = GetHeaderValue(originalRequest, "X-Forwarded-Host") ?? originalRequest.Host.Host;
var port = GetHeaderValue(originalRequest, "X-Forwarded-Port") ?? originalRequest.Host.Port.Value.ToString(CultureInfo.InvariantCulture);
var prefix = GetHeaderValue(originalRequest, "X-Forwarded-Prefix") ?? originalRequest.PathBase;


message.Headers.Add("X-Forwarded-Host", host);
if (!string.IsNullOrWhiteSpace(port)) message.Headers.Add("X-Forwarded-Port", port);
if (!string.IsNullOrWhiteSpace(prefix)) message.Headers.Add("X-Forwarded-Prefix", prefix);


return Task.FromResult(0);
};
});
}


private static string GetHeaderValue(HttpRequest request, string headerName)
{
return request.Headers.TryGetValue(headerName, out StringValues list) ? list.FirstOrDefault() : null;
}


public void Configure(IApplicationBuilder app)
{
app.UseWebSockets()
.Map("/api", api => api.RunProxy(new Uri("http://localhost:8833")))
.Map("/image", api => api.RunProxy(new Uri("http://localhost:8844")))
.Map("/admin", api => api.RunProxy(new Uri("http://localhost:8822")))
.RunProxy(new Uri("http://localhost:8811"));
}


public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseIISIntegration()
.UseStartup<Startup>()
.Build();


host.Run();
}
}
}

如果有人感兴趣,我采用了微软的 AspNetCore.Proxy 代码,并用中间件做得更好一些。

看这里: https://github.com/twitchax/AspNetCore.Proxy。NuGet here: https://www.nuget.org/packages/AspNetCore.Proxy/.微软存档了这篇文章中提到的另一个问题,我打算回应这个项目中的任何问题。

基本上,它使得反向代理另一个 Web 服务器变得更加容易,因为它允许您在使用 args 路由并计算代理地址的方法上使用属性。

[ProxyRoute("api/searchgoogle/{query}")]
public static Task<string> SearchGoogleProxy(string query)
{
// Get the proxied address.
return Task.FromResult($"https://www.google.com/search?q={query}");
}

我有运气使用 Twitchax 的 AspNetCore 代理 NuGet 软件包,但无法得到它的工作使用 Twitchax 的回答中所示的 ProxyRoute方法。(这很可能是我这边的一个错误。)

相反,我在 Statup.cs Configure ()方法中定义了类似于下面代码的映射。

app.UseProxy("api/someexternalapp-proxy/{arg1}", async (args) =>
{
string url = "https://someexternalapp.com/" + args["arg1"];
return await Task.FromResult<string>(url);
});

借助 James Lawruk 的答案 https://stackoverflow.com/a/54149906/6596451使 twitchax Proxy 属性工作,我也得到了一个404错误,直到我在 ProxyRoute 属性中指定了完整的路由。我将静态路由放在一个单独的控制器中,而 Controller 路由的相对路径无法工作。

这个方法奏效了:

public class ProxyController : Controller
{
[ProxyRoute("api/Proxy/{name}")]
public static Task<string> Get(string name)
{
return Task.FromResult($"http://www.google.com/");
}
}

这不是:

[Route("api/[controller]")]
public class ProxyController : Controller
{
[ProxyRoute("{name}")]
public static Task<string> Get(string name)
{
return Task.FromResult($"http://www.google.com/");
}
}

希望这对谁有帮助!

这里还有一个很好的反向代理中间件实现: https://auth0.com/blog/building-a-reverse-proxy-in-dot-net-core/

注意,我在这里替换了这一行

requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());

requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToString());

在我的示例中,如果没有修改,则不会添加原始头(例如,带有持有标记的授权头)。

这篇文章讨论了如何用 C # 或 ASP.NET Core 编写一个简单的 HTTP 代理逻辑。并允许您的项目将请求代理到任何其他 URL。它与为 ASP.NET 核心项目部署代理服务器无关。

在项目的任何位置添加以下代码。

        public static HttpRequestMessage CreateProxyHttpRequest(this HttpContext context, Uri uri)
{
var request = context.Request;


var requestMessage = new HttpRequestMessage();
var requestMethod = request.Method;
if (!HttpMethods.IsGet(requestMethod) &&
!HttpMethods.IsHead(requestMethod) &&
!HttpMethods.IsDelete(requestMethod) &&
!HttpMethods.IsTrace(requestMethod))
{
var streamContent = new StreamContent(request.Body);
requestMessage.Content = streamContent;
}


// Copy the request headers
foreach (var header in request.Headers)
{
if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()) && requestMessage.Content != null)
{
requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
}
}


requestMessage.Headers.Host = uri.Authority;
requestMessage.RequestUri = uri;
requestMessage.Method = new HttpMethod(request.Method);


return requestMessage;
}

这个方法隐藏用户将 HttpContext.Request发送到一个可重用的 HttpRequestMessage。因此您可以将这个消息发送到目标服务器。

在您的目标服务器响应之后,您需要将响应的 HttpResponseMessage复制到 HttpContext.Response,这样用户的浏览器就可以得到它。

        public static async Task CopyProxyHttpResponse(this HttpContext context, HttpResponseMessage responseMessage)
{
if (responseMessage == null)
{
throw new ArgumentNullException(nameof(responseMessage));
}


var response = context.Response;


response.StatusCode = (int)responseMessage.StatusCode;
foreach (var header in responseMessage.Headers)
{
response.Headers[header.Key] = header.Value.ToArray();
}


foreach (var header in responseMessage.Content.Headers)
{
response.Headers[header.Key] = header.Value.ToArray();
}


// SendAsync removes chunking from the response. This removes the header so it doesn't expect a chunked response.
response.Headers.Remove("transfer-encoding");


using (var responseStream = await responseMessage.Content.ReadAsStreamAsync())
{
await responseStream.CopyToAsync(response.Body, _streamCopyBufferSize, context.RequestAborted);
}
}

现在准备工作已经完成:

    private readonly HttpClient _client;


public YourController()
{
_client = new HttpClient(new HttpClientHandler()
{
AllowAutoRedirect = false
});
}


public async Task<IActionResult> Rewrite()
{
var request = HttpContext.CreateProxyHttpRequest(new Uri("https://www.google.com"));
var response = await _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, HttpContext.RequestAborted);
await HttpContext.CopyProxyHttpResponse(response);
return Ok();
}

然后尝试访问它,它会被代理到 google.com

![](/uploads/img-f2dd7ca2-79e4-4846-a7d0-6685f9b33ff4.png)

Twitchax 的回答似乎是目前最好的解决方案。在研究这个问题时,我发现微软正在开发一个更加健壮的解决方案,这个解决方案恰好适合 OP 试图解决的问题。

回购: https://github.com/microsoft/reverse-proxy

Preview 1的文章(他们实际上刚刚发布了 Preview 2) : https://devblogs.microsoft.com/dotnet/introducing-yarp-preview-1/

来自文章..。

YARP 是一个创建反向代理服务器的项目。当我们注意到来自微软内部团队的一系列问题时,这些团队要么正在为他们的服务构建一个反向代理,要么正在询问构建一个反向代理的 API 和技术,所以我们决定把他们聚集在一起,开发一个共同的解决方案,这就是 YARP。

YARP 是一个反向代理工具包,用于在。NET 使用来自 ASP.NET 和。NET.YARP 的关键区别在于,它被设计成易于定制和调整,以满足每个部署场景的特定需求。YARP 插入 ASP.NET 管道以处理传入请求,然后拥有自己的子管道以执行将请求代理到后端服务器的步骤。客户可以添加其他模块,或根据需要替换库存模块。
...
YARP 与这两者都有合作。NET Core 3.1或。NET 5预览4(或更高版本)。下载预览版4(或更高版本)。NET 5 SDK 来自 < a href = “ https://dotnet.microsoft.com/download/dotnet/5.0”rel = “ nofollow norefrer”> https://dotnet.microsoft.com/download/dotnet/5.0

更具体地说,他们的一个示例应用程序实现了身份验证(至于 OP 的原始意图) Https://github.com/microsoft/reverse-proxy/blob/master/samples/reverseproxy 授权样本/Startup.cs