Browse Source

Merge pull request #4742 from abpframework/maliming/CachetheAccessToken

Cache the AccessToken in IdentityModelAuthenticationService.
pull/5037/head
Halil İbrahim Kalkan 6 years ago
committed by GitHub
parent
commit
9aa165d4e9
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj
  2. 4
      framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/AbpIdentityModelModule.cs
  3. 25
      framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityClientConfiguration.cs
  4. 104
      framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs
  5. 27
      framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelDiscoveryDocumentCacheItem.cs
  6. 28
      framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelTokenCacheItem.cs

1
framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj

@ -17,6 +17,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="IdentityModel" Version="4.3.0" /> <PackageReference Include="IdentityModel" Version="4.3.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.6" /> <PackageReference Include="Microsoft.Extensions.Http" Version="3.1.6" />
<ProjectReference Include="..\Volo.Abp.Caching\Volo.Abp.Caching.csproj" />
<ProjectReference Include="..\Volo.Abp.MultiTenancy\Volo.Abp.MultiTenancy.csproj" /> <ProjectReference Include="..\Volo.Abp.MultiTenancy\Volo.Abp.MultiTenancy.csproj" />
<ProjectReference Include="..\Volo.Abp.Threading\Volo.Abp.Threading.csproj" /> <ProjectReference Include="..\Volo.Abp.Threading\Volo.Abp.Threading.csproj" />
</ItemGroup> </ItemGroup>

4
framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/AbpIdentityModelModule.cs

@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Caching;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading; using Volo.Abp.Threading;
@ -7,7 +8,8 @@ namespace Volo.Abp.IdentityModel
{ {
[DependsOn( [DependsOn(
typeof(AbpThreadingModule), typeof(AbpThreadingModule),
typeof(AbpMultiTenancyModule) typeof(AbpMultiTenancyModule),
typeof(AbpCachingModule)
)] )]
public class AbpIdentityModelModule : AbpModule public class AbpIdentityModelModule : AbpModule
{ {

25
framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityClientConfiguration.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using IdentityModel; using IdentityModel;
namespace Volo.Abp.IdentityModel namespace Volo.Abp.IdentityModel
@ -81,21 +82,32 @@ namespace Volo.Abp.IdentityModel
get => this.GetOrDefault(nameof(RequireHttps))?.To<bool>() ?? true; get => this.GetOrDefault(nameof(RequireHttps))?.To<bool>() ?? true;
set => this[nameof(RequireHttps)] = value.ToString().ToLowerInvariant(); set => this[nameof(RequireHttps)] = value.ToString().ToLowerInvariant();
} }
/// <summary>
/// Absolute expiration duration (as seconds) for the access token cache.
/// Default: 1800 seconds (30 minutes)
/// </summary>
public int CacheAbsoluteExpiration
{
get => this.GetOrDefault(nameof(CacheAbsoluteExpiration ))?.To<int>() ?? 60 * 30;
set => this[nameof(CacheAbsoluteExpiration)] = value.ToString(CultureInfo.InvariantCulture);
}
public IdentityClientConfiguration() public IdentityClientConfiguration()
{ {
} }
public IdentityClientConfiguration( public IdentityClientConfiguration(
string authority, string authority,
string scope, string scope,
string clientId, string clientId,
string clientSecret, string clientSecret,
string grantType = OidcConstants.GrantTypes.ClientCredentials, string grantType = OidcConstants.GrantTypes.ClientCredentials,
string userName = null, string userName = null,
string userPassword = null, string userPassword = null,
bool requireHttps = true) bool requireHttps = true,
int cacheAbsoluteExpiration = 60 * 30)
{ {
this[nameof(Authority)] = authority; this[nameof(Authority)] = authority;
this[nameof(Scope)] = scope; this[nameof(Scope)] = scope;
@ -105,6 +117,7 @@ namespace Volo.Abp.IdentityModel
this[nameof(UserName)] = userName; this[nameof(UserName)] = userName;
this[nameof(UserPassword)] = userPassword; this[nameof(UserPassword)] = userPassword;
this[nameof(RequireHttps)] = requireHttps.ToString().ToLowerInvariant(); this[nameof(RequireHttps)] = requireHttps.ToString().ToLowerInvariant();
this[nameof(CacheAbsoluteExpiration)] = cacheAbsoluteExpiration.ToString(CultureInfo.InvariantCulture);
} }
} }
} }

104
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;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading; using Volo.Abp.Threading;
@ -26,18 +28,24 @@ namespace Volo.Abp.IdentityModel
protected IHttpClientFactory HttpClientFactory { get; } protected IHttpClientFactory HttpClientFactory { get; }
protected ICurrentTenant CurrentTenant { get; } protected ICurrentTenant CurrentTenant { get; }
protected IdentityModelHttpRequestMessageOptions IdentityModelHttpRequestMessageOptions { get; } protected IdentityModelHttpRequestMessageOptions IdentityModelHttpRequestMessageOptions { get; }
protected IDistributedCache<IdentityModelTokenCacheItem> TokenCache { get; }
protected IDistributedCache<IdentityModelDiscoveryDocumentCacheItem> DiscoveryDocumentCache { get; }
public IdentityModelAuthenticationService( public IdentityModelAuthenticationService(
IOptions<AbpIdentityClientOptions> options, IOptions<AbpIdentityClientOptions> options,
ICancellationTokenProvider cancellationTokenProvider, ICancellationTokenProvider cancellationTokenProvider,
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
ICurrentTenant currentTenant, ICurrentTenant currentTenant,
IOptions<IdentityModelHttpRequestMessageOptions> identityModelHttpRequestMessageOptions) IOptions<IdentityModelHttpRequestMessageOptions> identityModelHttpRequestMessageOptions,
IDistributedCache<IdentityModelTokenCacheItem> tokenCache,
IDistributedCache<IdentityModelDiscoveryDocumentCacheItem> discoveryDocumentCache)
{ {
ClientOptions = options.Value; ClientOptions = options.Value;
CancellationTokenProvider = cancellationTokenProvider; CancellationTokenProvider = cancellationTokenProvider;
HttpClientFactory = httpClientFactory; HttpClientFactory = httpClientFactory;
CurrentTenant = currentTenant; CurrentTenant = currentTenant;
TokenCache = tokenCache;
DiscoveryDocumentCache = discoveryDocumentCache;
IdentityModelHttpRequestMessageOptions = identityModelHttpRequestMessageOptions.Value; IdentityModelHttpRequestMessageOptions = identityModelHttpRequestMessageOptions.Value;
Logger = NullLogger<IdentityModelAuthenticationService>.Instance; Logger = NullLogger<IdentityModelAuthenticationService>.Instance;
} }
@ -70,27 +78,34 @@ namespace Volo.Abp.IdentityModel
public virtual async Task<string> GetAccessTokenAsync(IdentityClientConfiguration configuration) public virtual async Task<string> GetAccessTokenAsync(IdentityClientConfiguration configuration)
{ {
var discoveryResponse = await GetDiscoveryResponse(configuration); var cacheKey = CalculateTokenCacheKey(configuration);
if (discoveryResponse.IsError) var tokenCacheItem = await TokenCache.GetAsync(cacheKey);
if (tokenCacheItem == null)
{ {
throw new AbpException($"Could not retrieve the OpenId Connect discovery document! ErrorType: {discoveryResponse.ErrorType}. Error: {discoveryResponse.Error}"); var tokenResponse = await GetTokenResponse(configuration);
}
var tokenResponse = await GetTokenResponse(discoveryResponse, configuration);
if (tokenResponse.IsError) if (tokenResponse.IsError)
{
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}"); 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[] { "<eof/>" }, StringSplitOptions.RemoveEmptyEntries);
throw new AbpException(withoutInnerException[0]);
} }
var rawError = tokenResponse.Raw; tokenCacheItem = new IdentityModelTokenCacheItem(tokenResponse.AccessToken);
var withoutInnerException = rawError.Split(new string[] { "<eof/>" }, StringSplitOptions.RemoveEmptyEntries); await TokenCache.SetAsync(cacheKey, tokenCacheItem,
throw new AbpException(withoutInnerException[0]); new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(configuration.CacheAbsoluteExpiration)
});
} }
return tokenResponse.AccessToken; return tokenCacheItem.AccessToken;
} }
protected virtual void SetAccessToken(HttpClient client, string accessToken) protected virtual void SetAccessToken(HttpClient client, string accessToken)
@ -110,8 +125,33 @@ namespace Volo.Abp.IdentityModel
ClientOptions.IdentityClients.Default; ClientOptions.IdentityClients.Default;
} }
protected virtual async Task<DiscoveryDocumentResponse> GetDiscoveryResponse( protected virtual async Task<string> GetTokenEndpoint(IdentityClientConfiguration configuration)
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
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(configuration.CacheAbsoluteExpiration)
});
}
return discoveryDocumentCacheItem.TokenEndpoint;
}
protected virtual async Task<DiscoveryDocumentResponse> GetDiscoveryResponse(IdentityClientConfiguration configuration)
{ {
using (var httpClient = HttpClientFactory.CreateClient(HttpClientName)) using (var httpClient = HttpClientFactory.CreateClient(HttpClientName))
{ {
@ -128,10 +168,10 @@ namespace Volo.Abp.IdentityModel
} }
} }
protected virtual async Task<TokenResponse> GetTokenResponse( protected virtual async Task<TokenResponse> GetTokenResponse(IdentityClientConfiguration configuration)
DiscoveryDocumentResponse discoveryResponse,
IdentityClientConfiguration configuration)
{ {
var tokenEndpoint = await GetTokenEndpoint(configuration);
using (var httpClient = HttpClientFactory.CreateClient(HttpClientName)) using (var httpClient = HttpClientFactory.CreateClient(HttpClientName))
{ {
AddHeaders(httpClient); AddHeaders(httpClient);
@ -140,12 +180,12 @@ namespace Volo.Abp.IdentityModel
{ {
case OidcConstants.GrantTypes.ClientCredentials: case OidcConstants.GrantTypes.ClientCredentials:
return await httpClient.RequestClientCredentialsTokenAsync( return await httpClient.RequestClientCredentialsTokenAsync(
await CreateClientCredentialsTokenRequestAsync(discoveryResponse, configuration), await CreateClientCredentialsTokenRequestAsync(tokenEndpoint, configuration),
CancellationTokenProvider.Token CancellationTokenProvider.Token
); );
case OidcConstants.GrantTypes.Password: case OidcConstants.GrantTypes.Password:
return await httpClient.RequestPasswordTokenAsync( return await httpClient.RequestPasswordTokenAsync(
await CreatePasswordTokenRequestAsync(discoveryResponse, configuration), await CreatePasswordTokenRequestAsync(tokenEndpoint, configuration),
CancellationTokenProvider.Token CancellationTokenProvider.Token
); );
default: default:
@ -154,11 +194,11 @@ namespace Volo.Abp.IdentityModel
} }
} }
protected virtual Task<PasswordTokenRequest> CreatePasswordTokenRequestAsync(DiscoveryDocumentResponse discoveryResponse, IdentityClientConfiguration configuration) protected virtual Task<PasswordTokenRequest> CreatePasswordTokenRequestAsync(string tokenEndpoint, IdentityClientConfiguration configuration)
{ {
var request = new PasswordTokenRequest var request = new PasswordTokenRequest
{ {
Address = discoveryResponse.TokenEndpoint, Address = tokenEndpoint,
Scope = configuration.Scope, Scope = configuration.Scope,
ClientId = configuration.ClientId, ClientId = configuration.ClientId,
ClientSecret = configuration.ClientSecret, ClientSecret = configuration.ClientSecret,
@ -172,13 +212,11 @@ namespace Volo.Abp.IdentityModel
return Task.FromResult(request); return Task.FromResult(request);
} }
protected virtual Task<ClientCredentialsTokenRequest> CreateClientCredentialsTokenRequestAsync( protected virtual Task<ClientCredentialsTokenRequest> CreateClientCredentialsTokenRequestAsync(string tokenEndpoint, IdentityClientConfiguration configuration)
DiscoveryDocumentResponse discoveryResponse,
IdentityClientConfiguration configuration)
{ {
var request = new ClientCredentialsTokenRequest var request = new ClientCredentialsTokenRequest
{ {
Address = discoveryResponse.TokenEndpoint, Address = tokenEndpoint,
Scope = configuration.Scope, Scope = configuration.Scope,
ClientId = configuration.ClientId, ClientId = configuration.ClientId,
ClientSecret = configuration.ClientSecret ClientSecret = configuration.ClientSecret
@ -209,5 +247,15 @@ namespace Volo.Abp.IdentityModel
client.DefaultRequestHeaders.Add(TenantResolverConsts.DefaultTenantKey, CurrentTenant.Id.Value.ToString()); client.DefaultRequestHeaders.Add(TenantResolverConsts.DefaultTenantKey, CurrentTenant.Id.Value.ToString());
} }
} }
protected virtual string CalculateDiscoveryDocumentCacheKey(IdentityClientConfiguration configuration)
{
return IdentityModelDiscoveryDocumentCacheItem.CalculateCacheKey(configuration);
}
protected virtual string CalculateTokenCacheKey(IdentityClientConfiguration configuration)
{
return IdentityModelTokenCacheItem.CalculateCacheKey(configuration);
}
} }
} }

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

28
framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelTokenCacheItem.cs

@ -0,0 +1,28 @@
using System;
using System.Linq;
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(IdentityClientConfiguration configuration)
{
return string.Join(",", configuration.Select(x => x.Key + ":" + x.Value)).ToMd5();
}
}
}
Loading…
Cancel
Save