Azure / готовый для веб-фермы SecurityTokenCache

#azure #wif #web-farm #adfs #geneva-framework

#azure #wif #веб-ферма #adfs #женева-фреймворк

Вопрос:

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

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

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

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

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

Спасибо!

jd

Обновление в блоге Витторио
от 16.03.2012 содержит ссылки на образец с использованием нового материала .net 4.5:

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

Комментарии:

1. Этот вопрос также был опубликован на женевском форуме .

2. Если вы используете .net 4.5, есть лучшее решение: code.msdn.microsoft.com/vstudio/Claims-Aware-Web-Farm-088a7a4f

Ответ №1:

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

В глобальном.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;
}
  

Надежный кэш-накопитель securitytokencache.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 resu<
    }

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

Комментарии:

1. Спасибо за этот код. Определенно подтвердил подход, который я использовал при реализации.

2. jdanyow, я тоже изучаю ту же проблему. Была ли ваша реализация развернута на PROD? Кроме того, нужно ли мне добавлять в web.config для работы, или он работает как есть? Код Web.config, на который я ссылаюсь, таков: <system.IdentityModel> <identityConfiguration> <tokenReplayDetection> <Тип кэша воспроизведения =’ПОЛНОЕ имя вашего типа’ /> </tokenReplayDetection> </identityConfiguration> </system.IdentityModel>

3. @Echiban- да, это было развернуто в рабочей среде. никаких изменений в web.config не требуется

Ответ №2:

Вот пример, который я написал. Я использую 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>
  

Комментарии:

1. спасибо за помощь, я искал что-то, что унаследовано от абстрактного класса SecurityTokenCache.