Using a database cache to share SCTs in a farm, volume 1

Sorry I’ve gone dark for the last few days just as things were getting interesting and I had promised some code snippets. I decided to actually implement a solution using a distributed cache for SCT’s – I had implemented a proof-of-concept embedding state in the SCT already, so I assumed that doing a distributed cache approach would be even easier extrapolation of this work…well you know what the say about assumptions. In any case, it’s made this an interesting enough problem that I think I’ll write it up for MSDN in the future.

OK, well on to how to do a distributed approach. Last time I talked about the approach of using some sort of session affinity mechanism to ensure the client was always routed to the same service instance. This was the simplest approach, but had a number of drawbacks. The second approach we’ll examine is using a database cache.

Now to how we achieve this approach? My first theory was to override the token cache in the security token manager for the SCT to handle this situation, and write a new type of cache class for handling SCT's. In WSE, for each type of token handled there is an associated specialized security token manager that knows how to handle pretty much all aspects of managing that token type that is registered via the config file with the WSE runtime. The only requirement for a security token manager is that they implement the ISecurityTokenManager interface, however there is also a base SecurityTokenManager implementation that would more commonly be used if you are implementing your own custom tokens. In this case we don’t even need to do that – we really just need to derive from the SecurityContextTokenManager class, and override the TokenCache property. This part of the implementation is pretty trivial:

 

public class DistfibutedCacheSCTManager: SecurityContextTokenManager

{

DistributedTokenCache _cache;

public DistributedCacheSCTManager(): base()

{
_cache = new DistributedTokenCache();

}

protected override ISecurityTokenCache TokenCache

{

get

{

return _cache;

}

}

}

 

Then we define a distributed cache. In order to do this, I just derived from the WSE MRUSecurityTokenCache class (MRU == Most Recently Used). We still want to cache the token locally in addition to persisting it to a central database cache. This way we only have one database hit per SCT per service instance in the web farm maximum for each SCT that is established with a client (this assumes the token is never flushed from a local cache).

public class DistributedTokenCache: MRUSecurityTokenCache

{

       public DistributedTokenCache(int capacity): base(capacity)

       {

       }

       public override void Add(string identifier, SecurityToken token)

       {

              if(base.Contains(identifier) == false)

              {

                     base.Add(identifier, token);

                     SaveToStore(token);

              }

       }

       public override SecurityToken this[string identifier]

       {

              get

              {

                     SecurityToken token = base[identifier];

 

                     If(token == null)

                     {

                           token = ReadFromStore(identifier);

                           if(token != null)

                                 base.Add(token);

                     }

                     return token;

              |

       |

       public override bool Contains(string identifier)

       {

              bool containsToken = false;

containsToken = base.Contains(identifier) ;

 

              if(containsToken == false)

              {

                     SecurityToken token = ReadFromStore(identifier);

                    

                     if(token != null)

{

containsToken = true;

                           base.Add(identifier, token);

                     }

              }

              return containsToken;

       |

       provtected virtual void SaveToStore(SecurityToken token)

       {

              … persist the token

       }

       protected virtual SecurityToken ReadFromStore(identifier string)

       {

              … load the token

       }

}

In order to use the derived security context token manager, you need to register the manager with WSE. This is a simple config file entry:

<securityTokenManager type="SecureConvCodeService.DistributedCacheSCTManager, SecureConvCodeService" xmlns:wssc="https://schemas.xmlsoap.org/ws/2004/04/sc" qname="wssc:SecurityContextToken"/>

Next entry I’ll go into detail about saving and loading the token back from the database cache, and cover the first unanticipated issues I encountered with the implementation.