通过 HTTPClient 传递 Windows 身份验证

时间:2021-03-25 09:33:20

标签: c# rest windows-authentication dotnet-httpclient

我正在尝试将个人 Windows 凭据从一个 REST 服务(ASP.NET 核心 3.1)传递到另一个 REST 服务(net462),但我遇到了一般的 Windows Identity 和 Windows Identity Impersonation 问题.

为了手头的争论,我将分享我的整个 HttpClient 类,当我从 ASP.NET 核心 api 发送请求时运行该类

 public class ExtendedHttpClient
{
    #region Private Variables

    private readonly HttpMessageHandler _httpMessageHandler;
    private readonly Uri _baseUri;

    private WindowsIdentity _windowsIdentity;
    private HttpClient _client;
    private ILogger _logger;

    #endregion

    #region Constructor

    /// <summary>
    /// Initializes a new instance of the ExtendedHttpClient class.
    /// </summary>
    /// <param name='baseUri'>
    /// The base URI of the service.
    /// </param>
    public ExtendedHttpClient(Uri baseUri)
    {
        _baseUri = baseUri ?? throw new ArgumentNullException(nameof(baseUri));
        Initialize();
    }

    public ExtendedHttpClient(Uri baseUri, ILogger logger)
        : this(baseUri) => _logger = logger ?? throw new ArgumentNullException(nameof(logger));

    public ExtendedHttpClient(Uri baseUri, HttpMessageHandler httpMessageHandler)
        : this(baseUri) => _httpMessageHandler = httpMessageHandler ?? throw new ArgumentNullException(nameof(httpMessageHandler));

    public ExtendedHttpClient(Uri baseUri, HttpMessageHandler httpMessageHandler, ILogger logger)
        : this(baseUri, httpMessageHandler) => _logger = logger ?? throw new ArgumentNullException(nameof(logger));

    #endregion

    #region Public Methods

    public async Task<T> PostAsync<T>(string endpointUri, string jsonBody)
    {
        HttpRequestMessage httpRequestMessage = CreateRequestMessage(HttpMethod.Post, endpointUri, jsonBody);

        var response = await GetResponseMessageAsync(httpRequestMessage);
        return await response.GetResultAsync<T>();
    }

    public async Task<T> GetAsync<T>(string endpointUri)
    {
        HttpRequestMessage httpRequestMessage = CreateRequestMessage(HttpMethod.Get, endpointUri);
        var response = await GetResponseMessageAsync(httpRequestMessage);
        return await response.GetResultAsync<T>();
    }

    public async Task<T> PutAsync<T>(string endpointUri, string jsonBody)
    {
        HttpRequestMessage httpRequestMessage = CreateRequestMessage(HttpMethod.Put, endpointUri, jsonBody);

        var response = await GetResponseMessageAsync(httpRequestMessage);
        return await response.GetResultAsync<T>();
    }

    public async Task<T> DeleteAsync<T>(string endpointUri)
    {
        HttpRequestMessage httpRequestMessage = CreateRequestMessage(HttpMethod.Delete, endpointUri);

        var response = await GetResponseMessageAsync(httpRequestMessage);
        return await response.GetResultAsync<T>();
    }

    public void SetWindowsIdentityForImpersonation(IPrincipal principal)
    {
        if(principal is WindowsPrincipal windowsPrincipal)
        {
            _windowsIdentity = (WindowsIdentity)windowsPrincipal.Identity;
        }
    }

    #endregion

    #region Private Methods

    private void Initialize()
    {
        if (_logger == null)
        {
            var logConfiguration = new LoggerConfiguration()
                .MinimumLevel.Debug()
                .WriteTo.File(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs/library_http_error_.txt"),
                    LogEventLevel.Debug, "{Timestamp:HH:mm:ss} [{Level}] {Message}{NewLine}{Exception}",
                    rollingInterval: RollingInterval.Hour);

            _logger = logConfiguration.CreateLogger();
        }


        _client = _httpMessageHandler != null ? new HttpClient(_httpMessageHandler) : new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
        _client.BaseAddress = _baseUri;
        _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

    }

    private HttpRequestMessage CreateRequestMessage(HttpMethod httpMethod, string endPointUri, string jsonBody = "")
    {
        HttpRequestMessage httpRequestMessage = new HttpRequestMessage(httpMethod, string.Format("{0}{1}", _client.BaseAddress, endPointUri));
        
        if (httpMethod == HttpMethod.Post || httpMethod == HttpMethod.Put)
        {
            if (string.IsNullOrEmpty(jsonBody))
            {
                throw new ArgumentNullException(nameof(jsonBody), "Provide request body");
            }

            httpRequestMessage.Content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
        }

        return httpRequestMessage;
    }

    private async Task<HttpResponseMessage> GetResponseMessageAsync(HttpRequestMessage httpRequestMessage)
    {
        _logger.Debug($"Getting HttpResponseMessage with parameters: requestPath={httpRequestMessage.RequestUri} baseUri={_client.BaseAddress}");
        Environment.SetEnvironmentVariable("DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER", "0");

        try
        {
            var maxRetryAttempts = 3;
            var pauseBetweenFailures = TimeSpan.FromSeconds(2);

            var retryPolicy = Policy
                .Handle<HttpRequestException>()
                .WaitAndRetryAsync(maxRetryAttempts, i => TimeSpan.FromSeconds(Math.Pow(2, i)), (result, timeSpan, retryCount, context) =>
                {
                    _logger.Warning($"Request failed with {result.Message}. Waiting {timeSpan} before next retry. Retry attempt {retryCount}");

                    if (retryCount == maxRetryAttempts)
                    {
                        _logger.Error(result, $"Request failed with {result.Message}. Waiting {timeSpan} before next retry. Retry attempt {retryCount}");

                        throw new ApiServiceException((int)HttpStatusCode.InternalServerError, string.Empty,
                                                      $"Request to requestPath={httpRequestMessage.RequestUri} baseUri={_client.BaseAddress} has failed",
                                                      result);
                    }
                });

            return await retryPolicy.ExecuteAsync(async () =>
            {
                return WindowsIdentity.RunImpersonated(_windowsIdentity.AccessToken, () =>
                {
                    var impersonatedUser = WindowsIdentity.GetCurrent().Name;

                    var clonedMessage = httpRequestMessage.CloneAsync().Result;
                    var response = _client.SendAsync(clonedMessage).Result;
                    response.EnsureSuccessStatusCode();

                    return response;
                });
            });
        }
        catch (Exception ex)
        {
            _logger.Error(ex, $"Request failed with {ex.Message}.");

            throw new ApiServiceException((int)HttpStatusCode.InternalServerError, string.Empty,
                                          $"Request to requestPath={httpRequestMessage.RequestUri} baseUri={_client.BaseAddress} has failed",
                                          ex);
        }
    }

    #endregion
}

现在使用此代码,我收到 This is usually a temporary error during hostname resolution and means that the local server did not receive a response from an authoritative server. 作为错误。

现在,从我在过去几天的陡峭学习中学到的是,通过 HTTP 调用传递 Windows 凭据有些困难。如果您将 Impersonation 位排除在外,则上面描述的代码可以工作,但是在接收端您将获得 IIS 应用程序池用户,而不是在原始应用程序中进行身份验证的 Windows 用户。

一些提示会很好,所以我可以结束这个可怕的章节;)

0 个答案:

没有答案