Готовность к Azure/веб-ферме SecurityTokenCache

Наш сайт использует ADFS для аутентификации. Чтобы уменьшить объем файлов cookie при каждом запросе, мы включаем IsSessionMode (см. Ваши файлы cookie fedauth на диете).

Последнее, что нам нужно сделать, чтобы это заработало в нашей среде с балансировкой нагрузки, — это реализовать готовый к ферме SecurityTokenCache. Реализация кажется довольно простой, меня в основном интересует, есть ли какие-либо ошибки, которые мы должны учитывать при работе с SecurityTokenCacheKey и методами TryGetAllEntries и TryRemoveAllEntries (SecurityTokenCacheKey имеет собственную реализацию методов Equals и GetHashCode).

У кого-нибудь есть пример этого? Мы планируем использовать AppFabric в качестве резервного хранилища, но будет полезен пример использования любого постоянного хранилища — таблицы базы данных, хранилища таблиц Azure и т. д.

Вот некоторые места, которые я искал:

  • В сеансе PDC09 Херви Уилсона он использует DatabaseSecurityTokenCache. Я не смог найти пример кода для его сеанса.
  • На странице 192 превосходной книги Витторио Берточчи «Программирование Windows Identity Foundation» он упоминает о загрузке примера реализации готового к Azure SecurityTokenCache на веб-сайт книги. Я тоже не смог найти этот образец.

Спасибо!

jd

ОБНОВЛЕНИЕ 16 марта 2012 г. блог Витторио ссылается на образец, использующий новый материал .net 4.5:

ClaimsAwareWebFarm Этот образец является ответом на отзывы, которые мы получили от многих из вас, ребята. : вам нужен образец, показывающий кеш сеансов, готовых к работе фермы (в отличие от кэша реплик реплик), чтобы вы могли использовать сеансы по ссылке вместо обмена большими файлами cookie; и вы просили более простой способ защиты файлов cookie на ферме.


person Jeremy Danyow    schedule 04.11.2011    source источник
comment
Этот вопрос также был опубликован на женевский форум.   -  person Jeremy Danyow    schedule 05.11.2011
comment
Если вы используете .net 4.5, есть лучшее решение: code .msdn.microsoft.com/vstudio/Claims-Aware-Web-Farm-088a7a4f   -  person Jeremy Danyow    schedule 16.03.2012


Ответы (2)


Чтобы придумать работающую реализацию, нам в конечном итоге пришлось использовать рефлектор для анализа различных классов, связанных с SessionSecurityToken, в Microsoft.IdentityModel. Ниже то, что у нас получилось. Эта реализация развернута в наших средах разработки и контроля качества, работает нормально, устойчива к повторному использованию пула приложений и т. д.

В 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);
}
person Jeremy Danyow    schedule 10.11.2011
comment
Спасибо за этот код. Определенно подтвердил подход, который я использовал в своей реализации. - person codeprogression; 15.05.2012
comment
jdanyow, я тоже изучаю ту же проблему. Была ли ваша реализация развернута в PROD? Кроме того, мне нужно добавить в web.config для работы, или он работает как есть? Я имею в виду код Web.config: ‹system.identityModel› ‹identityConfiguration› ‹tokenReplayDetection› ‹replayCache type='FQTN вашего типа' /› ‹/tokenReplayDetection› ‹/identityConfiguration› ‹/system.identityModel› - person Echiban; 10.07.2012
comment
@Echiban- да, это было развернуто в производстве. никаких изменений web.config не требуется - person Jeremy Danyow; 13.08.2012

Вот образец, который я написал. Я использую 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>
person halfbit    schedule 10.11.2011
comment
спасибо за помощь, я искал что-то, что унаследовано от абстрактного класса SecurityTokenCache. - person Jeremy Danyow; 11.11.2011