Sdílet prostřednictvím


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.

 

ReSerializeSaml.zip

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, Bob

  • Anonymous
    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 removed

  • Anonymous
    February 06, 2007
    The comment has been removed

  • Anonymous
    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, Govind

  • Anonymous
    May 02, 2007
    The comment has been removed

  • 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
    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.aspx

  • Anonymous
    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 one

  • Anonymous
    December 14, 2007
    When creating many services in SOA it's a common scenario that you need the SAML token to flow from one

  • Anonymous
    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 removed

  • Anonymous
    June 13, 2009
    話題の小向美奈子ストリップを隠し撮り!入念なボディチェックをすり抜けて超小型カメラで撮影した神動画がアップ中!期間限定配信の衝撃的映像を見逃すな