Azure/ веб-ферма готова SecurityTokenCache
Наш сайт использует ADFS для аутентификации. Чтобы уменьшить полезную нагрузку на куки-файлы при каждом запросе, мы включаем IsSessionMode (см. " Ваши куки-файлы fedauth на диете").
Последнее, что нам нужно сделать, чтобы это работало в нашей среде с балансировкой нагрузки, - это реализовать готовый к работе фермер SecurityTokenCache. Реализация кажется довольно простой, в основном меня интересует, есть ли какие-то ошибки, которые следует учитывать при работе с методами SecurityTokenCacheKey и TryGetAllEntries и TryRemoveAllEntries (SecurityTokenCacheKey имеет собственную реализацию методов Equals и GetHashCode).
У кого-нибудь есть пример этого? Мы планируем использовать AppFabric в качестве резервного хранилища, но в качестве примера можно использовать любое постоянное хранилище - таблица базы данных, хранилище таблиц Azure и т. Д.
Вот несколько мест, которые я искал:
- В сеансе PDC09 Херви Уилсона он использует DatabaseSecurityTokenCache. Я не смог найти пример кода для его сессии.
- На странице 192 замечательной книги Витторио Берточчи "Программирование Windows Identity Foundation" он упоминает о загрузке примера реализации готового Azure SecurityTokenCache на веб-сайт книги. Я также не смог найти этот образец.
Спасибо!
юлианский день
16.03.2012 ОБНОВЛЕНИЕ В блоге Витторио есть ссылки на образец с использованием нового материала.net 4.5:
ClaimsAwareWebFarm Этот пример является ответом на отзывы, которые мы получили от многих из вас, ребята: вы хотели получить пример, показывающий кэш сеансов, готовый для фермы (в отличие от tokenreplycache), чтобы вы могли использовать сеансы по ссылке вместо обмена большими файлами cookie; и вы попросили более простой способ защиты файлов cookie на ферме.
2 ответа
Чтобы создать работающую реализацию, нам в конечном итоге пришлось использовать рефлектор для анализа различных классов, связанных с SessionSecurityToken, в Microsoft.IdentityModel. Ниже то, что мы придумали. Эта реализация развернута в наших средах dev и qa, кажется, работает нормально, она устойчива к повторным пулам приложений и т. Д.
В 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);
}
Вот образец, который я написал. Я использую 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>