dotnet / wcf

This repo contains the client-oriented WCF libraries that enable applications built on .NET Core to communicate with WCF services.
MIT License
1.72k stars 558 forks source link

UserName Endpoint STS #4542

Open AlleSchonWeg opened 3 years ago

AlleSchonWeg commented 3 years ago

Hi,

how can i obtain a token from a custom sts via ws-trust: https://social.msdn.microsoft.com/Forums/azure/en-US/9b91ae9f-3e3c-4ba4-a19c-d8af3bba5981/wstruststs-support-on-net-core?forum=netfxbcl

The answer in this thread says use nuget System.ServiceModel.Security but i saw no RequestSecurityToken class for example. Is there another with .NetStandard >=2 ?

mconnew commented 3 years ago

Enough people have asked for this I think we actually need to put together a WSTrustChannelFactory or WSTrustClient for Core to piece these pieces together for you. This is basically the code you need:

var tokenParams = WSTrustTokenParameters.CreateWSFederationTokenParameters(Binding issuerBinding, EndpointAddress issuerAddress);
var clientCredentials = new ClientCredentials();
clientCredentials.UserName.UserName = "user";
clientCredentials.UserName.Password= "its_a_secret";
var trustCredentials = new WSTrustChannelClientCredentials(clientCredentials);
var tokenManager = trustCredentials.CreateSecurityTokenManager();
var tokenRequirement = new SecurityTokenRequirement();
tokenRequirement.Properties["http://schemas.microsoft.com/ws/2006/05/servicemodel/securitytokenrequirement/IssuedSecurityTokenParameters"] = tokenParams;
var tokenProvider = tokenManager.CreateSecurityTokenProvider(tokenRequirement);

After which you can request the security token using either the sync or APM methods:

// Sync
var token = tokenProvider.GetToken(Timeout.InfiniteTimeSpan);

// Async
var token = await Task.Factory.FromAsync(tokenProvider.BeginGetToken, tokenProvider.EndGetToken, Timeout.InfiniteTimeSpan, null);

I haven't executed this code as I don't have a username STS readily available to test it against so there might be some errors. The type of token you get returned will be GenericXmlSecurityToken. Don't forget to set the client credential type on your issuer binding to UserName.

AlleSchonWeg commented 3 years ago

Hi @mconnew I tested your sample code with a few additions:

        private void TestTokenIssue(string userName, string password)
        {
            var adrr = new EndpointAddress("http://localhost:14785/issue/username");
            var binding = new WS2007HttpBinding();
            binding.Security.Mode = SecurityMode.None;
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
            binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
            binding.Security.Message.EstablishSecurityContext = false;
            binding.Security.Message.AlgorithmSuite = SecurityAlgorithmSuite.Default;

            var tokenParams = WSTrustTokenParameters.CreateWSFederationTokenParameters(binding, adrr);

            tokenParams.KeyType = SecurityKeyType.BearerKey; // "http://schemas.microsoft.com/idfx/keytype/bearer";
            var clientCredentials = new ClientCredentials();
            clientCredentials.UserName.UserName = userName;
            clientCredentials.UserName.Password = password; ;
            var trustCredentials = new WSTrustChannelClientCredentials(clientCredentials);
            var tokenManager = trustCredentials.CreateSecurityTokenManager();
            var tokenRequirement = new SecurityTokenRequirement();
            tokenRequirement.Properties["http://schemas.microsoft.com/ws/2006/05/servicemodel/securitytokenrequirement/IssuedSecurityTokenParameters"] = tokenParams;
            var tokenProvider = tokenManager.CreateSecurityTokenProvider(tokenRequirement);
            var token = tokenProvider.GetToken(Timeout.InfiniteTimeSpan);
        }

It works until i call tokenProvider.GetToken. I receive a exception:

System.InvalidOperationException: 'The communication object, System.ServiceModel.Federation.WSTrustChannelSecurityTokenProvider, is in the Created state. Communication objects cannot be used for communication unless they are in the Opened state.'

Now the question is, how can i open the channel?

mconnew commented 3 years ago

Sorry, I forgot that part. Cast the SecurityTokenProvider to ICommunicationObject and call Open().

AlleSchonWeg commented 3 years ago

Thank you @mconnew. But next problem ;) If i test the code with my xamarin app, it compiles but crahses if loading some types. For example:

System.TypeLoadException: 'Could not resolve type with token 010001ac from typeref (expected class 'System.ServiceModel.WS2007HttpBinding' in assembly 'System.ServiceModel.Http, Version=4.8.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a')'

I added the System.ServiceModel.Http by hand, but still the same exceptions. WS2007HttpBinding is not the only type which can't resolve.

mconnew commented 3 years ago

Xamarin app's don't use the nuget version of WCF. With the exception of System.ServiceModel.Federation, all the other packages are replaced with an inbox Xamarin provided implementation. There's nothing I can do about Xamarin issues, you will need to open an issue with them.

AlleSchonWeg commented 3 years ago

This code works in my non Xamarin Testclient:

        private void TestTokenIssueLocal(string userName, string password)
        {
            var adrr = new EndpointAddress("http://localhost:14785/issue/username");
            var binding = new WS2007HttpBinding();
            binding.Security.Mode = SecurityMode.None;
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
            binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
            binding.Security.Message.EstablishSecurityContext = false;
            binding.Security.Message.AlgorithmSuite = SecurityAlgorithmSuite.Default;

            //var tokenParams = WSTrustTokenParameters.CreateWSFederationTokenParameters(binding, adrr);
            var tokenParams = WSTrustTokenParameters.CreateWS2007FederationTokenParameters(binding, adrr);
            tokenParams.KeyType = SecurityKeyType.BearerKey; // "http://schemas.microsoft.com/idfx/keytype/bearer";
            var clientCredentials = new ClientCredentials();
            clientCredentials.UserName.UserName = userName;
            clientCredentials.UserName.Password = password; ;
            var trustCredentials = new WSTrustChannelClientCredentials(clientCredentials);
            var tokenManager = trustCredentials.CreateSecurityTokenManager();
            var tokenRequirement = new SecurityTokenRequirement();
            EndpointAddress appliesTo = new EndpointAddress("urn:xxx:xxx:xxx"); //Realm
            tokenRequirement.Properties["http://schemas.microsoft.com/ws/2006/05/servicemodel/securitytokenrequirement/IssuedSecurityTokenParameters"] = tokenParams;
            tokenRequirement.Properties["http://schemas.microsoft.com/ws/2006/05/servicemodel/securitytokenrequirement/TargetAddress"] = appliesTo;
            var tokenProvider = tokenManager.CreateSecurityTokenProvider(tokenRequirement);
            ((ICommunicationObject)tokenProvider).Open();
            var gerericXmlToken = (GenericXmlSecurityToken)tokenProvider.GetToken(Timeout.InfiniteTimeSpan);
        }
mconnew commented 3 years ago

You should also close the token provider when you are finished with it. It does contain an instance of a ChannelFactory and creates clients so you could get a memory leak if you don't close it. Ideally though you would persist the token provider and reuse it every time you need a token.

AlleSchonWeg commented 3 years ago

Thank you. Is it thread safe, so we can store the token provider in a static variable?

I have also open an issue on xf. Hopefully I get an answer.

mconnew commented 3 years ago

Yes, it's thread safe. It creates a new ChannelFactory when you call Open, then each request creates a new channel, uses it, then closes the channel. No shared state is modified with each call to get a token.

sramreddola commented 1 year ago

This code works in my non Xamarin Testclient:

        private void TestTokenIssueLocal(string userName, string password)
        {
            var adrr = new EndpointAddress("http://localhost:14785/issue/username");
            var binding = new WS2007HttpBinding();
            binding.Security.Mode = SecurityMode.None;
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
            binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
            binding.Security.Message.EstablishSecurityContext = false;
            binding.Security.Message.AlgorithmSuite = SecurityAlgorithmSuite.Default;

            //var tokenParams = WSTrustTokenParameters.CreateWSFederationTokenParameters(binding, adrr);
            var tokenParams = WSTrustTokenParameters.CreateWS2007FederationTokenParameters(binding, adrr);
            tokenParams.KeyType = SecurityKeyType.BearerKey; // "http://schemas.microsoft.com/idfx/keytype/bearer";
            var clientCredentials = new ClientCredentials();
            clientCredentials.UserName.UserName = userName;
            clientCredentials.UserName.Password = password; ;
            var trustCredentials = new WSTrustChannelClientCredentials(clientCredentials);
            var tokenManager = trustCredentials.CreateSecurityTokenManager();
            var tokenRequirement = new SecurityTokenRequirement();
            EndpointAddress appliesTo = new EndpointAddress("urn:xxx:xxx:xxx"); //Realm
            tokenRequirement.Properties["http://schemas.microsoft.com/ws/2006/05/servicemodel/securitytokenrequirement/IssuedSecurityTokenParameters"] = tokenParams;
            tokenRequirement.Properties["http://schemas.microsoft.com/ws/2006/05/servicemodel/securitytokenrequirement/TargetAddress"] = appliesTo;
            var tokenProvider = tokenManager.CreateSecurityTokenProvider(tokenRequirement);
            ((ICommunicationObject)tokenProvider).Open();
            var gerericXmlToken = (GenericXmlSecurityToken)tokenProvider.GetToken(Timeout.InfiniteTimeSpan);
        }

Your code works fine in windows but it is failing in linux machine with the error 'The communication object, System.ServiceModel.Federation.WSTrustChannelSecurityTokenProvider, is in the Created state. Communication objects cannot be used for communication unless they are in the Opened state.'. Any idea if this type of communication is supported in linux from .net 6 application?

AlleSchonWeg commented 1 year ago

This code works in my non Xamarin Testclient:

        private void TestTokenIssueLocal(string userName, string password)
        {
            var adrr = new EndpointAddress("http://localhost:14785/issue/username");
            var binding = new WS2007HttpBinding();
            binding.Security.Mode = SecurityMode.None;
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
            binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
            binding.Security.Message.EstablishSecurityContext = false;
            binding.Security.Message.AlgorithmSuite = SecurityAlgorithmSuite.Default;

            //var tokenParams = WSTrustTokenParameters.CreateWSFederationTokenParameters(binding, adrr);
            var tokenParams = WSTrustTokenParameters.CreateWS2007FederationTokenParameters(binding, adrr);
            tokenParams.KeyType = SecurityKeyType.BearerKey; // "http://schemas.microsoft.com/idfx/keytype/bearer";
            var clientCredentials = new ClientCredentials();
            clientCredentials.UserName.UserName = userName;
            clientCredentials.UserName.Password = password; ;
            var trustCredentials = new WSTrustChannelClientCredentials(clientCredentials);
            var tokenManager = trustCredentials.CreateSecurityTokenManager();
            var tokenRequirement = new SecurityTokenRequirement();
            EndpointAddress appliesTo = new EndpointAddress("urn:xxx:xxx:xxx"); //Realm
            tokenRequirement.Properties["http://schemas.microsoft.com/ws/2006/05/servicemodel/securitytokenrequirement/IssuedSecurityTokenParameters"] = tokenParams;
            tokenRequirement.Properties["http://schemas.microsoft.com/ws/2006/05/servicemodel/securitytokenrequirement/TargetAddress"] = appliesTo;
            var tokenProvider = tokenManager.CreateSecurityTokenProvider(tokenRequirement);
            ((ICommunicationObject)tokenProvider).Open();
            var gerericXmlToken = (GenericXmlSecurityToken)tokenProvider.GetToken(Timeout.InfiniteTimeSpan);
        }

Your code works fine in windows but it is failing in linux machine with the error 'The communication object, System.ServiceModel.Federation.WSTrustChannelSecurityTokenProvider, is in the Created state. Communication objects cannot be used for communication unless they are in the Opened state.'. Any idea if this type of communication is supported in linux from .net 6 application?

No sorry.