From a1ca5979f87d725a820f97f2a6b2dd76f440ad31 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Mon, 13 Jul 2020 17:57:35 +0800 Subject: [PATCH 1/8] Cache the AccessToken in IdentityModelAuthenticationService. Resolve #4603 --- .../Volo.Abp.IdentityModel.csproj | 1 + .../IdentityModel/AbpIdentityModelModule.cs | 4 +- .../IdentityModelAuthenticationService.cs | 45 ++++++++++++++----- .../IdentityModelTokenCacheItem.cs | 29 ++++++++++++ 4 files changed, 68 insertions(+), 11 deletions(-) create mode 100644 framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelTokenCacheItem.cs diff --git a/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj b/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj index 0143db8265..1fb77b36d9 100644 --- a/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj +++ b/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj @@ -17,6 +17,7 @@ + diff --git a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/AbpIdentityModelModule.cs b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/AbpIdentityModelModule.cs index 7ab97c49fe..bf499e7b5f 100644 --- a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/AbpIdentityModelModule.cs +++ b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/AbpIdentityModelModule.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Caching; using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; @@ -7,7 +8,8 @@ namespace Volo.Abp.IdentityModel { [DependsOn( typeof(AbpThreadingModule), - typeof(AbpMultiTenancyModule) + typeof(AbpMultiTenancyModule), + typeof(AbpCachingModule) )] public class AbpIdentityModelModule : AbpModule { diff --git a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs index 5e8c67a9ab..c20d473f81 100644 --- a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs +++ b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs @@ -10,6 +10,8 @@ using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Distributed; +using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; @@ -26,18 +28,21 @@ namespace Volo.Abp.IdentityModel protected IHttpClientFactory HttpClientFactory { get; } protected ICurrentTenant CurrentTenant { get; } protected IdentityModelHttpRequestMessageOptions IdentityModelHttpRequestMessageOptions { get; } + protected IDistributedCache Cache { get; } public IdentityModelAuthenticationService( IOptions options, ICancellationTokenProvider cancellationTokenProvider, IHttpClientFactory httpClientFactory, ICurrentTenant currentTenant, - IOptions identityModelHttpRequestMessageOptions) + IOptions identityModelHttpRequestMessageOptions, + IDistributedCache cache) { ClientOptions = options.Value; CancellationTokenProvider = cancellationTokenProvider; HttpClientFactory = httpClientFactory; CurrentTenant = currentTenant; + Cache = cache; IdentityModelHttpRequestMessageOptions = identityModelHttpRequestMessageOptions.Value; Logger = NullLogger.Instance; } @@ -76,21 +81,36 @@ namespace Volo.Abp.IdentityModel throw new AbpException($"Could not retrieve the OpenId Connect discovery document! ErrorType: {discoveryResponse.ErrorType}. Error: {discoveryResponse.Error}"); } - var tokenResponse = await GetTokenResponse(discoveryResponse, configuration); - - if (tokenResponse.IsError) + var cacheKey = CalculateCacheKey(discoveryResponse, configuration); + var tokenCacheItem = await Cache.GetAsync(cacheKey); + if (tokenCacheItem == null) { - if (tokenResponse.ErrorDescription != null) + var tokenResponse = await GetTokenResponse(discoveryResponse, configuration); + + if (tokenResponse.IsError) { - throw new AbpException($"Could not get token from the OpenId Connect server! ErrorType: {tokenResponse.ErrorType}. Error: {tokenResponse.Error}. ErrorDescription: {tokenResponse.ErrorDescription}. HttpStatusCode: {tokenResponse.HttpStatusCode}"); + if (tokenResponse.ErrorDescription != null) + { + throw new AbpException($"Could not get token from the OpenId Connect server! ErrorType: {tokenResponse.ErrorType}. " + + $"Error: {tokenResponse.Error}. ErrorDescription: {tokenResponse.ErrorDescription}. HttpStatusCode: {tokenResponse.HttpStatusCode}"); + } + + var rawError = tokenResponse.Raw; + var withoutInnerException = rawError.Split(new string[] { "" }, StringSplitOptions.RemoveEmptyEntries); + throw new AbpException(withoutInnerException[0]); } - var rawError = tokenResponse.Raw; - var withoutInnerException = rawError.Split(new string[] { "" }, StringSplitOptions.RemoveEmptyEntries); - throw new AbpException(withoutInnerException[0]); + await Cache.SetAsync(cacheKey, new IdentityModelTokenCacheItem(tokenResponse.AccessToken), + new DistributedCacheEntryOptions() + { + //Subtract 10 seconds of network request time. + AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(tokenResponse.ExpiresIn - 10) + }); + + return tokenResponse.AccessToken; } - return tokenResponse.AccessToken; + return tokenCacheItem.AccessToken; } protected virtual void SetAccessToken(HttpClient client, string accessToken) @@ -209,5 +229,10 @@ namespace Volo.Abp.IdentityModel client.DefaultRequestHeaders.Add(TenantResolverConsts.DefaultTenantKey, CurrentTenant.Id.Value.ToString()); } } + + protected virtual string CalculateCacheKey(DiscoveryDocumentResponse discoveryResponse, IdentityClientConfiguration configuration) + { + return IdentityModelTokenCacheItem.CalculateCacheKey(discoveryResponse, configuration); + } } } diff --git a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelTokenCacheItem.cs b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelTokenCacheItem.cs new file mode 100644 index 0000000000..8d01b83c32 --- /dev/null +++ b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelTokenCacheItem.cs @@ -0,0 +1,29 @@ +using System; +using System.Linq; +using IdentityModel.Client; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.IdentityModel +{ + [Serializable] + [IgnoreMultiTenancy] + public class IdentityModelTokenCacheItem + { + public string AccessToken { get; set; } + + public IdentityModelTokenCacheItem() + { + + } + + public IdentityModelTokenCacheItem(string accessToken) + { + AccessToken = accessToken; + } + + public static string CalculateCacheKey(DiscoveryDocumentResponse discoveryResponse, IdentityClientConfiguration configuration) + { + return discoveryResponse.TokenEndpoint + string.Join(",", configuration.Select(x => x.Key + ":" + x.Value)); + } + } +} From c509499ac402f96882d6eab37306a23b3d1df0c5 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Fri, 17 Jul 2020 15:57:36 +0800 Subject: [PATCH 2/8] Cache Identity Server discover document. --- .../IdentityModelAuthenticationService.cs | 72 +++++++++++++------ ...IdentityModelDiscoveryDocumentCacheItem.cs | 27 +++++++ .../IdentityModelTokenCacheItem.cs | 5 +- 3 files changed, 80 insertions(+), 24 deletions(-) create mode 100644 framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelDiscoveryDocumentCacheItem.cs diff --git a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs index c20d473f81..5d22d0c7a5 100644 --- a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs +++ b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs @@ -28,7 +28,8 @@ namespace Volo.Abp.IdentityModel protected IHttpClientFactory HttpClientFactory { get; } protected ICurrentTenant CurrentTenant { get; } protected IdentityModelHttpRequestMessageOptions IdentityModelHttpRequestMessageOptions { get; } - protected IDistributedCache Cache { get; } + protected IDistributedCache TokenCache { get; } + protected IDistributedCache DiscoveryDocumentCache { get; } public IdentityModelAuthenticationService( IOptions options, @@ -36,13 +37,15 @@ namespace Volo.Abp.IdentityModel IHttpClientFactory httpClientFactory, ICurrentTenant currentTenant, IOptions identityModelHttpRequestMessageOptions, - IDistributedCache cache) + IDistributedCache tokenCache, + IDistributedCache discoveryDocumentCache) { ClientOptions = options.Value; CancellationTokenProvider = cancellationTokenProvider; HttpClientFactory = httpClientFactory; CurrentTenant = currentTenant; - Cache = cache; + TokenCache = tokenCache; + DiscoveryDocumentCache = discoveryDocumentCache; IdentityModelHttpRequestMessageOptions = identityModelHttpRequestMessageOptions.Value; Logger = NullLogger.Instance; } @@ -75,17 +78,13 @@ namespace Volo.Abp.IdentityModel public virtual async Task GetAccessTokenAsync(IdentityClientConfiguration configuration) { - var discoveryResponse = await GetDiscoveryResponse(configuration); - if (discoveryResponse.IsError) - { - throw new AbpException($"Could not retrieve the OpenId Connect discovery document! ErrorType: {discoveryResponse.ErrorType}. Error: {discoveryResponse.Error}"); - } + var tokenEndpoint = await GetTokenEndpoint(configuration); - var cacheKey = CalculateCacheKey(discoveryResponse, configuration); - var tokenCacheItem = await Cache.GetAsync(cacheKey); + var cacheKey = CalculateTokenCacheKey(configuration); + var tokenCacheItem = await TokenCache.GetAsync(cacheKey); if (tokenCacheItem == null) { - var tokenResponse = await GetTokenResponse(discoveryResponse, configuration); + var tokenResponse = await GetTokenResponse(tokenEndpoint, configuration); if (tokenResponse.IsError) { @@ -100,7 +99,7 @@ namespace Volo.Abp.IdentityModel throw new AbpException(withoutInnerException[0]); } - await Cache.SetAsync(cacheKey, new IdentityModelTokenCacheItem(tokenResponse.AccessToken), + await TokenCache.SetAsync(cacheKey, new IdentityModelTokenCacheItem(tokenResponse.AccessToken), new DistributedCacheEntryOptions() { //Subtract 10 seconds of network request time. @@ -130,6 +129,32 @@ namespace Volo.Abp.IdentityModel ClientOptions.IdentityClients.Default; } + protected virtual async Task GetTokenEndpoint(IdentityClientConfiguration configuration) + { + //TODO: Can use (configuration.Authority + /connect/token) directly? + + var tokenEndpointUrlCacheKey = CalculateDiscoveryDocumentCacheKey(configuration); + var discoveryDocumentCacheItem = await DiscoveryDocumentCache.GetAsync(tokenEndpointUrlCacheKey); + if (discoveryDocumentCacheItem == null) + { + var discoveryResponse = await GetDiscoveryResponse(configuration); + if (discoveryResponse.IsError) + { + throw new AbpException($"Could not retrieve the OpenId Connect discovery document! " + + $"ErrorType: {discoveryResponse.ErrorType}. Error: {discoveryResponse.Error}"); + } + + discoveryDocumentCacheItem = new IdentityModelDiscoveryDocumentCacheItem(discoveryResponse.TokenEndpoint); + await DiscoveryDocumentCache.SetAsync(tokenEndpointUrlCacheKey, discoveryDocumentCacheItem, + new DistributedCacheEntryOptions + { + SlidingExpiration = TimeSpan.FromMinutes(30) + }); + } + + return discoveryDocumentCacheItem.TokenEndpoint; + } + protected virtual async Task GetDiscoveryResponse( IdentityClientConfiguration configuration) { @@ -149,7 +174,7 @@ namespace Volo.Abp.IdentityModel } protected virtual async Task GetTokenResponse( - DiscoveryDocumentResponse discoveryResponse, + string tokenEndpoint, IdentityClientConfiguration configuration) { using (var httpClient = HttpClientFactory.CreateClient(HttpClientName)) @@ -160,12 +185,12 @@ namespace Volo.Abp.IdentityModel { case OidcConstants.GrantTypes.ClientCredentials: return await httpClient.RequestClientCredentialsTokenAsync( - await CreateClientCredentialsTokenRequestAsync(discoveryResponse, configuration), + await CreateClientCredentialsTokenRequestAsync(tokenEndpoint, configuration), CancellationTokenProvider.Token ); case OidcConstants.GrantTypes.Password: return await httpClient.RequestPasswordTokenAsync( - await CreatePasswordTokenRequestAsync(discoveryResponse, configuration), + await CreatePasswordTokenRequestAsync(tokenEndpoint, configuration), CancellationTokenProvider.Token ); default: @@ -174,11 +199,11 @@ namespace Volo.Abp.IdentityModel } } - protected virtual Task CreatePasswordTokenRequestAsync(DiscoveryDocumentResponse discoveryResponse, IdentityClientConfiguration configuration) + protected virtual Task CreatePasswordTokenRequestAsync(string tokenEndpoint, IdentityClientConfiguration configuration) { var request = new PasswordTokenRequest { - Address = discoveryResponse.TokenEndpoint, + Address = tokenEndpoint, Scope = configuration.Scope, ClientId = configuration.ClientId, ClientSecret = configuration.ClientSecret, @@ -193,12 +218,12 @@ namespace Volo.Abp.IdentityModel } protected virtual Task CreateClientCredentialsTokenRequestAsync( - DiscoveryDocumentResponse discoveryResponse, + string tokenEndpoint, IdentityClientConfiguration configuration) { var request = new ClientCredentialsTokenRequest { - Address = discoveryResponse.TokenEndpoint, + Address = tokenEndpoint, Scope = configuration.Scope, ClientId = configuration.ClientId, ClientSecret = configuration.ClientSecret @@ -230,9 +255,14 @@ namespace Volo.Abp.IdentityModel } } - protected virtual string CalculateCacheKey(DiscoveryDocumentResponse discoveryResponse, IdentityClientConfiguration configuration) + protected virtual string CalculateDiscoveryDocumentCacheKey(IdentityClientConfiguration configuration) + { + return IdentityModelDiscoveryDocumentCacheItem.CalculateCacheKey(configuration); + } + + protected virtual string CalculateTokenCacheKey(IdentityClientConfiguration configuration) { - return IdentityModelTokenCacheItem.CalculateCacheKey(discoveryResponse, configuration); + return IdentityModelTokenCacheItem.CalculateCacheKey(configuration); } } } diff --git a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelDiscoveryDocumentCacheItem.cs b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelDiscoveryDocumentCacheItem.cs new file mode 100644 index 0000000000..3a07cb3735 --- /dev/null +++ b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelDiscoveryDocumentCacheItem.cs @@ -0,0 +1,27 @@ +using System; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.IdentityModel +{ + [Serializable] + [IgnoreMultiTenancy] + public class IdentityModelDiscoveryDocumentCacheItem + { + public string TokenEndpoint { get; set; } + + public IdentityModelDiscoveryDocumentCacheItem() + { + + } + + public IdentityModelDiscoveryDocumentCacheItem(string tokenEndpoint) + { + TokenEndpoint = tokenEndpoint; + } + + public static string CalculateCacheKey(IdentityClientConfiguration configuration) + { + return configuration.Authority.ToLower().ToMd5(); + } + } +} diff --git a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelTokenCacheItem.cs b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelTokenCacheItem.cs index 8d01b83c32..e8f8dfb8db 100644 --- a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelTokenCacheItem.cs +++ b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelTokenCacheItem.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using IdentityModel.Client; using Volo.Abp.MultiTenancy; namespace Volo.Abp.IdentityModel @@ -21,9 +20,9 @@ namespace Volo.Abp.IdentityModel AccessToken = accessToken; } - public static string CalculateCacheKey(DiscoveryDocumentResponse discoveryResponse, IdentityClientConfiguration configuration) + public static string CalculateCacheKey(IdentityClientConfiguration configuration) { - return discoveryResponse.TokenEndpoint + string.Join(",", configuration.Select(x => x.Key + ":" + x.Value)); + return string.Join(",", configuration.Select(x => x.Key + ":" + x.Value)).ToMd5(); } } } From c5bfdb7053fbf02bb4efff6997b5e3da772e997a Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Fri, 17 Jul 2020 17:03:02 +0800 Subject: [PATCH 3/8] Add CacheAbsoluteExpiration to IdentityClientConfiguration. --- .../IdentityClientConfiguration.cs | 25 ++++++++++++---- .../IdentityModelAuthenticationService.cs | 29 +++++++------------ 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityClientConfiguration.cs b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityClientConfiguration.cs index 17040ff1db..d831c28808 100644 --- a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityClientConfiguration.cs +++ b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityClientConfiguration.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using IdentityModel; namespace Volo.Abp.IdentityModel @@ -81,21 +82,32 @@ namespace Volo.Abp.IdentityModel get => this.GetOrDefault(nameof(RequireHttps))?.To() ?? true; set => this[nameof(RequireHttps)] = value.ToString().ToLowerInvariant(); } - + + /// + /// Cache absolute expiration + /// Default: 30 minutes. + /// + public double CacheAbsoluteExpiration + { + get => this.GetOrDefault(nameof(CacheAbsoluteExpiration ))?.To() ?? 60 * 30; + set => this[nameof(CacheAbsoluteExpiration)] = value.ToString(CultureInfo.InvariantCulture); + } + public IdentityClientConfiguration() { - + } public IdentityClientConfiguration( string authority, string scope, - string clientId, - string clientSecret, + string clientId, + string clientSecret, string grantType = OidcConstants.GrantTypes.ClientCredentials, string userName = null, string userPassword = null, - bool requireHttps = true) + bool requireHttps = true, + double cacheAbsoluteExpiration = 60 * 30) { this[nameof(Authority)] = authority; this[nameof(Scope)] = scope; @@ -105,6 +117,7 @@ namespace Volo.Abp.IdentityModel this[nameof(UserName)] = userName; this[nameof(UserPassword)] = userPassword; this[nameof(RequireHttps)] = requireHttps.ToString().ToLowerInvariant(); + this[nameof(CacheAbsoluteExpiration)] = cacheAbsoluteExpiration.ToString(CultureInfo.InvariantCulture); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs index 5d22d0c7a5..0fb4c32648 100644 --- a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs +++ b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs @@ -78,13 +78,11 @@ namespace Volo.Abp.IdentityModel public virtual async Task GetAccessTokenAsync(IdentityClientConfiguration configuration) { - var tokenEndpoint = await GetTokenEndpoint(configuration); - var cacheKey = CalculateTokenCacheKey(configuration); var tokenCacheItem = await TokenCache.GetAsync(cacheKey); if (tokenCacheItem == null) { - var tokenResponse = await GetTokenResponse(tokenEndpoint, configuration); + var tokenResponse = await GetTokenResponse(configuration); if (tokenResponse.IsError) { @@ -99,14 +97,12 @@ namespace Volo.Abp.IdentityModel throw new AbpException(withoutInnerException[0]); } - await TokenCache.SetAsync(cacheKey, new IdentityModelTokenCacheItem(tokenResponse.AccessToken), - new DistributedCacheEntryOptions() + tokenCacheItem = new IdentityModelTokenCacheItem(tokenResponse.AccessToken); + await TokenCache.SetAsync(cacheKey, tokenCacheItem, + new DistributedCacheEntryOptions { - //Subtract 10 seconds of network request time. - AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(tokenResponse.ExpiresIn - 10) + AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(configuration.CacheAbsoluteExpiration) }); - - return tokenResponse.AccessToken; } return tokenCacheItem.AccessToken; @@ -148,15 +144,14 @@ namespace Volo.Abp.IdentityModel await DiscoveryDocumentCache.SetAsync(tokenEndpointUrlCacheKey, discoveryDocumentCacheItem, new DistributedCacheEntryOptions { - SlidingExpiration = TimeSpan.FromMinutes(30) + AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(configuration.CacheAbsoluteExpiration) }); } return discoveryDocumentCacheItem.TokenEndpoint; } - protected virtual async Task GetDiscoveryResponse( - IdentityClientConfiguration configuration) + protected virtual async Task GetDiscoveryResponse(IdentityClientConfiguration configuration) { using (var httpClient = HttpClientFactory.CreateClient(HttpClientName)) { @@ -173,10 +168,10 @@ namespace Volo.Abp.IdentityModel } } - protected virtual async Task GetTokenResponse( - string tokenEndpoint, - IdentityClientConfiguration configuration) + protected virtual async Task GetTokenResponse(IdentityClientConfiguration configuration) { + var tokenEndpoint = await GetTokenEndpoint(configuration); + using (var httpClient = HttpClientFactory.CreateClient(HttpClientName)) { AddHeaders(httpClient); @@ -217,9 +212,7 @@ namespace Volo.Abp.IdentityModel return Task.FromResult(request); } - protected virtual Task CreateClientCredentialsTokenRequestAsync( - string tokenEndpoint, - IdentityClientConfiguration configuration) + protected virtual Task CreateClientCredentialsTokenRequestAsync(string tokenEndpoint, IdentityClientConfiguration configuration) { var request = new ClientCredentialsTokenRequest { From df8ad3bcbc97223c777e43e361a68a2019d45747 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Tue, 11 Aug 2020 17:30:55 +0800 Subject: [PATCH 4/8] Update Volo.Abp.IdentityModel.csproj --- .../src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj b/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj index 1fb77b36d9..b96da9be3e 100644 --- a/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj +++ b/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj @@ -16,7 +16,7 @@ - + From 1f72c489a4ee408e88dac1142173045173b318b7 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Tue, 11 Aug 2020 17:31:54 +0800 Subject: [PATCH 5/8] Update Volo.Abp.IdentityModel.csproj --- .../src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj | 3 --- 1 file changed, 3 deletions(-) diff --git a/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj b/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj index 3cdc6f6c98..b96da9be3e 100644 --- a/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj +++ b/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj @@ -17,10 +17,7 @@ -<<<<<<< maliming/CachetheAccessToken -======= ->>>>>>> dev From 2eb0913378932a85a5817766f83f5351f01a68d3 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Wed, 12 Aug 2020 11:18:03 +0800 Subject: [PATCH 6/8] Use integer instead of double. --- .../Abp/IdentityModel/IdentityClientConfiguration.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityClientConfiguration.cs b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityClientConfiguration.cs index d831c28808..5f2f4af573 100644 --- a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityClientConfiguration.cs +++ b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityClientConfiguration.cs @@ -84,12 +84,12 @@ namespace Volo.Abp.IdentityModel } /// - /// Cache absolute expiration - /// Default: 30 minutes. + /// Absolute expiration duration (as seconds) for the access token cache. + /// Default: 1800 seconds (30 minutes) /// - public double CacheAbsoluteExpiration + public int CacheAbsoluteExpiration { - get => this.GetOrDefault(nameof(CacheAbsoluteExpiration ))?.To() ?? 60 * 30; + get => this.GetOrDefault(nameof(CacheAbsoluteExpiration ))?.To() ?? 60 * 30; set => this[nameof(CacheAbsoluteExpiration)] = value.ToString(CultureInfo.InvariantCulture); } @@ -107,7 +107,7 @@ namespace Volo.Abp.IdentityModel string userName = null, string userPassword = null, bool requireHttps = true, - double cacheAbsoluteExpiration = 60 * 30) + int cacheAbsoluteExpiration = 60 * 30) { this[nameof(Authority)] = authority; this[nameof(Scope)] = scope; From d529c16af6378d1daedeaa7b3f2c93c6f91e5f4d Mon Sep 17 00:00:00 2001 From: Alper Ebicoglu Date: Wed, 12 Aug 2020 16:39:34 +0300 Subject: [PATCH 7/8] add Chrome login issue article --- .../POST.md | 209 ++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 docs/en/Community-Articles/2020-08-12-Patch-Chrome-Login-Issue-For-IdentityServer4/POST.md diff --git a/docs/en/Community-Articles/2020-08-12-Patch-Chrome-Login-Issue-For-IdentityServer4/POST.md b/docs/en/Community-Articles/2020-08-12-Patch-Chrome-Login-Issue-For-IdentityServer4/POST.md new file mode 100644 index 0000000000..a4c40196f2 --- /dev/null +++ b/docs/en/Community-Articles/2020-08-12-Patch-Chrome-Login-Issue-For-IdentityServer4/POST.md @@ -0,0 +1,209 @@ +# How to fix the Chrome login issue for the IdentityServer4 + +## Introduction + +When you use HTTP on your Identity Server 4 enabled website, users may not login because of the changes made by Chrome in the version 8x. This occurs when you use HTTP schema in your website. The issue is explained here https://docs.microsoft.com/en-gb/dotnet/core/compatibility/3.0-3.1#http-browser-samesite-changes-impact-authentication + +## How to solve it? + +### Step-1 + +Create the below extension in your ***.Web** project. + +```csharp +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class SameSiteCookiesServiceCollectionExtensions + { + /// + /// -1 defines the unspecified value, which tells ASPNET Core to NOT + /// send the SameSite attribute. With ASPNET Core 3.1 the + /// enum will have a definition for + /// Unspecified. + /// + private const SameSiteMode Unspecified = (SameSiteMode)(-1); + + /// + /// Configures a cookie policy to properly set the SameSite attribute + /// for Browsers that handle unknown values as Strict. Ensure that you + /// add the + /// into the pipeline before sending any cookies! + /// + /// + /// Minimum ASPNET Core Version required for this code: + /// - 2.1.14 + /// - 2.2.8 + /// - 3.0.1 + /// - 3.1.0-preview1 + /// Starting with version 80 of Chrome (to be released in February 2020) + /// cookies with NO SameSite attribute are treated as SameSite=Lax. + /// In order to always get the cookies send they need to be set to + /// SameSite=None. But since the current standard only defines Lax and + /// Strict as valid values there are some browsers that treat invalid + /// values as SameSite=Strict. We therefore need to check the browser + /// and either send SameSite=None or prevent the sending of SameSite=None. + /// Relevant links: + /// - https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-4.1 + /// - https://tools.ietf.org/html/draft-west-cookie-incrementalism-00 + /// - https://www.chromium.org/updates/same-site + /// - https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/ + /// - https://bugs.webkit.org/show_bug.cgi?id=198181 + /// + /// The service collection to register into. + /// The modified . + public static IServiceCollection ConfigureNonBreakingSameSiteCookies(this IServiceCollection services) + { + services.Configure(options => + { + options.MinimumSameSitePolicy = Unspecified; + options.OnAppendCookie = cookieContext => + CheckSameSite(cookieContext.Context, cookieContext.CookieOptions); + options.OnDeleteCookie = cookieContext => + CheckSameSite(cookieContext.Context, cookieContext.CookieOptions); + }); + + return services; + } + + private static void CheckSameSite(HttpContext httpContext, CookieOptions options) + { + if (options.SameSite == SameSiteMode.None) + { + var userAgent = httpContext.Request.Headers["User-Agent"].ToString(); + + if (DisallowsSameSiteNone(userAgent)) + { + options.SameSite = Unspecified; + } + } + } + + /// + /// Checks if the UserAgent is known to interpret an unknown value as Strict. + /// For those the property should be + /// set to . + /// + /// + /// This code is taken from Microsoft: + /// https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/ + /// + /// The user agent string to check. + /// Whether the specified user agent (browser) accepts SameSite=None or not. + private static bool DisallowsSameSiteNone(string userAgent) + { + // Cover all iOS based browsers here. This includes: + // - Safari on iOS 12 for iPhone, iPod Touch, iPad + // - WkWebview on iOS 12 for iPhone, iPod Touch, iPad + // - Chrome on iOS 12 for iPhone, iPod Touch, iPad + // All of which are broken by SameSite=None, because they use the + // iOS networking stack. + // Notes from Thinktecture: + // Regarding https://caniuse.com/#search=samesite iOS versions lower + // than 12 are not supporting SameSite at all. Starting with version 13 + // unknown values are NOT treated as strict anymore. Therefore we only + // need to check version 12. + if (userAgent.Contains("CPU iPhone OS 12") + || userAgent.Contains("iPad; CPU OS 12")) + { + return true; + } + + // Cover Mac OS X based browsers that use the Mac OS networking stack. + // This includes: + // - Safari on Mac OS X. + // This does not include: + // - Chrome on Mac OS X + // because they do not use the Mac OS networking stack. + // Notes from Thinktecture: + // Regarding https://caniuse.com/#search=samesite MacOS X versions lower + // than 10.14 are not supporting SameSite at all. Starting with version + // 10.15 unknown values are NOT treated as strict anymore. Therefore we + // only need to check version 10.14. + if (userAgent.Contains("Safari") + && userAgent.Contains("Macintosh; Intel Mac OS X 10_14") + && userAgent.Contains("Version/")) + { + return true; + } + + // Cover Chrome 50-69, because some versions are broken by SameSite=None + // and none in this range require it. + // Note: this covers some pre-Chromium Edge versions, + // but pre-Chromium Edge does not require SameSite=None. + // Notes from Thinktecture: + // We can not validate this assumption, but we trust Microsofts + // evaluation. And overall not sending a SameSite value equals to the same + // behavior as SameSite=None for these old versions anyways. + if (userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6")) + { + return true; + } + + if (GetChromeVersion(userAgent) >= 80) + { + return true; + } + + return false; + } + + private static int GetChromeVersion(string userAgent) + { + try + { + return Convert.ToInt32(userAgent.Split("Chrome/")[1].Split('.')[0]); + } + catch (Exception) + { + return 0; + } + } + } +} +``` + +### Step-2 + +Assume that your project name is *Acme.BookStore*. Then open `AcmeBookStoreWebModule.cs` class. + +Add the following line to `ConfigureServices()` method. + +```csharp + context.Services.ConfigureNonBreakingSameSiteCookies(); +``` +### Step-3 + +Go to`OnApplicationInitialization()` method in `AcmeBookStoreWebModule.cs` add `app.UseCookiePolicy();` + +```csharp +public override void OnApplicationInitialization(ApplicationInitializationContext context) +{ + var app = context.GetApplicationBuilder(); + var env = context.GetEnvironment(); + + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseErrorPage(); + app.UseHsts(); + } + + app.UseCookiePolicy(); //<--- added this ---> +``` + + + +It's all! You are ready to go! + + + +--- + +Referenced from https://www.thinktecture.com/en/identity/samesite/prepare-your-identityserver/ \ No newline at end of file From 9adab77257b31d40e4e4b1ae826a813d075efe4e Mon Sep 17 00:00:00 2001 From: Alper Ebicoglu Date: Wed, 12 Aug 2020 16:44:29 +0300 Subject: [PATCH 8/8] Update POST.md --- .../POST.md | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/docs/en/Community-Articles/2020-08-12-Patch-Chrome-Login-Issue-For-IdentityServer4/POST.md b/docs/en/Community-Articles/2020-08-12-Patch-Chrome-Login-Issue-For-IdentityServer4/POST.md index a4c40196f2..718a949f3a 100644 --- a/docs/en/Community-Articles/2020-08-12-Patch-Chrome-Login-Issue-For-IdentityServer4/POST.md +++ b/docs/en/Community-Articles/2020-08-12-Patch-Chrome-Login-Issue-For-IdentityServer4/POST.md @@ -182,20 +182,23 @@ Go to`OnApplicationInitialization()` method in `AcmeBookStoreWebModule.cs` add ` ```csharp public override void OnApplicationInitialization(ApplicationInitializationContext context) { - var app = context.GetApplicationBuilder(); - var env = context.GetEnvironment(); - - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - app.UseErrorPage(); - app.UseHsts(); - } - - app.UseCookiePolicy(); //<--- added this ---> + var app = context.GetApplicationBuilder(); + var env = context.GetEnvironment(); + + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseErrorPage(); + app.UseHsts(); + } + + app.UseCookiePolicy(); //<--- added this ---> + + //.... +} ``` @@ -206,4 +209,4 @@ It's all! You are ready to go! --- -Referenced from https://www.thinktecture.com/en/identity/samesite/prepare-your-identityserver/ \ No newline at end of file +Referenced from https://www.thinktecture.com/en/identity/samesite/prepare-your-identityserver/