使用Asp.net核心创建另一个Web api的代理

Gim*_*mly 35 c# asp.net asp.net-core-mvc asp.net-core-webapi

我正在开发一个ASP.Net Core Web应用程序,我需要为另一个(外部)Web服务创建一种"身份验证代理".

我的身份验证代理的意思是我将通过我的Web应用程序的特定路径接收请求,并且必须检查这些请求的标头以获取我之前发布的身份验证令牌,然后将所有请求重定向到相同的请求字符串/内容到我的应用程序将通过HTTP Basic身份验证进行身份验证的外部Web API.

这是伪代码的整个过程

  • 客户端通过对我之前发送给他的唯一URL进行POST来请求令牌
  • 我的应用程序向他发送了一个唯一的令牌以响应此POST
  • 客户端向我的应用程序的特定URL发出GET请求,/extapi并在HTTP标头中添加auth-token
  • 我的应用程序获取请求,检查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);
    }
}
Run Code Online (Sandbox Code Playgroud)

_httpClient作为一个HttpClient类实例化别的地方,是一个独立的,并用BaseAddresshttp://someexternalapp.com/api/

此外,令牌创建/令牌检查比手动更简单吗?

Gim*_*mly 14

我最终实现了一个受Asp.Net的GitHub项目启发的代理中间件.

它基本上实现了一个中间件,它读取收到的请求,从中创建一个副本并将其发送回配置的服务,从服务中读取响应并将其发送回调用者.

  • 您能分享一下您的中间件的实现吗?如果可以的话。它是否强烈基于.Net Core?谢谢。 (2认同)

And*_*uin 9

这篇文章讨论了在 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;
        }
Run Code Online (Sandbox Code Playgroud)

此方法隐蔽用户发送HttpContext.Request到一个可重用的HttpRequestMessage. 因此,您可以将此消息发送到目标服务器。

在您的目标服务器响应之后,您需要将响应复制HttpResponseMessageHttpContext.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);
            }
        }
Run Code Online (Sandbox Code Playgroud)

现在准备工作已经完成。回到我们的控制器:

    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();
        }
Run Code Online (Sandbox Code Playgroud)

并尝试访问它。它将被代理到 google.com

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

  • 感谢您的代码。这里有足够的内容来指导我走我想要走的路。 (3认同)

twi*_*hax 8

如果有人感兴趣,我会使用Microsoft.AspNetCore.Proxy代码,并使它与中间件更好。

在这里查看:https : //github.com/twitchax/AspNetCore.Proxy。NuGet此处:https//www.nuget.org/packages/AspNetCore.Proxy/。Microsoft存档了这篇文章中提到的另一篇文章,我计划对这个项目中的任何问题做出回应。

基本上,它允许您在通过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}");
}
Run Code Online (Sandbox Code Playgroud)


Jam*_*ruk 6

我很幸运地使用了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);
});
Run Code Online (Sandbox Code Playgroud)


spe*_*741 6

Twitchax 的答案似乎是目前最好的解决方案。在研究过程中,我发现 Microsoft 正在开发一种更强大的解决方案,该解决方案适合 OP 试图解决的确切问题。

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

Preview 1 的文章(他们实际上刚刚发布了 prev 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 预览版 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


Dan*_*iew 5

借助 James Lawruk 的回答/sf/answers/3790493451/来使 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/");
    }
}
Run Code Online (Sandbox Code Playgroud)

这不会:

[Route("api/[controller]")]
public class ProxyController : Controller
{
    [ProxyRoute("{name}")]
    public static Task<string> Get(string name)
    {
        return Task.FromResult($"http://www.google.com/");
    }
}
Run Code Online (Sandbox Code Playgroud)

希望这对某人有帮助!


bau*_*arb 5

一个不错的反向代理中间件实现也可以在这里找到:https : //auth0.com/blog/building-a-reverse-proxy-in-dot-net-core/

请注意,我在这里替换了这一行

requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
Run Code Online (Sandbox Code Playgroud)

requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToString());
Run Code Online (Sandbox Code Playgroud)

在我的情况下,如果没有我的修改,则不会添加原始标头(例如,带有不记名令牌的授权标头)。

  • 此代理也不会将查询字符串添加到它代理的请求中。我在 `BuildTargetUri` 中使用 `string query = request.QueryString.ToString();` 添加了这一点 (2认同)