使用www.example.com核心创建另一个Web API的代理Asp.net

gblwokeq  于 2023-02-20  发布在  .NET
关注(0)|答案(8)|浏览(199)

我正在开发一个ASP .NetCoreWeb应用程序,在这个应用程序中,我需要创建一种到另一个(外部)Web服务的“身份验证代理”。
我所说的身份验证代理是指我将通过Web应用的特定路径接收请求,并且必须检查这些请求的标头以查找我之前发布的身份验证令牌,然后将具有相同请求字符串/内容的所有请求重定向到外部Web API,应用将通过HTTP Basic auth使用该API进行身份验证。
这是整个过程的伪代码

  • 客户端通过对我之前发送给他的唯一URL进行POST来请求令牌
  • 我的应用程序向他发送一个唯一令牌以响应此POST
  • 客户端向我的应用的特定URL(例如/extapi)发出GET请求,并在HTTP标头中添加auth-token
  • 我的应用程序收到请求,检查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类,并且是单例,并且具有http://someexternalapp.com/api/BaseAddress
此外,是否有比手动更简单的令牌创建/令牌检查方法?

8cdiaqws

8cdiaqws1#

如果有人感兴趣的话,我拿了Microsoft.AspNetCore.Proxy代码,用中间件做了一点改进。
点击这里查看:https://github.com/twitchax/AspNetCore.Proxy. NuGet在此获取:微软存档了这篇文章中提到的另一个,我计划对这个项目的任何问题做出回应。
基本上,它使反向代理另一个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}");
}
a9wyjsp7

a9wyjsp72#

我最终实现了一个受a project in Asp.Net's GitHub启发的代理中间件。
它基本上实现了一个中间件,该中间件读取接收到的请求,从中创建一个副本并将其发送回已配置的服务,从服务读取响应并将其发送回调用者。

jvlzgdj9

jvlzgdj93#

这篇文章讨论了用C#或ASP.NET核心编写一个简单的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 new EmptyResult();
        }

并尝试访问它。它将被代理到google.com

r9f1avp5

r9f1avp54#

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

在我的例子中,如果没有我的修改,原始头(例如带有承载令牌的授权头)将不会被添加。

lx0bsm1f

lx0bsm1f5#

我很幸运地使用了twitchax's AspNetCore.Proxy NuGet package,但是使用twitchax's answer中所示的ProxyRoute方法无法使它工作(很可能是我的错误)。
相反,我在Statup.cs Configure()方法中定义了Map,类似于下面的代码。

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

lmyy7pcs6#

在James Lawruk的答案https://stackoverflow.com/a/54149906/6596451的基础上使用twitchax Proxy属性,我也收到了一个404错误,直到我在ProxyRoute属性中指定了完整的路由。我的静态路由在一个单独的控制器中,而控制器路由的相对路径不起作用。
这起了作用:

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

希望这对某人有帮助!

w80xi6nr

w80xi6nr7#

Twitchax的答案似乎是目前最好的解决方案。在研究这个问题时,我发现微软正在开发一个更强大的解决方案,它正好适合OP试图解决的问题。
回购:https://github.com/microsoft/reverse-proxy
文章预览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的preview 4(或更高版本
更具体地说,他们的一个示例应用程序实现了身份验证(就OP的初衷而言)https://github.com/microsoft/reverse-proxy/blob/master/samples/ReverseProxy.Auth.Sample/Startup.cs

mtb9vblg

mtb9vblg8#

下面是Proxy library for ASP.NET Core的基本实现:
这并不实现授权,但是对于寻找一个简单的ASP.NET核心反向代理的人来说可能很有用。我们只在开发阶段使用它。

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();
        }
    }
}

相关问题