我们的网站使用ADFS进行验证。为了减少每个请求的cookie有效负载,我们正在转动IsSessionMode(参见
Your fedauth cookies on a diet)。
我们需要做的最后一件事是让我们在负载平衡的环境中工作,就是实现一个农场准备好的SecurityTokenCache。实现看起来很简单,我主要感兴趣的是发现在处理SecurityTokenCacheKey和TryGetAllEntries和TryRemoveAllEntries方法时,我们应该考虑什么问题(SecurityTokenCacheKey具有Equals和GetHashCode方法的自定义实现)。
有人有这个例子吗?我们正在计划使用AppFabric作为后备存储,但使用任何持久存储的示例将是有用的 – 数据库表,Azure表存储等。
以下是我搜索过的一些地方:
>在Hervey Wilson’s PDC09
session他使用a
DatabaseSecurityTokenCache。我找不到样本
他的会话代码
> Vittorio Bertocci的出色的第192页
他写道,“编程Windows身份基金会”他提到上传
Azure的一个示例实现ReadyTokenCache到
书的网站。我还没有找到这个样本。
谢谢!
JD
3/16/2012更新
Vittorio’s blog链接到一个示例使用新的.net 4.5的东西:
ClaimsAwareWebFarm
这个样本是我们从许多人得到的反馈的答案:你想要一个示例显示一个农场准备好的会话缓存(而不是一个tokenreplycache),以便您可以通过引用使用会话而不是交换大的cookie;您要求在农场中更容易地保护Cookie。
解决方法
为了想出一个工作实现,我们最终不得不使用反射器来分析Microsoft.IdentityModel中不同的SessionSecurityToken相关的类。下面是我们想出来的这个实现部署在我们的开发环境和qa环境中,似乎工作正常,它对应用程序池的循环利用是很重要的。
在全球:
protected void Application_Start(object sender,EventArgs e) { FederatedAuthentication.ServiceConfigurationCreated += this.OnServiceConfigurationCreated; } private void OnServiceConfigurationCreated(object sender,ServiceConfigurationCreatedEventArgs e) { var sessionTransforms = new List<CookieTransform>(new CookieTransform[] { new DeflateCookieTransform(),new RSAEncryptionCookieTransform( e.ServiceConfiguration.ServiceCertificate),new RsaSignatureCookieTransform( e.ServiceConfiguration.ServiceCertificate) }); // following line is pseudo code. use your own durable cache implementation. var durableCache = new AppFabricCacheWrapper(); var tokenCache = new DurableSecurityTokenCache(durableCache,5000); var sessionHandler = new SessionSecurityTokenHandler(sessionTransforms.AsReadOnly(),tokenCache,TimeSpan.FromDays(1)); e.ServiceConfiguration.SecurityTokenHandlers.AddOrReplace(sessionHandler); } private void WSFederationAuthenticationModule_SecurityTokenValidated(object sender,SecurityTokenValidatedEventArgs e) { FederatedAuthentication.SessionAuthenticationModule.IsSessionMode = true; }
DurableSecurityTokenCache.cs:
/// <summary> /// Two level durable security token cache (level 1: in memory MRU,level 2: out of process cache). /// </summary> public class DurableSecurityTokenCache : SecurityTokenCache { private ICache<string,byte[]> durableCache; private readonly MruCache<SecurityTokenCacheKey,SecurityToken> mruCache; /// <summary> /// The constructor. /// </summary> /// <param name="durableCache">The durable second level cache (should be out of process ie sql server,azure table,app fabric,etc).</param> /// <param name="mruCapacity">Capacity of the internal first level cache (in-memory MRU cache).</param> public DurableSecurityTokenCache(ICache<string,byte[]> durableCache,int mruCapacity) { this.durableCache = durableCache; this.mruCache = new MruCache<SecurityTokenCacheKey,SecurityToken>(mruCapacity,mruCapacity / 4); } public override bool TryAddEntry(object key,SecurityToken value) { var cacheKey = (SecurityTokenCacheKey)key; // add the entry to the mru cache. this.mruCache.Add(cacheKey,value); // add the entry to the durable cache. var keyString = GetKeyString(cacheKey); var buffer = this.GetSerializer().Serialize((SessionSecurityToken)value); this.durableCache.Add(keyString,buffer); return true; } public override bool TryGetEntry(object key,out SecurityToken value) { var cacheKey = (SecurityTokenCacheKey)key; // attempt to retrieve the entry from the mru cache. value = this.mruCache.Get(cacheKey); if (value != null) return true; // entry wasn't in the mru cache,retrieve it from the app fabric cache. var keyString = GetKeyString(cacheKey); var buffer = this.durableCache.Get(keyString); var result = buffer != null; if (result) { // we had a cache miss in the mru cache but found the item in the durable cache... // deserialize the value retrieved from the durable cache. value = this.GetSerializer().Deserialize(buffer); // push this item into the mru cache. this.mruCache.Add(cacheKey,value); } return result; } public override bool TryRemoveEntry(object key) { var cacheKey = (SecurityTokenCacheKey)key; // remove the entry from the mru cache. this.mruCache.Remove(cacheKey); // remove the entry from the durable cache. var keyString = GetKeyString(cacheKey); this.durableCache.Remove(keyString); return true; } public override bool TryReplaceEntry(object key,SecurityToken newValue) { var cacheKey = (SecurityTokenCacheKey)key; // remove the entry in the mru cache. this.mruCache.Remove(cacheKey); // remove the entry in the durable cache. var keyString = GetKeyString(cacheKey); // add the new value. return this.TryAddEntry(key,newValue); } public override bool TryGetAllEntries(object key,out IList<SecurityToken> tokens) { // not implemented... haven't been able to find how/when this method is used. tokens = new List<SecurityToken>(); return true; //throw new NotImplementedException(); } public override bool TryRemoveAllEntries(object key) { // not implemented... haven't been able to find how/when this method is used. return true; //throw new NotImplementedException(); } public override void ClearEntries() { // not implemented... haven't been able to find how/when this method is used. //throw new NotImplementedException(); } /// <summary> /// Gets the string representation of the specified SecurityTokenCacheKey. /// </summary> private string GetKeyString(SecurityTokenCacheKey key) { return string.Format("{0}; {1}; {2}",key.ContextId,key.KeyGeneration,key.EndpointId); } /// <summary> /// Gets a new instance of the token serializer. /// </summary> private SessionSecurityTokenCookieSerializer GetSerializer() { return new SessionSecurityTokenCookieSerializer(); // may need to do something about handling bootstrap tokens. } }
MruCache.cs:
/// <summary> /// Most recently used (MRU) cache. /// </summary> /// <typeparam name="TKey">The key type.</typeparam> /// <typeparam name="TValue">The value type.</typeparam> public class MruCache<TKey,TValue> : ICache<TKey,TValue> { private Dictionary<TKey,TValue> mruCache; private LinkedList<TKey> mruList; private object syncRoot; private int capacity; private int sizeAfterPurge; /// <summary> /// The constructor. /// </summary> /// <param name="capacity">The capacity.</param> /// <param name="sizeAfterPurge">Size to make the cache after purging because it's reached capacity.</param> public MruCache(int capacity,int sizeAfterPurge) { this.mruList = new LinkedList<TKey>(); this.mruCache = new Dictionary<TKey,TValue>(capacity); this.capacity = capacity; this.sizeAfterPurge = sizeAfterPurge; this.syncRoot = new object(); } /// <summary> /// Adds an item if it doesn't already exist. /// </summary> public void Add(TKey key,TValue value) { lock (this.syncRoot) { if (mruCache.ContainsKey(key)) return; if (mruCache.Count + 1 >= this.capacity) { while (mruCache.Count > this.sizeAfterPurge) { var lru = mruList.Last.Value; mruCache.Remove(lru); mruList.RemoveLast(); } } mruCache.Add(key,value); mruList.AddFirst(key); } } /// <summary> /// Removes an item if it exists. /// </summary> public void Remove(TKey key) { lock (this.syncRoot) { if (!mruCache.ContainsKey(key)) return; mruCache.Remove(key); mruList.Remove(key); } } /// <summary> /// Gets an item. If a matching item doesn't exist null is returned. /// </summary> public TValue Get(TKey key) { lock (this.syncRoot) { if (!mruCache.ContainsKey(key)) return default(TValue); mruList.Remove(key); mruList.AddFirst(key); return mruCache[key]; } } /// <summary> /// Gets whether a key is contained in the cache. /// </summary> public bool ContainsKey(TKey key) { lock (this.syncRoot) return mruCache.ContainsKey(key); } }
ICache.cs:
/// <summary> /// A cache. /// </summary> /// <typeparam name="TKey">The key type.</typeparam> /// <typeparam name="TValue">The value type.</typeparam> public interface ICache<TKey,TValue> { void Add(TKey key,TValue value); void Remove(TKey key); TValue Get(TKey key); }