Re-Serialize SAML token
In a Federation Scenario a client might want to access the services by using a SAML token that was issued to it by a STS. The service in turn might have to call other services (like a intermediary) to fulfill the request. When calling the backend service the service might want to use the SAML token that was presented to it by the client. This is a very common enterprise scenario. WCF currently does not enable this scenario. You can get around this by writing some custom code on the service side. Basically you need to write a custom SAML assertion that will remember the stream and will write it out when it has to. This also involves registering your own serializer and so on. Below is some code samples,
Write a Custom SAML Assertion
public class CustomSamlAssertion : SamlAssertion { MemoryStream ms; public override void ReadXml(XmlDictionaryReader reader, SamlSerializer samlSerializer, SecurityTokenSerializer keyInfoSerializer, SecurityTokenResolver outOfBandTokenResolver) { ms = new MemoryStream(Encoding.UTF8.GetBytes(reader.ReadOuterXml())); ms.Position = 0; XmlDictionaryReader dicReader = XmlDictionaryReader.CreateTextReader(ms, XmlDictionaryReaderQuotas.Max); base.ReadXml(dicReader, samlSerializer, keyInfoSerializer, outOfBandTokenResolver); } public override void WriteXml(XmlDictionaryWriter writer, SamlSerializer samlSerializer, SecurityTokenSerializer keyInfoSerializer) { if (ms != null) { ms.Position = 0; XmlDocument dom = new XmlDocument(); dom.Load(ms); dom.DocumentElement.WriteTo(writer); return; } base.WriteXml(writer, samlSerializer, keyInfoSerializer); } }
|
The above assertion just stores the incoming SAML Assertion in a memory stream and writes out the stream when you try to re-send the assertion. Note, if you want to create a new SAML assertion you will have to new up the built in SAML Assertion. The way signature processing is handled on the send side will prevent from writing out the signature if the CustomSamlAssertion is new'ed up to build a new assertion. The next step would be to provide a custom SAML serializer,
Write a Custom SAML Serializer
public class CustomSamlSerializer : SamlSerializer { public override SamlAssertion LoadAssertion(XmlDictionaryReader reader, SecurityTokenSerializer keyInfoSerializer, SecurityTokenResolver outOfBandTokenResolver) { CustomSamlAssertion assertion = new CustomSamlAssertion(); assertion.ReadXml(reader, this, keyInfoSerializer, outOfBandTokenResolver); return assertion; } }
|
Now we need to plug the Custom Serializer with the way Token Serialization is handled in WCF. So we need to write a
Write a Custom Token Serializer
public class CustomTokenSerializer : WSSecurityTokenSerializer { protected override void WriteTokenCore(XmlWriter writer, SecurityToken token) { if (token is SamlSecurityToken) { SamlAssertion assertion = ((SamlSecurityToken)token).Assertion; if (assertion is CustomSamlAssertion) { XmlDictionaryWriter dicWriter = XmlDictionaryWriter.CreateDictionaryWriter(writer); ((CustomSamlAssertion)assertion).WriteXml(dicWriter, new SamlSerializer(), WSSecurityTokenSerializer.DefaultInstance); return; } } base.WriteTokenCore(writer, token); } }
|
The above code delegates all token serialization to the base class except for SAML.
Next, we need to provide a TokenManager that gives out our Custom Serializer instead of the default serializer.
Write a Custom Token Manager
public class CustomTokenManager : ClientCredentialsSecurityTokenManager { public CustomTokenManager(CustomClientCredentials clientCredentials) : base(clientCredentials) { this.tokenProvider = new SamlTokenProvider(token as SamlSecurityToken); } public override SecurityTokenSerializer CreateSecurityTokenSerializer(SecurityTokenVersion version) { return new CustomTokenSerializer(); } }
|
All Credentials related stuff should end up in ClientCredentials or ServiceCredentials in object in WCF. So let's implement a Custom Client Credentials that wraps the Token Manager.
Write Custom Client Credentials
public class CustomClientCredentials : ClientCredentials { SecurityToken securityToken; public override SecurityTokenManager CreateSecurityTokenManager() { return new CustomTokenManager(this); } protected override ClientCredentials CloneCore() { return this; } }
|
When you are receiving the SAML token (you are the service) all that you need is the custom SAML Serializer. Below is how you would configure this,
ServiceHost serviceHost = new ServiceHost(typeof(CalculatorService)); serviceHost.Credentials.IssuedTokenAuthentication.SamlSerializer = new CustomSamlSerializer(); serviceHost.Open();
|
Now any SAML token received via this serviceHost will be loaded into the Custom SAML Assertion we have created.
When you want to re-serialize the SAML token, you have to register your Custom Client Credentials with the Channel Factory (Note: you will be acting as a client in this case). Below is how you would configure this,
EndpointAddress er = new EndpointAddress(new Uri(backEndServiceUri), EndpointIdentity.CreateDnsIdentity("Server-Cert")); ChannelFactory<ICalculator> factory = new ChannelFactory<ICalculator>(GetCustomBinding(), er); CustomClientCredentials clientCredentials = new CustomClientCredentials(); factory.Endpoint.Behaviors.Remove<ClientCredentials>(); factory.Endpoint.Behaviors.Add(clientCredentials); ICalculator client = factory.CreateChannel();
|
That's it, you can now receive SAML tokens and re-serialize the token to a backend service.
The attached project has code for this scenario.
Comments
Anonymous
January 10, 2007
Hi Govind, Once we fixed the serialisation problem it all worked. However I implemented this slightly differently to your example. Instead of creating a new Token Serialiser, I did the following: In the Custom Saml Serialiser I overrode WriteToken like this: public override void WriteToken(SamlSecurityToken token, XmlWriter writer, SecurityTokenSerializer keyInfoSerializer) { XmlDictionaryWriter dictionaryWriter = XmlDictionaryWriter.CreateDictionaryWriter(writer); token.Assertion.WriteXml(dictionaryWriter, this, keyInfoSerializer); } and in the Customer Token Manager I overrode CreateSecurityTokenSerializer like this: public override SecurityTokenSerializer CreateSecurityTokenSerializer(SecurityTokenVersion version) { if (version.GetSecuritySpecifications().Contains(WsseSecExt11Namespace)) { return new WSSecurityTokenSerializer(SecurityVersion.WSSecurity11, version.GetSecuritySpecifications().Contains(BspNamespace), new CustomSamlSerializer()); } if (version.GetSecuritySpecifications().Contains(WsseSecExt10Namespace)) { return new WSSecurityTokenSerializer(SecurityVersion.WSSecurity10, version.GetSecuritySpecifications().Contains(BspNamespace), new CustomSamlSerializer()); } throw new NotSupportedException(Resources.SecurityTokenVersionNotSupported); } this is basically a lift from the framework code excpet that I pass in our new Saml Serialiser. Also, I can't do the down cast to MessageSecurityTokenVersion because it is Internal, which is pretty annoying. I will have to check that I am using the correct thing for the first two parameters. Also in the CustomSamlAssertion I did the following to save spinning up an XmlDocument each time: private string _originalSamlText; public override void ReadXml(XmlDictionaryReader reader, SamlSerializer samlSerializer, SecurityTokenSerializer keyInfoSerializer, SecurityTokenResolver outOfBandTokenResolver) { reader.MoveToContent(); _originalSamlText = reader.ReadOuterXml(); using (MemoryStream samlStream = new MemoryStream(Encoding.UTF8.GetBytes(_originalSamlText))) { XmlDictionaryReader xmlDictionaryReader = XmlDictionaryReader.CreateTextReader(samlStream, XmlDictionaryReaderQuotas.Max); base.ReadXml(xmlDictionaryReader, samlSerializer, keyInfoSerializer, outOfBandTokenResolver); } } public override void WriteXml(XmlDictionaryWriter writer, SamlSerializer samlSerializer, SecurityTokenSerializer keyInfoSerializer) { if (_originalSamlText != null) { writer.WriteRaw(_originalSamlText); return; } base.WriteXml(writer, samlSerializer, keyInfoSerializer); } Does this solution look OK? Cheers, BobAnonymous
January 18, 2007
Hi Bob, That looks fine. I guess you have over written LoadAssertion method of the Custom SAML Assertion too, right?
- Govind
Anonymous
January 21, 2007
The comment has been removedAnonymous
February 06, 2007
The comment has been removedAnonymous
February 26, 2007
Hi, I have been playing around with your code, but there is one thing I can't work out. Where do you store the Saml Assertion until the service is really the make its backend call? I had assumed that you could store the Saml Assertion somewhere in the current OperationContext in the body of SamlSerializer.LoadAssertion method. However I have discovered that the operation context has not been setup when the serializer runs. Any thoughts?Anonymous
March 06, 2007
Hi, If you plug in your custom serializer described in the post then the SAML token will be parsed using the custom serializer and will be part of the OperationContext. Based on how the SAML token was used in the incoming message you can find it at OperationContet.Current.RequetContext.RequestMessage.Properties.Security.ProtectionToken or IncomingSupportingTokens collection. Thanks, GovindAnonymous
May 02, 2007
The comment has been removedAnonymous
May 03, 2007
Hi Ray, I have shown below how the SamlTokenProvider code can be implemented. Let me know if you have any more questions. public class SamlTokenProvider : SecurityTokenProvider { SamlSecurityToken token; public SamlTokenProvider(SamlSecurityToken token) { this.token = token; } protected override SecurityToken GetTokenCore(TimeSpan timeout) { return token; } }
- Govind
- Anonymous
May 03, 2007
Hi Ray, I have shown below how the SamlTokenProvider code can be implemented. Let me know if you have any more questions. public class SamlTokenProvider : SecurityTokenProvider { SamlSecurityToken token; public SamlTokenProvider(SamlSecurityToken token) { this.token = token; } protected override SecurityToken GetTokenCore(TimeSpan timeout) { return token; } }
- Govind
Anonymous
May 03, 2007
There has been a lot of interest around this and hence I have attached some code listing to this post.Anonymous
August 12, 2007
Is this scenario covered in WCF BETA 2 ? http://blogs.msdn.com/govindr/archive/2006/10/24/re-serialize-saml-token.aspxAnonymous
August 20, 2007
I think you are referring to .NET Fx 3.5. Yes this has been fixed in 3.5.Anonymous
December 14, 2007
When creating many services in SOA it's a common scenario that you need the SAML token to flow from oneAnonymous
December 14, 2007
When creating many services in SOA it's a common scenario that you need the SAML token to flow from oneAnonymous
March 02, 2008
You mention this scenario is fixed in 3.5. Exactly what part of the code you list would not be needed when using 3.5?Anonymous
June 17, 2008
in client i don't see the token being used? how to i access token in the client.Anonymous
June 26, 2008
Is there a way I can extract the SAMLAssertion from the OperationContex? )Similar to getting SecurityToken from OperationContext.Current.RequestContext.RequestMessage.Properties.Security.ProtectionToken.SecurityToken)Anonymous
October 24, 2008
The comment has been removedAnonymous
June 13, 2009
話題の小向美奈子ストリップを隠し撮り!入念なボディチェックをすり抜けて超小型カメラで撮影した神動画がアップ中!期間限定配信の衝撃的映像を見逃すな