From 6899abbf45eec811a2ff2784344e2c2db475cc44 Mon Sep 17 00:00:00 2001 From: ciaozhang Date: Thu, 23 Feb 2023 14:48:54 -0800 Subject: [PATCH] Add _lastKnownGoodConfigurationCache into ctor --- .../JsonWebTokenHandler.cs | 4 +- .../Configuration/ConfigurationManager.cs | 31 +++++++++++- .../LastKnownGoodConfigurationCacheOptions.cs | 50 +++++++++++++++++++ .../StaticConfigurationManager.cs | 6 +++ .../BaseConfigurationManager.cs | 18 ++++--- .../TokenUtilities.cs | 13 ----- .../JwtSecurityTokenHandler.cs | 4 +- .../ConfigurationManagerTests.cs | 10 ++-- 8 files changed, 105 insertions(+), 31 deletions(-) create mode 100644 src/Microsoft.IdentityModel.Protocols/Configuration/LastKnownGoodConfigurationCacheOptions.cs diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs index 36ba3746f6..1dacba06ba 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs @@ -1258,13 +1258,13 @@ private async Task ValidateTokenAsync(JsonWebToken jsonWe } } - if (TokenUtilities.ShouldValidateWithLKG(validationParameters)) + if (validationParameters.ConfigurationManager.UseLastKnownGoodConfiguration) { validationParameters.RefreshBeforeValidation = false; validationParameters.ValidateWithLKG = true; var recoverableException = tokenValidationResult.Exception; - foreach (BaseConfiguration lkgConfiguration in validationParameters.ConfigurationManager.GetValidLkgConfiguraitons()) + foreach (BaseConfiguration lkgConfiguration in validationParameters.ConfigurationManager.GetValidLkgConfigurations()) { if (!lkgConfiguration.Equals(currentConfiguration) && TokenUtilities.IsRecoverableConfiguration(jsonWebToken.Kid, currentConfiguration, lkgConfiguration, recoverableException)) { diff --git a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs index d737734b88..1c020a3efa 100644 --- a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs +++ b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.Protocols.Configuration; using Microsoft.IdentityModel.Tokens; namespace Microsoft.IdentityModel.Protocols @@ -33,7 +34,7 @@ public class ConfigurationManager : BaseConfigurationManager, IConfigurationM /// Static initializer for a new object. Static initializers run before the first instance of the type is created. /// static ConfigurationManager() - { + { } /// @@ -81,6 +82,12 @@ public ConfigurationManager(string metadataAddress, IConfigurationRetriever c _docRetriever = docRetriever; _configRetriever = configRetriever; _refreshLock = new SemaphoreSlim(1); + + _lastKnownGoodConfigurationCache = new EventBasedLRUCache( + LastKnownGoodConfigurationCacheOptions.DefaultLastKnownGoodConfigurationSizeLimit, + TaskCreationOptions.None, + new BaseConfigurationComparer(), + true); } /// @@ -100,6 +107,28 @@ public ConfigurationManager(string metadataAddress, IConfigurationRetriever c _configValidator = configValidator; } + /// + /// Instantiates a new with cinfiguration validator that manages automatic and controls refreshing on configuration data. + /// + /// The address to obtain configuration. + /// The + /// The that reaches out to obtain the configuration. + /// The + /// The + /// If 'configValidator' is null. + public ConfigurationManager(string metadataAddress, IConfigurationRetriever configRetriever, IDocumentRetriever docRetriever, IConfigurationValidator configValidator, LastKnownGoodConfigurationCacheOptions lkgCacheOptions) + : this(metadataAddress, configRetriever, docRetriever, configValidator) + { + if (lkgCacheOptions == null) + throw LogHelper.LogArgumentNullException(nameof(lkgCacheOptions)); + + _lastKnownGoodConfigurationCache = new EventBasedLRUCache( + lkgCacheOptions.LastKnownGoodConfigurationSizeLimit, + TaskCreationOptions.None, + lkgCacheOptions.BaseConfigurationComparer, + true); + } + /// /// Obtains an updated version of Configuration. /// diff --git a/src/Microsoft.IdentityModel.Protocols/Configuration/LastKnownGoodConfigurationCacheOptions.cs b/src/Microsoft.IdentityModel.Protocols/Configuration/LastKnownGoodConfigurationCacheOptions.cs new file mode 100644 index 0000000000..b10f1666d9 --- /dev/null +++ b/src/Microsoft.IdentityModel.Protocols/Configuration/LastKnownGoodConfigurationCacheOptions.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; + +using Microsoft.IdentityModel.Tokens; +using Microsoft.IdentityModel.Logging; + +namespace Microsoft.IdentityModel.Protocols.Configuration +{ + /// + /// Specifies the LastKnownGoodConfigurationCacheOptions which can be used to configure the internal LKG configuration cache. + /// See for more details. + /// + public class LastKnownGoodConfigurationCacheOptions + { + private IEqualityComparer _baseConfigurationComparer = new BaseConfigurationComparer(); + private int _lastKnownGoodConfigurationSizeLimit = DefaultLastKnownGoodConfigurationSizeLimit; + + /// + /// 10 is the default size limit of the cache (in number of items) for last known good configuration. + /// + public static readonly int DefaultLastKnownGoodConfigurationSizeLimit = 10; + + /// + /// Gets or sets the BaseConfgiurationComparer that to compare . + /// + public IEqualityComparer BaseConfigurationComparer + { + get { return _baseConfigurationComparer; } + set + { + _baseConfigurationComparer = value ?? throw LogHelper.LogExceptionMessage(new ArgumentNullException(nameof(value))); + } + } + + /// + /// The size limit of the cache (in number of items) for last known good configuration. + /// + public int LastKnownGoodConfigurationSizeLimit + { + get { return _lastKnownGoodConfigurationSizeLimit; } + set + { + _lastKnownGoodConfigurationSizeLimit = (value > 0) ? value : throw LogHelper.LogExceptionMessage(new ArgumentOutOfRangeException(nameof(value))); + } + } + } +} diff --git a/src/Microsoft.IdentityModel.Protocols/Configuration/StaticConfigurationManager.cs b/src/Microsoft.IdentityModel.Protocols/Configuration/StaticConfigurationManager.cs index 1d5f66d777..0ee2b32f71 100644 --- a/src/Microsoft.IdentityModel.Protocols/Configuration/StaticConfigurationManager.cs +++ b/src/Microsoft.IdentityModel.Protocols/Configuration/StaticConfigurationManager.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.Protocols.Configuration; using Microsoft.IdentityModel.Tokens; using System; using System.Threading; @@ -28,6 +29,11 @@ public StaticConfigurationManager(T configuration) throw LogHelper.LogExceptionMessage(new ArgumentNullException(nameof(configuration), LogHelper.FormatInvariant(LogMessages.IDX20000, LogHelper.MarkAsNonPII(nameof(configuration))))); _configuration = configuration; + _lastKnownGoodConfigurationCache = new EventBasedLRUCache( + LastKnownGoodConfigurationCacheOptions.DefaultLastKnownGoodConfigurationSizeLimit, + TaskCreationOptions.None, + new BaseConfigurationComparer(), + true); } /// diff --git a/src/Microsoft.IdentityModel.Tokens/BaseConfigurationManager.cs b/src/Microsoft.IdentityModel.Tokens/BaseConfigurationManager.cs index 3fcb12d07d..999bbc4340 100644 --- a/src/Microsoft.IdentityModel.Tokens/BaseConfigurationManager.cs +++ b/src/Microsoft.IdentityModel.Tokens/BaseConfigurationManager.cs @@ -22,7 +22,9 @@ public abstract class BaseConfigurationManager private BaseConfiguration _lastKnownGoodConfiguration; private DateTime? _lastKnownGoodConfigFirstUse = null; - private EventBasedLRUCache _lkgConfigurationCache = null; + internal EventBasedLRUCache _lastKnownGoodConfigurationCache; + //private EventBasedLRUCache _lastKnownGoodConfigurationCache = new EventBasedLRUCache(10, TaskCreationOptions.None, new BaseConfigurationComparer(), true, maintainLRU: true); + private int _lastKnownGoodConfigurationSizeLimit = DefaultLastKnownGoodConfigurationSizeLimit; private IEqualityComparer _baseConfigurationComparer = new BaseConfigurationComparer(); @@ -90,12 +92,12 @@ public virtual Task GetBaseConfigurationAsync(CancellationTok /// Gets all valid last known good configurations. /// /// A collection of all valid last known good configurations. - internal ICollection GetValidLkgConfiguraitons() + internal ICollection GetValidLkgConfigurations() { - if (_lkgConfigurationCache == null) + if (_lastKnownGoodConfigurationCache == null) return null; - return _lkgConfigurationCache.ToArray().Where(x => x.Value.Value > DateTime.UtcNow).Select(x => x.Key).ToArray(); + return _lastKnownGoodConfigurationCache.ToArray().Where(x => x.Value.Value > DateTime.UtcNow).Select(x => x.Key).ToArray(); } /// @@ -112,11 +114,11 @@ public BaseConfiguration LastKnownGoodConfiguration _lastKnownGoodConfiguration = value ?? throw LogHelper.LogArgumentNullException(nameof(value)); _lastKnownGoodConfigFirstUse = DateTime.UtcNow; - if (_lkgConfigurationCache == null) - _lkgConfigurationCache = new EventBasedLRUCache(LastKnownGoodConfigurationSizeLimit, TaskCreationOptions.None, BaseConfigurationComparer, true, maintainLRU: true); - + if (_lastKnownGoodConfigurationCache == null) + _lastKnownGoodConfigurationCache = new EventBasedLRUCache(LastKnownGoodConfigurationSizeLimit, TaskCreationOptions.None, BaseConfigurationComparer, true, maintainLRU: true); + // LRU cache will remove the expired configuration - _lkgConfigurationCache.SetValue(_lastKnownGoodConfiguration, DateTime.UtcNow + LastKnownGoodLifetime, DateTime.UtcNow + LastKnownGoodLifetime); + _lastKnownGoodConfigurationCache.SetValue(_lastKnownGoodConfiguration, DateTime.UtcNow + LastKnownGoodLifetime, DateTime.UtcNow + LastKnownGoodLifetime); } } diff --git a/src/Microsoft.IdentityModel.Tokens/TokenUtilities.cs b/src/Microsoft.IdentityModel.Tokens/TokenUtilities.cs index dd31a65393..971c5788e1 100644 --- a/src/Microsoft.IdentityModel.Tokens/TokenUtilities.cs +++ b/src/Microsoft.IdentityModel.Tokens/TokenUtilities.cs @@ -233,18 +233,5 @@ internal static bool IsRecoverableConfiguration(string kid, BaseConfiguration cu return false; } - - /// - /// Check if the token can be validated by LKG. - /// - /// The to be used for validation. - /// true if the token can be validated by LKG, false. - internal static bool ShouldValidateWithLKG(TokenValidationParameters validationParameters) - { - ICollection validLkgConfiguraitons = validationParameters.ConfigurationManager.GetValidLkgConfiguraitons(); - return (validationParameters.ConfigurationManager.UseLastKnownGoodConfiguration - && validLkgConfiguraitons != null - && validLkgConfiguraitons.Any()); - } } } diff --git a/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs b/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs index 9d64235b27..5796da1036 100644 --- a/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs +++ b/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs @@ -929,7 +929,7 @@ private ClaimsPrincipal ValidateToken(string token, JwtSecurityToken outerToken, } } - if (TokenUtilities.ShouldValidateWithLKG(validationParameters)) + if (validationParameters.ConfigurationManager.UseLastKnownGoodConfiguration) { validationParameters.RefreshBeforeValidation = false; validationParameters.ValidateWithLKG = true; @@ -937,7 +937,7 @@ private ClaimsPrincipal ValidateToken(string token, JwtSecurityToken outerToken, string kid = outerToken != null ? outerToken.Header.Kid : (ValidateSignatureUsingDelegates(token, validationParameters, null) ?? GetJwtSecurityTokenFromToken(token, validationParameters)).Header.Kid; - foreach (BaseConfiguration lkgConfiguration in validationParameters.ConfigurationManager.GetValidLkgConfiguraitons()) + foreach (BaseConfiguration lkgConfiguration in validationParameters.ConfigurationManager.GetValidLkgConfigurations()) { if (!lkgConfiguration.Equals(currentConfiguration) && TokenUtilities.IsRecoverableConfiguration(kid, currentConfiguration, lkgConfiguration, recoverableException)) { diff --git a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs index fd9a95255d..fbeb4cb125 100644 --- a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs +++ b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs @@ -393,19 +393,19 @@ public void TestConfigurationComparer() configWithSameKidDiffKeyMaterial.SigningKeys.Add(new SymmetricSecurityKey(KeyingMaterial.DefaultSymmetricSecurityKey_128.Key) { KeyId = KeyingMaterial.DefaultSymmetricSecurityKey_256.KeyId }); var configurationManager = new MockConfigurationManager(config, config); - IdentityComparer.AreEqual(configurationManager.GetValidLkgConfiguraitons().Count, 1, context); + IdentityComparer.AreEqual(configurationManager.GetValidLkgConfigurations().Count, 1, context); configurationManager.LastKnownGoodConfiguration = configWithSameKeysDiffOrder; - IdentityComparer.AreEqual(configurationManager.GetValidLkgConfiguraitons().Count, 1, context); + IdentityComparer.AreEqual(configurationManager.GetValidLkgConfigurations().Count, 1, context); configurationManager.LastKnownGoodConfiguration = configWithOverlappingKey; - IdentityComparer.AreEqual(configurationManager.GetValidLkgConfiguraitons().Count, 2, context); + IdentityComparer.AreEqual(configurationManager.GetValidLkgConfigurations().Count, 2, context); configurationManager.LastKnownGoodConfiguration = configWithOverlappingKeyDiffissuer; - IdentityComparer.AreEqual(configurationManager.GetValidLkgConfiguraitons().Count, 3, context); + IdentityComparer.AreEqual(configurationManager.GetValidLkgConfigurations().Count, 3, context); configurationManager.LastKnownGoodConfiguration = configWithSameKidDiffKeyMaterial; - IdentityComparer.AreEqual(configurationManager.GetValidLkgConfiguraitons().Count, 4, context); + IdentityComparer.AreEqual(configurationManager.GetValidLkgConfigurations().Count, 4, context); TestUtilities.AssertFailIfErrors(context); }