programing

Asp.net core를 사용하여 다른 웹 API에 대한 프록시 만들기

javajsp 2023. 7. 13. 20:39

Asp.net core를 사용하여 다른 웹 API에 대한 프록시 만들기

다른(외부) 웹 서비스에 대한 일종의 "인증 프록시"를 만들어야 하는 ASP.Net Core 웹 응용 프로그램을 개발하고 있습니다.

인증 프록시를 의미하는 것은 웹 앱의 특정 경로를 통해 요청을 수신하고 이전에 발급한 인증 토큰에 대해 해당 요청의 헤더를 확인해야 한다는 것입니다.그런 다음 동일한 요청 문자열/내용을 가진 모든 요청을 HTTP Basic 인증을 통해 내 앱이 인증할 외부 웹 API로 리디렉션합니다.

여기 의사 코드로 된 모든 과정이 있습니다.

  • 클라이언트가 내가 이전에 보낸 고유 URL에 POST를 만들어 토큰을 요청합니다.
  • 내 앱은 이 POST에 대한 응답으로 그에게 고유 토큰을 보냅니다.
  • 는 내. 를 들어, "GET"입니다./extapiHTTP 헤더에 인증 토큰을 추가합니다.
  • 앱이 요청을 받고 인증 토큰이 있고 유효한지 확인하는 경우
  • 내 앱이 외부 웹 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);
    }
}

_httpClienta가 되는 것HttpClient수업은 다른 곳에서 인스턴스화되고 싱글톤이 되고 그리고.BaseAddresshttp://someexternalapp.com/api/

또한 토큰 생성/토큰 검사를 수동으로 하는 것보다 더 간단한 방법이 있습니까?

관심 있는 사람이 있으면 마이크로소프트를 선택했습니다.AsNetCore.프록시 코드를 사용하여 미들웨어를 조금 개선했습니다.

여기를 확인해 보세요: https://github.com/twitchax/AspNetCore.Proxy .NuGet here: https://www.nuget.org/packages/AspNetCore.Proxy/ .마이크로소프트는 이 게시물에 언급된 다른 하나를 보관했으며, 저는 이 프로젝트에 대한 모든 문제에 대응할 계획입니다.

기본적으로 다른 웹 서버의 역방향 프록시를 사용하면 인수가 있는 경로를 사용하고 프록시 주소를 계산하는 메서드에 속성을 사용할 수 있습니다.

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

이 게시물은 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 new EmptyResult();
        }

그리고 접근해보세요.google.com 에 프록시됩니다.

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

저는 Asp의 프로젝트에서 영감을 얻은 프록시 미들웨어를 구현하게 되었습니다.넷의 깃허브.

기본적으로 요청을 읽고, 요청에서 복사본을 생성하여 구성된 서비스로 다시 보내고, 서비스의 응답을 읽고, 호출자에게 다시 보내는 미들웨어를 구현합니다.

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의 AsNetCore를 사용한 운이 좋았습니다.프록시 NuGet 패키지를 사용할 수 없지만ProxyRoutetwitchax의 답변에 표시된 방법. (내 쪽에서는 쉽게 실수할 수 있었습니다.)

대신 아래 코드와 유사하게 Statup.cs Configure() 메서드에서 매핑을 정의했습니다.

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

twitchax Proxy 속성이 작동하도록 하기 위해 James Lawruk의 답변 https://stackoverflow.com/a/54149906/6596451 을 피기백하여 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/");
    }
}

이것이 누군가에게 도움이 되기를 바랍니다!

트위치악스의 답변이 현재로서는 최선의 해결책인 것 같습니다.이것을 조사하면서, 저는 마이크로소프트가 OP가 해결하려고 했던 정확한 문제에 맞는 더 강력한 솔루션을 개발하고 있다는 것을 알게 되었습니다.

담당자: https://github.com/microsoft/reverse-proxy

프리뷰 1의 기사 (실제로 그들은 이전 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 미리 보기 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 .

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

언급URL : https://stackoverflow.com/questions/42000362/creating-a-proxy-to-another-web-api-with-asp-net-core