Migrating UsernameOverTransport security from WSE to WCF
WSE was, and I suppose is, a wrapper library to help make talking to WS-* compliant services easier. Specifically it can make communicating with secure services easier. However WSE was targeted at .NET v2, there is not a v4 version and development appears to to stopped. The current mechanism for talking to services like this is to use WCF.
With that in mind I have had to migrate a client library away from WSE to WCF. The library was used to communicate with an external service so from the server point of view nothing should change, however to make continued development easier it had been decided to use WCF.
The call in WSE looked something like this
ServiceRequest request = GetTestRequest(); Service proxy = new Service(); string username = ConfigurationManager.AppSettings["Username"]; string password = ConfigurationManager.AppSettings["Password"]; UsernameToken token = new UsernameToken(username, password, PasswordOption.SendHashed); proxy.SetClientCredential(token); proxy.SetPolicy("usernameTokenSecurity"); proxy.Url = ConfigurationManager.AppSettings["ServiceUrl"]; ServiceResponse result = proxy.MethodCall(request);
The config looked like this
<microsoft.web.services3> <diagnostics> <trace enabled="true" input="C:\Logs\input.log" output="C:\Logs\output.log"/> </diagnostics> <policy fileName="wse3policyCache.config"/> </microsoft.web.services3>
the policy file like this
<policies xmlns="http://schemas.microsoft.com/wse/2005/06/policy"> <extensions> <extension name="usernameOverTransportSecurity" type="Microsoft.Web.Services3.Design.UsernameOverTransportAssertion, Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> <extension name="requireActionHeader" type="Microsoft.Web.Services3.Design.RequireActionHeaderAssertion, Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> </extensions> <policy name="usernameTokenSecurity"> <usernameOverTransportSecurity /> <requireActionHeader /> </policy> </policies>
The security token is the UsernameToken which derives from System.Collections.ObjectModel.SecurityToken. This is all reasonably straight forward and there are a number of good examples of setting up the token.
It turns out that we can use exactly the same UsenameToken in WCF, in fact we need to be able to as the server will not be changing.
The call looks like this
string username = ConfigurationManager.AppSettings["Username"]; string password = ConfigurationManager.AppSettings["Password"]; UsernameClientCredentials credentials = new UsernameClientCredentials(new UsernameInfo(userName, passWord)); WcfService service = new WcfService("MyService"); service.Endpoint.EndpointBehaviors.Remove(typeof(ClientCredentials)); service.Endpoint.EndpointBehaviors.Add(credentials); ServiceResponse result = service.MethodCall(request);
The WCFService is a class that derives from the WCF client like this System.ServiceModel.ClientBase<IMyService>, IMyService. The config to hook it up looks like this, notice we are using UserNameOverTransport security
<system.serviceModel> <bindings> <customBinding> <binding name="httpsWithCredentialBinding"> <security enableUnsecuredResponse="true" authenticationMode="UserNameOverTransport" includeTimestamp="false" allowInsecureTransport="true"/> <textMessageEncoding messageVersion="Soap11"/> <httpsTransport maxReceivedMessageSize="655360000"/> </binding> </customBinding> </bindings> <client> <endpoint address="https://service" binding="customBinding" bindingConfiguration="httpsWithCredentialBinding" contract="IMyService" name="MyService"/> </client> </system.serviceModel>
In WCF the UsernameToken needs to be wrapped in classes to provide a token and serialise a token like this
public class UsernameClientCredentials : ClientCredentials { private readonly UsernameInfo usernameInfo; public UsernameClientCredentials(UsernameInfo usernameInfo) : base() { if (usernameInfo == null) { throw new ArgumentNullException("usernameInfo"); } this.usernameInfo = usernameInfo; } public UsernameInfo UsernameInfo { get { return this.usernameInfo; } } public override SecurityTokenManager CreateSecurityTokenManager() { return new UsernameClientCredentialsSecurityTokenManager(this); } protected override ClientCredentials CloneCore() { return new UsernameClientCredentials(this.usernameInfo); } } public class UsernameInfo { private readonly string userName; private readonly string password; public UsernameInfo(string userName, string password) { this.userName = userName; this.password = password; } public string Username { get { return this.userName; } } public string Password { get { return this.password; } } } public class UsernameClientCredentialsSecurityTokenManager : ClientCredentialsSecurityTokenManager { private readonly UsernameClientCredentials userNameClientCredentials; public UsernameClientCredentialsSecurityTokenManager (UsernameClientCredentials userNameClientCredentials) : base(userNameClientCredentials) { this.userNameClientCredentials = userNameClientCredentials; } public override SecurityTokenProvider CreateSecurityTokenProvider(SecurityTokenRequirement tokenRequirement) { if (tokenRequirement.TokenType == SecurityTokenTypes.UserName) { return new UsernameTokenProvider(this.userNameClientCredentials.UsernameInfo); } return base.CreateSecurityTokenProvider(tokenRequirement); } public override SecurityTokenSerializer CreateSecurityTokenSerializer(SecurityTokenVersion version) { return new UsernameSecurityTokenSerializer(version); } } // this class delivers the same UsernameToken that was used in WSE internal class UsernameTokenProvider : SecurityTokenProvider { private readonly UsernameInfo usernameInfo; public UsernameTokenProvider(UsernameInfo usernameInfo) : base() { if (usernameInfo == null) { throw new ArgumentNullException("usernameInfo"); } this.usernameInfo = usernameInfo; } protected override SecurityToken GetTokenCore(TimeSpan timeout) { SecurityToken result = new UsernameToken(this.usernameInfo); return result; } } // this class serialises the token onto the wire public class UsernameSecurityTokenSerializer : WSSecurityTokenSerializer { internal const string UsernameTokenPrefix = "wsse"; internal const string UsernameTokenNamespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; internal const string UsernameTokenName = "UsernameToken"; internal const string IdAttributeName = "Id"; internal const string WsUtilityPrefix = "wsu"; internal const string WsUtilityNamespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"; internal const string UsernameElementName = "Username"; internal const string PasswordElementName = "Password"; internal const string NonceElementName = "Nonce"; internal const string CreatedElementName = "Created"; internal const string TypeAttributeName = "Type"; internal const string PasswordDigestType = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"; public UsernameSecurityTokenSerializer(SecurityTokenVersion version) : base() { } protected override void WriteTokenCore(XmlWriter writer, SecurityToken token) { if (writer == null) { throw new ArgumentNullException("writer"); } if (token == null) { throw new ArgumentNullException("token"); } UsernameToken c = token as UsernameToken; if (c != null) { writer.WriteStartElement(UsernameTokenPrefix, UsernameTokenName, UsernameTokenNamespace); writer.WriteAttributeString(WsUtilityPrefix, IdAttributeName, WsUtilityNamespace, token.Id); writer.WriteElementString(UsernameElementName, UsernameTokenNamespace, c.UsernameInfo.Username); writer.WriteStartElement(UsernameTokenPrefix, PasswordElementName, UsernameTokenNamespace); writer.WriteAttributeString(TypeAttributeName, PasswordDigestType); writer.WriteValue(c.GetPasswordDigestAsBase64()); writer.WriteEndElement(); writer.WriteElementString(NonceElementName, UsernameTokenNamespace, c.GetNonceAsBase64()); writer.WriteElementString(CreatedElementName, WsUtilityNamespace, c.GetCreatedAsString()); writer.WriteEndElement(); writer.Flush(); } else { base.WriteTokenCore(writer, token); } } }
There are some loose ends to tie up, for example WSE logging is far nicer than WCF, it would be good to be able to ,log the wire packets.