diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs index 3d3eaed48b8c..e973c3e86083 100644 --- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs @@ -76,32 +76,50 @@ private static async Task SendWithNtAuthAsync(HttpRequestMe needDrain = false; } - string challengeData = challenge.ChallengeData; + if (NetEventSource.IsEnabled) + { + NetEventSource.Info(connection, $"Authentication: {challenge.AuthenticationType}, Uri: {authUri.AbsoluteUri.ToString()}"); + } - // Need to use FQDN normalized host so that CNAME's are traversed. - // Use DNS to do the forward lookup to an A (host) record. - // But skip DNS lookup on IP literals. Otherwise, we would end up - // doing an unintended reverse DNS lookup. - string spn; - UriHostNameType hnt = authUri.HostNameType; - if (hnt == UriHostNameType.IPv6 || hnt == UriHostNameType.IPv4) + // Calculate SPN (Service Principal Name) using the host name of the request. + // Use the request's 'Host' header if available. Otherwise, use the request uri. + string hostName; + if (request.HasHeaders && request.Headers.Host != null) { - spn = authUri.IdnHost; + // Use the host name without any normalization. + hostName = request.Headers.Host; + if (NetEventSource.IsEnabled) + { + NetEventSource.Info(connection, $"Authentication: {challenge.AuthenticationType}, Host: {hostName}"); + } } else { - IPHostEntry result = await Dns.GetHostEntryAsync(authUri.IdnHost).ConfigureAwait(false); - spn = result.HostName; + // Need to use FQDN normalized host so that CNAME's are traversed. + // Use DNS to do the forward lookup to an A (host) record. + // But skip DNS lookup on IP literals. Otherwise, we would end up + // doing an unintended reverse DNS lookup. + UriHostNameType hnt = authUri.HostNameType; + if (hnt == UriHostNameType.IPv6 || hnt == UriHostNameType.IPv4) + { + hostName = authUri.IdnHost; + } + else + { + IPHostEntry result = await Dns.GetHostEntryAsync(authUri.IdnHost).ConfigureAwait(false); + hostName = result.HostName; + } } - spn = "HTTP/" + spn; + string spn = "HTTP/" + hostName; if (NetEventSource.IsEnabled) { - NetEventSource.Info(connection, $"Authentication: {challenge.AuthenticationType}, Host: {authUri.IdnHost}, SPN: {spn}"); + NetEventSource.Info(connection, $"Authentication: {challenge.AuthenticationType}, SPN: {spn}"); } ChannelBinding channelBinding = connection.TransportContext?.GetChannelBinding(ChannelBindingKind.Endpoint); NTAuthentication authContext = new NTAuthentication(isServer:false, challenge.SchemeName, challenge.Credential, spn, ContextFlagsPal.Connection, channelBinding); + string challengeData = challenge.ChallengeData; try { while (true) diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Authentication.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Authentication.cs index 7b793a240ee7..767b014678d7 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Authentication.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Authentication.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; using System.Net.Test.Common; using System.Text; using System.Threading.Tasks; @@ -518,6 +520,10 @@ public static IEnumerable ServerUsesWindowsAuthentication_MemberData() private static bool IsNtlmInstalled => Capability.IsNtlmInstalled(); private static bool IsWindowsServerAvailable => !string.IsNullOrEmpty(Configuration.Http.WindowsServerHttpHost); private static bool IsDomainJoinedServerAvailable => !string.IsNullOrEmpty(Configuration.Http.DomainJoinedHttpHost); + private static NetworkCredential DomainCredential = new NetworkCredential( + Configuration.Security.ActiveDirectoryUserName, + Configuration.Security.ActiveDirectoryUserPassword, + Configuration.Security.ActiveDirectoryName); [ConditionalFact(nameof(IsDomainJoinedServerAvailable))] public async Task Credentials_DomainJoinedServerUsesKerberos_Success() @@ -530,19 +536,39 @@ public async Task Credentials_DomainJoinedServerUsesKerberos_Success() using (HttpClientHandler handler = CreateHttpClientHandler()) using (HttpClient client = CreateHttpClient(handler)) { - handler.Credentials = new NetworkCredential( - Configuration.Security.ActiveDirectoryUserName, - Configuration.Security.ActiveDirectoryUserPassword, - Configuration.Security.ActiveDirectoryName); + handler.Credentials = DomainCredential; - var request = new HttpRequestMessage(); - var server = $"http://{Configuration.Http.DomainJoinedHttpHost}/test/auth/kerberos/showidentity.ashx"; - request.RequestUri = new Uri(server); + string server = $"http://{Configuration.Http.DomainJoinedHttpHost}/test/auth/kerberos/showidentity.ashx"; + using (HttpResponseMessage response = await client.GetAsync(server)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + string body = await response.Content.ReadAsStringAsync(); + _output.WriteLine(body); + } + } + } - // Force HTTP/1.1 since both CurlHandler and SocketsHttpHandler have problems with - // HTTP/2.0 and Windows authentication (due to HTTP/2.0 -> HTTP/1.1 downgrade handling). - // Issue #35195 (for SocketsHttpHandler). - request.Version = new Version(1,1); + [ConditionalFact(nameof(IsDomainJoinedServerAvailable))] + public async Task Credentials_DomainJoinedServerUsesKerberos_UseIpAddressAndHostHeader_Success() + { + if (IsCurlHandler || IsWinHttpHandler) + { + throw new SkipTestException("Skipping test on platform handlers (CurlHandler, WinHttpHandler)"); + } + + using (HttpClientHandler handler = CreateHttpClientHandler()) + using (HttpClient client = CreateHttpClient(handler)) + { + handler.Credentials = DomainCredential; + + IPAddress[] addresses = Dns.GetHostAddresses(Configuration.Http.DomainJoinedHttpHost); + IPAddress hostIP = addresses.Where(a => a.AddressFamily == AddressFamily.InterNetwork).Select(a => a).First(); + + var request = new HttpRequestMessage(); + request.RequestUri = new Uri($"http://{hostIP}/test/auth/kerberos/showidentity.ashx"); + request.Headers.Host = Configuration.Http.DomainJoinedHttpHost; + _output.WriteLine(request.RequestUri.AbsoluteUri.ToString()); + _output.WriteLine($"Host: {request.Headers.Host}"); using (HttpResponseMessage response = await client.SendAsync(request)) { @@ -569,15 +595,7 @@ public async Task Credentials_ServerUsesWindowsAuthentication_Success(string ser Configuration.Security.WindowsServerUserName, Configuration.Security.WindowsServerUserPassword); - var request = new HttpRequestMessage(); - request.RequestUri = new Uri(server); - - // Force HTTP/1.1 since both CurlHandler and SocketsHttpHandler have problems with - // HTTP/2.0 and Windows authentication (due to HTTP/2.0 -> HTTP/1.1 downgrade handling). - // Issue #35195 (for SocketsHttpHandler). - request.Version = new Version(1,1); - - using (HttpResponseMessage response = await client.SendAsync(request)) + using (HttpResponseMessage response = await client.GetAsync(server)) { Assert.Equal(HttpStatusCode.OK, response.StatusCode); string body = await response.Content.ReadAsStringAsync();