我正在尝试将个人 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 用户。
一些提示会很好,所以我可以结束这个可怕的章节;)