2011-11-04 46 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類。 –