Создание прокси-сервера для другого веб-приложения с помощью ядра 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 of http://someexternalapp.com/api/

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

Ответ 1

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

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

Ответ 2

Если кому-то интересно, я взял код 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}");
}

Ответ 3

В ответ на ответ Джеймса Лаврука fooobar.com/questions/458101/... чтобы заставить работать атрибут proxy 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/");
    }
}

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

Ответ 4

Вот базовая реализация Прокси-библиотека для ядра 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();
        }
    }
}

Ответ 5

Мне посчастливилось использовать пакет NuGet twitchax AspNetCore.Proxy, но я не смог заставить его работать, используя метод 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);
});

Ответ 6

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

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