【问题标题】:Creating a proxy to another web api with Asp.net core使用 Asp.net 核心创建另一个 Web api 的代理
【发布时间】:2017-06-19 09:57:34
【问题描述】:

我正在开发一个 ASP.Net Core Web 应用程序,我需要为另一个(外部)Web 服务创建一种“身份验证代理”。

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

这是整个过程的伪代码

  • 客户端通过 POST 到我之前发送给他的唯一 URL 来请求令牌
  • 我的应用程序向他发送了一个唯一令牌以响应此 POST
  • 客户端向我的应用程序的特定 URL 发出 GET 请求,例如 /extapi,并在 HTTP 标头中添加 auth-token
  • 我的应用收到请求,检查身份验证令牌是否存在且有效
  • 我的应用程序向外部 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 类并且是一个单例并且具有 BaseAddressof http://someexternalapp.com/api/

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

【问题讨论】:

  • IIS 反向代理
  • 但是如果您不在 IIS 上托管怎么办?我可能会在 Docker 映像或类似的东西上使用 Kestrel 进行托管。
  • 您可以使用任何服务器作为反向代理。因此,启动一个带有反向代理的快速应用程序或任何其他带有反向代理的流行 Web 服务器......
  • 我真的看不出这将如何实现。然后如何检查来自反向代理的身份验证令牌?
  • 老实说,我真的认为您的代码没有任何问题,我只是将其抽象出来。并确保您明确复制任何标头或查询字符串值以保护自己免受攻击。

标签: c# asp.net asp.net-core-mvc asp.net-core-webapi


【解决方案1】:

Twitchax 的回答似乎是目前最好的解决方案。在对此进行研究时,我发现 Microsoft 正在开发一种更强大的解决方案,该解决方案适合 OP 试图解决的确切问题。

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

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

来自文章...

YARP 是一个创建反向代理服务器的项目。当我们注意到微软内部团队提出的一系列问题时,他们要么正在为他们的服务构建反向代理,要么一直在询问构建一个反向代理的 API 和技术,因此我们决定让他们一起研究一个通用的解决方案,这已成为 YARP。

YARP 是一个反向代理工具包,用于使用 ASP.NET 和 .NET 的基础结构在 .NET 中构建快速代理服务器。 YARP 的关键区别在于它的设计易于定制和调整,以匹配每个部署场景的特定需求。 YARP 插入 ASP.NET 管道以处理传入请求,然后拥有自己的子管道来执行将请求代理到后端服务器的步骤。客户可以根据需要添加额外的模块或更换库存模块。
...
YARP 适用于 .NET Core 3.1 或 .NET 5 preview 4(或更高版本)。从https://dotnet.microsoft.com/download/dotnet/5.0下载 .NET 5 SDK 的预览版 4(或更高版本)

更具体地说,他们的一个示例应用程序实现了身份验证(至于 OP 的初衷) https://github.com/microsoft/reverse-proxy/blob/master/samples/ReverseProxy.Auth.Sample/Startup.cs

【讨论】:

  • 哦@spencer741,这很有趣,而且设置起来似乎太容易了
  • @genuinefafa 是的,确实......肯定处于早期阶段。从安全或性能的角度来看,目前还不会信任。
  • 现在看来很有前途。
【解决方案2】:

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

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

        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

【讨论】:

  • 这样做的问题是,具有相对路径的图像和脚本将无法正确加载。
【解决方案3】:

一个不错的反向代理中间件实现也可以在这里找到: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());

在我的情况下,如果没有我的修改,将不会添加原始标头(例如,带有不记名令牌的授权标头)。

【讨论】:

  • 此代理也不会将查询字符串添加到它代理的请求中。我在BuildTargetUri 中使用string query = request.QueryString.ToString(); 补充说
【解决方案4】:

依靠 James Lawruk 的回答 https://stackoverflow.com/a/54149906/6596451 来让 twitchax Proxy 属性起作用,在我在 ProxyRoute 属性中指定完整路由之前,我也遇到了 404 错误。我的静态路由位于单独的控制器中,而控制器路由的相对路径不起作用。

这行得通:

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/");
    }
}

希望这对某人有所帮助!

【讨论】:

  • 啊,很好。随意提交一个错误。应该很容易解决。
【解决方案5】:

我很幸运地使用了twitchax's AspNetCore.Proxy NuGet package,但无法使用twitchax's answer 中显示的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);
});

【讨论】:

  • 这是您做的唯一配置吗?
  • 没关系,我将“api/someexternalapp-proxy/{arg1}”更改为“api/someexternalapp-proxy/{**catchall}”并在 ConfigureServices 中添加“services.AddProxies();”它现在正在工作!
【解决方案6】:

如果有人有兴趣,我拿了 Microsoft.AspNetCore.Proxy 代码,并用中间件使它更好一点。

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

基本上,它允许您在方法上使用属性,这些方法采用带参数的路由并计算代理地址,从而使反向代理另一个 Web 服务器变得更加容易。

[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}");
}

【讨论】:

  • 谢谢。无法使 ProxyRoute 属性起作用。得到了 404。可能是我做错了什么。使用 UseProxy() 方法成功,再次感谢。
  • 查看下面的解决方案。中间件还没有考虑到类路由。随时提出问题! :)
【解决方案7】:

这是Proxy library for ASP.NET Core的基本实现:

这不会实现授权,但对于正在使用 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();
        }
    }
}

【讨论】:

  • 你能更新 nuget 包吗?此代码不适用于已发布的 0.2.0
  • 不确定我是否缺少此代码的某些内容,但我无法解析 services.AddProxy(...)。我正在使用 Microsoft.AspNetCore.Proxy v0.2.0。此外,RunProxy 方法不接受 Uri 作为参数。这个例子使用的是什么版本?
  • 我使用了来自预览提要的 v0.2 nuget:dotnet.myget.org/feed/aspnetcore-release/package/nuget/…
  • 我对@Allan 有同样的问题。有什么解决办法吗?
  • 我编译源码的时候SDK好像不支持asp net core 2.1。
【解决方案8】:

我最终实现了一个受a project in Asp.Net's GitHub 启发的代理中间件。

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

【讨论】:

  • 你能分享你的中间件实现吗?如果可能的话。它是否强烈基于.Net Core?谢谢。
  • @Dmitriy 不,很抱歉我不能分享这个实现,因为它是一个封闭源程序的一部分。但它与作为中间件实现的问题中的代码基本相同。检查github.com/aspnet/Proxy/blob/dev/src/Microsoft.AspNetCore.Proxy/… 文件以了解如何启动中间件。
猜你喜欢
  • 2017-01-30
  • 2021-06-25
  • 1970-01-01
  • 1970-01-01
  • 2020-01-24
  • 2014-03-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多