web-dev-qa-db-ja.com

Asp.netコアを使用して別のWeb APIへのプロキシを作成する

別の(外部)Webサービスへの一種の「認証プロキシ」を作成する必要があるASP.Net Core Webアプリケーションを開発しています。

認証プロキシとは、Webアプリの特定のパスを介してリクエストを受信し、以前に発行した認証トークンのリクエストのヘッダーを確認し、すべてのリクエストをアプリケーションがHTTP Basic認証を通じて認証する外部Web APIへの同じリクエスト文字列/コンテンツ。

擬似コードの全プロセスはここにあります

  • クライアントは、以前に送信した一意のURLにPOSTを作成してトークンを要求します
  • 私のアプリは、このPOSTへの応答として彼に一意のトークンを送信します
  • クライアントは/extapiと言うアプリの特定のURLにGETリクエストを行い、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);
    }
}

_httpClientHttpClientクラスであり、他のどこかでインスタンス化され、シングルトンであり、BaseAddressof http://someexternalapp.com/api/である

また、トークンの作成/トークンのチェックには、手動で行うよりも簡単な方法がありますか?

32
Gimly

Asp.NetのGitHubのプロジェクト に触発されたプロキシミドルウェアを実装することになりました。

基本的には、受信した要求を読み取り、それからコピーを作成して構成済みサービスに送信し、サービスから応答を読み取り、呼び出し元に送信するミドルウェアを実装します。

13
Gimly

誰かが興味を持っているなら、Microsoft.AspNetCore.Proxyコードを取得し、ミドルウェアで少し良くしました。

こちらをご覧ください: https://github.com/twitchax/AspNetCore.Proxy 。 NuGetここ: https://www.nuget.org/packages/AspNetCore.Proxy/ 。マイクロソフトは、この投稿で言及されている他の1つをアーカイブし、このプロジェクトの問題に対応する予定です。

基本的に、引数を使用してルートを取得し、プロキシされたアドレスを計算するメソッドで属性を使用できるようにすることで、別の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}");
}
7
twitchax

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

これが誰かを助けることを願っています!

2

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

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);
});
0
James Lawruk

ニースのリバースプロキシミドルウェアの実装もここにあります: 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());

私の場合、元のヘッダー(たとえば、ベアラートークンを含む認証ヘッダーなど)は、変更しない限り追加されません。

0
baumgarb