簡體   English   中英

使用 Asp.net 核心創建到另一個 Web api 的代理

[英]Creating a proxy to another web api with Asp.net core

我正在開發一個 ASP.Net Core Web 應用程序,我需要為另一個(外部)Web 服務創建一種“身份驗證代理”。

我所說的身份驗證代理的意思是,我將通過我的 Web 應用程序的特定路徑接收請求,並且必須檢查這些請求的標頭以獲取我之前發布的身份驗證令牌,然后使用相同的請求字符串/內容到外部 Web API,我的應用程序將通過 HTTP 基本身份驗證進行身份驗證。

這是偽代碼的整個過程

  • 客戶端通過向我之前發送給他的唯一 URL 發送 POST 請求令牌
  • 我的應用程序向他發送了一個唯一的令牌以響應此 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類實例化別的地方,是一個獨立的,並與BaseAddresshttp://someexternalapp.com/api/

此外,是否有比手動進行令牌創建/令牌檢查更簡單的方法?

如果有人感興趣,我采用了 Microsoft.AspNetCore.Proxy 代碼,並使用中間件使其更好一些。

在這里查看: https : //github.com/twitchax/AspNetCore.Proxy NuGet 在這里: https : //www.nuget.org/packages/AspNetCore.Proxy/ 微軟存檔了這篇文章中提到的另一個,我計划回應這個項目的任何問題。

基本上,它允許您在采用帶有 args 的路由並計算代理地址的方法上使用屬性,從而使反向代理另一個 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}");
}

我最終實現了一個受Asp.Net 的 GitHub 項目啟發的代理中間件。

它基本上實現了一個中間件,它讀取收到的請求,從中創建一個副本並將其發送回配置的服務,讀取服務的響應並將其發送回調用者。

這篇文章討論了在 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

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

一個不錯的反向代理中間件實現也可以在這里找到: 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());

在我的情況下,如果沒有我的修改,則不會添加原始標頭(例如,帶有不記名令牌的授權標頭)。

我很幸運地使用了twitchax 的 AspNetCore.Proxy 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 屬性中指定了完整路由。 我在一個單獨的控制器中有我的靜態路由,而控制器路由的相對路徑不起作用。

這有效:

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

希望這可以幫助某人!

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

Twitchax 的回答似乎是目前最好的解決方案。 在對此進行研究時,我發現 Microsoft 正在開發一種更強大的解決方案,該解決方案適合 OP 試圖解決的確切問題。

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

預覽版 1 的文章(他們實際上剛剛發布了前版 2): https : //devblogs.microsoft.com/dotnet/introducing-yarp-preview-1/

從文章...

YARP 是一個創建反向代理服務器的項目。 當我們注意到 Microsoft 內部團隊提出的一系列問題時,他們要么為他們的服務構建反向代理,要么一直在詢問 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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM