Создание прокси для другого веб-API с ядром Asp.net

Я разрабатываю веб-приложение ASP.Net Core, в котором мне нужно создать своего рода «прокси-сервер аутентификации» для другой (внешней) веб-службы.

Под прокси-сервером аутентификации я подразумеваю то, что я буду получать запросы через определенный путь к моему веб-приложению, и мне придется проверять заголовки этих запросов на наличие токена аутентификации, который я выдал ранее, а затем перенаправлять все запросы с помощью та же строка запроса / контент для внешнего веб-API, с которым мое приложение будет аутентифицироваться через HTTP Basic auth.

Вот весь процесс в псевдокоде

  • Клиент запрашивает токен, отправляя POST на уникальный URL-адрес, который я отправил ему ранее.
  • Мое приложение отправляет ему уникальный токен в ответ на этот POST
  • Клиент делает запрос GET на определенный URL-адрес моего приложения, скажем /extapi, и добавляет токен аутентификации в заголовок HTTP.
  • Мое приложение получает запрос, проверяет, что токен аутентификации присутствует и действителен
  • Мое приложение выполняет тот же запрос к внешнему веб-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 классом, созданным где-то в другом месте, является синглтоном и имеет BaseAddressиз http://someexternalapp.com/api/

Кроме того, есть ли более простой подход к созданию / проверке токена, чем выполнение этого вручную?


person Gimly    schedule 02.02.2017    source источник
comment
Обратный прокси IIS   -  person Callum Linington    schedule 02.02.2017
comment
Но что, если вы не используете IIS? Я мог бы пойти по пути хостинга с использованием Kestrel на образе Docker или что-то в этом роде.   -  person Gimly    schedule 02.02.2017
comment
Вы можете использовать любой сервер в качестве обратного прокси. Так что запускайте экспресс-приложение с обратным прокси или любой другой популярный веб-сервер с обратным прокси ...   -  person Callum Linington    schedule 02.02.2017
comment
Я действительно не понимаю, как это будет реализовано. Как затем проверить токен аутентификации на обратном прокси-сервере?   -  person Gimly    schedule 02.02.2017
comment
Честно говоря, я не вижу проблем с вашим кодом, я бы просто абстрагировался. и убедитесь, что вы явно копируете все заголовки или значения строки запроса, чтобы защитить себя от эксплойтов.   -  person Callum Linington    schedule 02.02.2017
comment
вы можете попробовать летающий прокси NetCoreStack - github.com/NetCoreStack/Proxy   -  person Gencebay    schedule 11.08.2017


Ответы (8)


Если кому-то интересно, я взял код Microsoft.AspNetCore.Proxy и немного улучшил его с помощью промежуточного программного обеспечения.

Проверьте это здесь: https://github.com/twitchax/AspNetCore.Proxy. NuGet здесь: https://www.nuget.org/packages/AspNetCore.Proxy/. Microsoft заархивировала другой, упомянутый в этом посте, и я планирую ответить на любые вопросы по этому проекту.

По сути, это значительно упрощает обратное проксирование другого веб-сервера, позволяя использовать атрибуты в методах, которые выбирают маршрут с аргументами и вычисляют проксируемый адрес.

[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}");
}
person twitchax    schedule 05.12.2018
comment
Спасибо. Не удалось заставить работать атрибут ProxyRoute. Получил 404. Наверное, что-то делал не так. Успешно использовал метод UseProxy (), поэтому еще раз спасибо. - person James Lawruk; 11.01.2019
comment
Ознакомьтесь с решением ниже. Промежуточное ПО еще не принимает во внимание маршрут класса. Не стесняйтесь сообщать о проблеме! :) - person twitchax; 21.01.2019

В итоге я реализовал промежуточное ПО прокси, вдохновленное проектом в GitHub от Asp.Net.

Он в основном реализует промежуточное программное обеспечение, которое считывает полученный запрос, создает из него копию и отправляет ее обратно в настроенную службу, считывает ответ от службы и отправляет его обратно вызывающей стороне.

person Gimly    schedule 16.02.2017
comment
не могли бы вы поделиться своими инструментами своего промежуточного программного обеспечения? Если это возможно. Он сильно основан на .Net Core? Спасибо. - person Dmitriy; 28.11.2017
comment
@Dmitriy Нет, извините, я не могу поделиться реализацией, поскольку она является частью программы с закрытым исходным кодом. Но это в основном тот же код, что и в вопросе, реализованный как промежуточное ПО. Проверьте github.com/aspnet/Proxy/ blob / dev / src / Microsoft.AspNetCore.Proxy /, чтобы получить представление о том, как запустить промежуточное ПО. - person Gimly; 28.11.2017

В этом посте рассказывается о написании простой логики HTTP-прокси на C # или ASP.NET Core. И позволяя вашему проекту проксировать запрос на любой другой 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)

person Anduin    schedule 12.06.2020
comment
Проблема в том, что изображения и скрипты с относительными путями не загружаются правильно. - person Renato Sanhueza; 30.03.2021

Хорошую реализацию промежуточного программного обеспечения обратного прокси-сервера также можно найти здесь: 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());

Исходные заголовки (например, заголовок авторизации с токеном-носителем) не будут добавлены без моей модификации в моем случае.

person baumgarb    schedule 25.02.2019
comment
Этот прокси также не добавляет строку запроса к запросам, которые он передает через прокси. Я добавил это в BuildTargetUri, используя string query = request.QueryString.ToString(); - person Eddie; 31.01.2020

Мне посчастливилось использовать пакет NuGet AspNetCore.Proxy от Twitchax, но я не смог заставить его работать с помощью ProxyRoute метод, показанный в ответе twitchax. (С моей стороны это легко могло быть ошибкой.)

Вместо этого я определил отображение в методе Statup.cs Configure (), аналогичном приведенному ниже коду.

app.UseProxy("api/someexternalapp-proxy/{arg1}", async (args) =>
{
    string url = "https://someexternalapp.com/" + args["arg1"];
    return await Task.FromResult<string>(url);
});
person James Lawruk    schedule 11.01.2019
comment
Это была единственная конфигурация, которую вы сделали? - person BrunoMartinsPro; 04.12.2019
comment
неважно, я изменил api / someexternalapp-proxy / {arg1} на api / someexternalapp-proxy / {** catchall} и добавил в ConfigureServices services.AddProxies (); и теперь он работает! - person BrunoMartinsPro; 04.12.2019

Поддержка ответа Джеймса Лоурука https://stackoverflow.com/a/54149906/6596451 для получения атрибута прокси twitchax чтобы работать, я также получал ошибку 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/");
    }
}

Надеюсь, это кому-то поможет!

person Daniel Plainview    schedule 11.01.2019
comment
Аааа, приятно. Не стесняйтесь сообщить об этом. Должно быть легко исправить. - person twitchax; 21.01.2019

Вот базовая реализация библиотеки прокси для 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();
        }
    }
}
person Kerem Demirer    schedule 06.01.2018
comment
Можете ли вы обновить пакет nuget, этот код не работает с опубликованной версией 0.2.0 - person Kugel; 18.01.2018
comment
Не уверен, что мне что-то не хватает в этом коде, но я не могу разрешить services.AddProxy(...). Я использую Microsoft.AspNetCore.Proxy v0.2.0. Кроме того, метод RunProxy не принимает Uri в качестве параметра. Какая версия использовалась для этого примера? - person Allan; 20.01.2018
comment
Я использовал nuget версии 0.2 из каналов предварительного просмотра: dotnet. myget.org/feed/aspnetcore-release/package/nuget/ - person Kerem Demirer; 26.01.2018
comment
У меня такая же проблема с @Allan. Есть какое-нибудь решение для этого? - person Bangyou; 07.03.2018
comment
Кажется, что SDK не поддерживает asp net core 2.1, когда я компилирую исходные коды. - person Bangyou; 07.03.2018
comment
То же самое. Не удается разрешить функцию services.AddProxy () с Microsoft.AspNetCore.Proxy v0.2.0 ... С марта 2018 года кто-нибудь может дать нам решение ?? - person amin89; 17.04.2018
comment
@ amin89 Имя метода было изменено на RunProxy. Неясно, является ли эта функция экспериментальной или нет. github.com/aspnet/Home/issues/2931 < / а> - person JPelletier; 29.05.2018
comment
@Allan Я нашел это, если тебе все еще нужно решение. - person amin89; 29.05.2018

Ответ Twitchax кажется лучшим решением на данный момент. Изучая это, я обнаружил, что Microsoft разрабатывает более надежное решение, которое точно соответствует той проблеме, которую OP пытался решить.

Репо: https://github.com/microsoft/reverse-proxy

Статья для предварительного просмотра 1 (на самом деле они только что выпустили предыдущий 2): https://devblogs.microsoft.com/dotnet/introduction-yarp-preview-1/

Из статьи ...

YARP - это проект по созданию обратного прокси-сервера. Все началось, когда мы заметили ряд вопросов от внутренних команд в Microsoft, которые либо создавали обратный прокси для своей службы, либо спрашивали об API и технологиях для его создания, поэтому мы решили собрать их всех вместе для работы над общим решением. , который стал YARP.

YARP - это набор инструментов обратного прокси для создания быстрых прокси-серверов в .NET с использованием инфраструктуры из ASP.NET и .NET. Ключевым отличием YARP является то, что его легко настраивать и настраивать в соответствии с конкретными потребностями каждого сценария развертывания. YARP подключается к конвейеру ASP.NET для обработки входящих запросов, а затем имеет собственный вспомогательный конвейер для выполнения шагов по проксированию запросов к внутренним серверам. Клиенты могут добавлять дополнительные модули или заменять стандартные модули по мере необходимости.
...
YARP работает с .NET Core 3.1 или .NET 5 preview 4 (или новее). Загрузите предварительную версию 4 (или более позднюю) пакета SDK для .NET 5 со страницы https://dotnet.microsoft.com/download/dotnet/5.0

В частности, одно из их примеров приложений реализует аутентификацию (как и для исходного намерения OP) https://github.com/microsoft/reverse-proxy/blob/master/samples/ReverseProxy.Auth.Sample/Startup.cs

person spencer741    schedule 29.06.2020
comment
ой @ spencer741, это довольно интересно и слишком легко настроить - person genuinefafa; 10.09.2020
comment
@genuinefafa Да, действительно ... определенно находится на ранней стадии. Пока не доверял бы с точки зрения безопасности или производительности. - person spencer741; 11.09.2020
comment
Похоже, сейчас это очень многообещающе. - person spencer741; 28.02.2021