Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Connection that works with netcoreapp3.1 fails with net5.0 #4597

Closed
penenkel opened this issue Apr 20, 2021 · 4 comments
Closed

Connection that works with netcoreapp3.1 fails with net5.0 #4597

penenkel opened this issue Apr 20, 2021 · 4 comments

Comments

@penenkel
Copy link

Hello, me and my colleges have run into the following puzzling problem.

We have a WCF client application that communicates with a third party WCF server. We are using a BasicHttpBinding with BasicHttpSecurityMode.Transport and HttpClientCredentialType.Windows to establish the binding. Everything works as expected if the TFM is netcoreapp3.1, but as soon as we try to switch to net5.0 each connection fails with

System.ServiceModel.Security.MessageSecurityException: The HTTP request is unauthorized with client authentication scheme 'Negotiate'. The authentication header received from the server was 'Negotiate'.

The version of System.ServiceModel.Http does not seem to matter. We tried it with both 4.7 and 4.8.1.

As far as I can tell the above error simply indicates a 401 status response. As such I am not certain if this is really a problem with WCF or a problem with the underlying runtime.

@penenkel
Copy link
Author

Solved.

I have found the root problem and it has little to do with WCF at it primarily caused by a change in how the HttpClient client computes the SPN used for Kerberos authentication.

The following lines were added between 3.1 and 5.0

if (!isProxyAuth && !authUri.IsDefaultPort)
{
    hostName = $"{hostName}:{authUri.Port}";
}

which means that the computed SPN now includes the port of the URI if it its not the default port. Unfortunately the service I'm calling was registered without its (non-standard) port.

Arguably including the port is the correct behavior as indicated by this article.

Currently there is no way to directly specify the SPN to use for authentication. It has been requested in dotnet/runtime#25320, but the issue does not seem to have gained much traction.

As a workaround I'have implemented a custom IEndpointBehavior which sets the host-Header for each request to the domain-name-part of the SPN. This seems to be the only way to bypass the new "port-adding" code cited above.

@abakumov-v
Copy link

abakumov-v commented Aug 20, 2021

@penenkel Hi!
Could you provide your custom IEndpointBehavior implementation? We have same throubles...

@penenkel
Copy link
Author

Hi, @abakumov-v , happy to help.

Here is the implementation of IEndpointBehavior I ended up with:

public class HostHeaderEndpointBehavior : IEndpointBehavior
{
    private readonly string _host;
 
    public HostHeaderEndpointBehavior(string host)
    {
        _host = host;
    }
    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
 
        bindingParameters.Add(new Func<HttpClientHandler, HttpMessageHandler>(x =>
        {
            return new HostHeaderDelegatingHandler(x, _host);
        }));
    }
    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { }
    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }
    public void Validate(ServiceEndpoint endpoint) { }
 
    public class HostHeaderDelegatingHandler : DelegatingHandler
    {
        private readonly string _host;
 
        public HostHeaderDelegatingHandler(HttpClientHandler handler, string host)
        {
            InnerHandler = handler;
            _host = host;
        }
 
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            request.Headers.Add("host", _host);
            return base.SendAsync(request, cancellationToken);
        }
    }
}

And then apply it where you create your WCF-client with
client.Endpoint.EndpointBehaviors.Add(new HostHeaderEndpointBehavior("service.domain.com"));.

Be aware that this sets the host-Header on every request without any further checks. This will probably fail or at least lead to unexpected behavior if the host-Header is already otherwise used or set.

Please also consider upvoting the feature request to make the SPN configurable on HttpClient: dotnet/runtime#25320

@penenkel
Copy link
Author

Interesting, apparently the dotnet team is trying to mitigate the problem by making it configurable via an AppContext switch or an environment variable: dotnet/runtime#57159

The pull request ist marked with the 6.0 milestone, so if you are planning to to switch to NET 6.0 anytime soon you might want to consider that approach.

I'm not quite sure if this would work in my scenario as ist is obviously an application wide switch and would thus apply to all http communications not just one endpoint.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants