Few people have contacted me regarding follow-up on my last
post so this post continues where the last one left off.
To keep the API simple, I have created few extension
methods. Let’s first review these.
public
static class Extensions
{
public static void EnableSamlTokenCaching(this
ServiceHostBase source)
{
var orignal =
source.Description.Behaviors.Remove<ServiceCredentials>();
source.Description.Behaviors.Add(new ServiceCredentialsEx(orignal));
}
public static SamlSecurityToken GetIncommingSamlToken(this ServiceSecurityContext
source)
{
var policy =
source.AuthorizationPolicies.OfType<SamlTokenCachingPolicy>().FirstOrDefault();
if (policy != null)
return policy.IncommingSamlToken;
return null;
}
public static void EnableSamlTokenFlow<TChannel>(this ClientBase<TChannel>
source, SamlSecurityToken tokenToFlow) where TChannel : class
{
var orignal =
source.Endpoint.Behaviors.Remove<ClientCredentials>();
source.Endpoint.Behaviors.Add(new ClientCredentialsWrapper(orignal));
}
}
EnableSamlTokenCaching is what you have to call to enable
SAML token caching functionality for a particular ServiceHost. Here is
an example of a simple host (created using a custom factory (IIS/WAS hosted
scenario)) being configured with this API
public
class EchoServiceHostFactory
: ServiceHostFactoryBase
{
public override
ServiceHostBase CreateServiceHost(string constructorString, Uri[]
baseAddresses)
{
var sh = new ServiceHost(typeof(EchoService), baseAddresses);
sh.EnableSamlTokenCaching();
return sh;
}
}
The highlighted call simply injects couple of customized
components (interceptors) into WCF’s security framework. Specifically it
is injecting a new customized SamlSecurityTokenAuthenticator,
which wraps the original authenticator and simply delegates all calls to it.
SamlSecurityTokenAuthenticator is called
during secure conversation handshake. Here our custom authenticator (SamlSecurityTokenAuthenticatorEx)
enables us to get hold of the incoming SAML token etc.
public
class ServiceCredentialsEx
: ServiceCredentials
{
public ServiceCredentialsEx(ServiceCredentials other)
: base(other){}
public override
SecurityTokenManager
CreateSecurityTokenManager()
{
return new ServiceCredentialsSecurityTokenManagerEx(this);
}
protected override
ServiceCredentials CloneCore()
{
return new ServiceCredentialsEx(this);
}
}
public
class ServiceCredentialsSecurityTokenManagerEx
: ServiceCredentialsSecurityTokenManager
{
public
ServiceCredentialsSecurityTokenManagerEx(ServiceCredentials
parent)
: base(parent){}
public override
SecurityTokenAuthenticator
CreateSecurityTokenAuthenticator(SecurityTokenRequirement
tokenRequirement, out SecurityTokenResolver
outOfBandTokenResolver)
{
var authenticator = base.CreateSecurityTokenAuthenticator(tokenRequirement,
out outOfBandTokenResolver);
if (authenticator is
SamlSecurityTokenAuthenticator)
return new
SamlSecurityTokenAuthenticatorEx((SamlSecurityTokenAuthenticator)authenticator);
return authenticator;
}
}
Inside SamlSecurityTokenAuthenticatorEx, I’m simply
creating an additional token caching policy and adding it to the collection
of policies created by default SAML token authenticator.
public
class SamlSecurityTokenAuthenticatorEx
: SamlSecurityTokenAuthenticator
{
SamlSecurityTokenAuthenticator wrapped;
public SamlSecurityTokenAuthenticatorEx(SamlSecurityTokenAuthenticator wrapped)
: base(new List<SecurityTokenAuthenticator>())
{
this.wrapped = wrapped;
}
protected override
ReadOnlyCollection<IAuthorizationPolicy> ValidateTokenCore(SecurityToken token)
{
var orignalPolicies =
wrapped.ValidateToken(token);
List<IAuthorizationPolicy>
finalPolicies = new List<IAuthorizationPolicy>(orignalPolicies);
finalPolicies.Add(new SamlTokenCachingPolicy((SamlSecurityToken)token));
return finalPolicies.AsReadOnly();
}
}
My SamlTokenCachingPolicy is just a wrapped
around the cached token.
public
class SamlTokenCachingPolicy
: IAuthorizationPolicy
{
public const string PolicyID = "{32835E28-3ED4-42d4-A2EA-FA71E13AF51F}";
public SamlTokenCachingPolicy(SamlSecurityToken token)
{
this.IncommingSamlToken = token;
}
public bool
Evaluate(EvaluationContext
evaluationContext, ref object
state)
{
return true;
}
public ClaimSet
Issuer
{
get { throw new System.NotImplementedException();
}
}
public string
Id
{
get { return
PolicyID; }
}
public SamlSecurityToken
IncommingSamlToken { get; private set; }
}
IAuthorizationPolicy model nicely aligns with WCF security
framework (so I have used it here). If you are not comfortable with this
approach, you could get the same results by choosing a different caching
strategy.
Inside your service method you can get hold of the incoming
SAML token using following piece of code.
[ServiceBehavior(AddressFilterMode = AddressFilterMode.Prefix)]
public
class EchoService
: IEchoService
{
public string
Echo(string input)
{
// Get token from incoming security context..
var samlToken = OperationContext.Current.ServiceSecurityContext.GetIncommingSamlToken();
}
}
GetIncommingSamlToken is again an extension which
extracts the actual token from my custom AuthorizationPolicy(SamlTokenCachingPolicy).
public static SamlSecurityToken GetIncommingSamlToken(this ServiceSecurityContext
source)
{
var policy = source.AuthorizationPolicies.OfType<SamlTokenCachingPolicy>().FirstOrDefault();
if
(policy != null)
return
policy.IncommingSamlToken;
return null;
}
Oh, this post is already way too long. Probably next time I
will show you, how to pass this token to backend services (sharing the same
certificate).