2011-11-04 45 views
17

我们的网站使用ADFS进行身份验证。为了减少每个请求上的Cookie负载,我们将IsSessionMode开启(请参阅Your fedauth cookies on a diet)。Azure /网络农场就绪SecurityTokenCache

我们需要做的最后一件事是在我们的负载平衡环境中实现这个工作,就是实现一个准备好农场的SecurityTokenCache。实现看起来非常简单,我主要感兴趣的是在处理SecurityTokenCacheKey和TryGetAllEntries和TryRemoveAllEntries方法时(SecurityTokenCacheKey具有Equals和GetHashCode方法的自定义实现),我们应该考虑是否有任何问题。

有没有人有这样的例子?我们计划使用的AppFabric作为后备存储,但是使用任何持久性存储的例子是helpful-数据库表,Azure的表存储等

这里有一些地方我搜索:

  • Hervey Wilson's PDC09 session中,他使用了一个 DatabaseSecurityTokenCache。我一直无法找到他的会议代码示例 。
  • 在Vittorio Bertocci的优秀 书籍“编程Windows身份基础”第192页上,他提到了将 Azure准备好的SecurityTokenCache的示例实现上传到 本书的网站。我还没能找到这个样本。

谢谢!

JD

2012年3月16日更新 Vittorio's blog链接到一个样品使用新的.NET 4.5的东西:

ClaimsAwareWebFarm 此示例是一个答案,我们从众多的得到反馈你们:你想要一个显示农场准备好会话缓存的示例(而不是一个tokenreplycache),这样你就可以通过引用来使用会话,而不是交换大的cookie;而且你问了一个更简单的方法来保证农场中的cookies的安全。

+0

这个问题也被张贴在[日内瓦论坛](http://social.msdn.microsoft.com/Forums/en-US/日内瓦/线程/ a74117a8-2981-498e-8d2f-b95cd55a0e46)。 –

+0

如果您使用.net 4.5,有更好的解决方案: http://code.msdn.microsoft.com/vstudio/Claims-Aware-Web-Farm-088a7a4f –

回答

15

要拿出一个工作实现我们最终不得不使用反射来分析微软不同SessionSecurityToken相关的类.IdentityModel。以下是我们想出的。此实现部署在我们的开发和QA环境,似乎是做工精细,它的resiliant到应用程序池回收等

在Global.asax中:

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); 
} 
+0

感谢此代码。肯定地证实了我在实施中采取的方法。 – codeprogression

+0

jdanyow,我正在调查同样的问题。您的实施是否部署在PROD上?另外,我需要添加到web.config工作,或者它的工作原理?的web.config代码我指的是: Echiban

+0

@ Echiban- yep这已被部署到生产中。没有web.config更改必要 –

3

这是我写的一个样本。我使用Windows Azure永久存储令牌,从而击败任何可能的重播。

http://tokenreplaycache.codeplex.com/releases/view/76652

您需要把这个在你的web.config:

<service> 
     <securityTokenHandlers> 
     <securityTokenHandlerConfiguration saveBootstrapTokens="true"> 
      <tokenReplayDetection enabled="true" expirationPeriod="50" purgeInterval="1"> 
      <replayCache type="LC.Security.AzureTokenReplayCache.ACSTokenReplayCache,LC.Security.AzureTokenReplayCache, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> 
      </tokenReplayDetection> 
     </securityTokenHandlerConfiguration> 
     </securityTokenHandlers> 
    </service> 
+0

感谢您的帮助,我一直在寻找继承自AbstractTokenCache类。 –