@ -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,24 @@ namespace Volo.Abp.IdentityModel
protected IHttpClientFactory HttpClientFactory { get ; }
protected ICurrentTenant CurrentTenant { get ; }
protected IdentityModelHttpRequestMessageOptions IdentityModelHttpRequestMessageOptions { get ; }
protected IDistributedCache < IdentityModelTokenCacheItem > TokenCache { get ; }
protected IDistributedCache < IdentityModelDiscoveryDocumentCacheItem > DiscoveryDocumentCache { get ; }
public IdentityModelAuthenticationService (
IOptions < AbpIdentityClientOptions > options ,
ICancellationTokenProvider cancellationTokenProvider ,
IHttpClientFactory httpClientFactory ,
ICurrentTenant currentTenant ,
IOptions < IdentityModelHttpRequestMessageOptions > identityModelHttpRequestMessageOptions )
IOptions < IdentityModelHttpRequestMessageOptions > identityModelHttpRequestMessageOptions ,
IDistributedCache < IdentityModelTokenCacheItem > tokenCache ,
IDistributedCache < IdentityModelDiscoveryDocumentCacheItem > discoveryDocumentCache )
{
ClientOptions = options . Value ;
CancellationTokenProvider = cancellationTokenProvider ;
HttpClientFactory = httpClientFactory ;
CurrentTenant = currentTenant ;
TokenCache = tokenCache ;
DiscoveryDocumentCache = discoveryDocumentCache ;
IdentityModelHttpRequestMessageOptions = identityModelHttpRequestMessageOptions . Value ;
Logger = NullLogger < IdentityModelAuthenticationService > . Instance ;
}
@ -70,27 +78,34 @@ namespace Volo.Abp.IdentityModel
public virtual async Task < string > GetAccessTokenAsync ( IdentityClientConfiguration configuration )
{
var discoveryResponse = await GetDiscoveryResponse ( configuration ) ;
if ( discoveryResponse . IsError )
var cacheKey = CalculateTokenCacheKey ( configuration ) ;
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 ( discoveryResponse , configuration ) ;
var tokenResponse = await GetTokenResponse ( configuration ) ;
if ( tokenResponse . IsError )
{
if ( tokenResponse . ErrorDescription ! = null )
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 [ ] { "<eof/>" } , StringSplitOptions . RemoveEmptyEntries ) ;
throw new AbpException ( withoutInnerException [ 0 ] ) ;
}
var rawError = tokenResponse . Raw ;
var withoutInnerException = rawError . Split ( new string [ ] { "<eof/>" } , StringSplitOptions . RemoveEmptyEntries ) ;
throw new AbpException ( withoutInnerException [ 0 ] ) ;
tokenCacheItem = new IdentityModelTokenCacheItem ( tokenResponse . AccessToken ) ;
await TokenCache . SetAsync ( cacheKey , tokenCacheItem ,
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan . FromSeconds ( configuration . CacheAbsoluteExpiration )
} ) ;
}
return tokenResponse . AccessToken ;
return tokenCacheItem . AccessToken ;
}
protected virtual void SetAccessToken ( HttpClient client , string accessToken )
@ -110,8 +125,33 @@ namespace Volo.Abp.IdentityModel
ClientOptions . IdentityClients . Default ;
}
protected virtual async Task < DiscoveryDocumentResponse > GetDiscoveryResponse (
IdentityClientConfiguration configuration )
protected virtual async Task < string > 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
{
AbsoluteExpirationRelativeToNow = TimeSpan . FromSeconds ( configuration . CacheAbsoluteExpiration )
} ) ;
}
return discoveryDocumentCacheItem . TokenEndpoint ;
}
protected virtual async Task < DiscoveryDocumentResponse > GetDiscoveryResponse ( IdentityClientConfiguration configuration )
{
using ( var httpClient = HttpClientFactory . CreateClient ( HttpClientName ) )
{
@ -128,10 +168,10 @@ namespace Volo.Abp.IdentityModel
}
}
protected virtual async Task < TokenResponse > GetTokenResponse (
DiscoveryDocumentResponse discoveryResponse ,
IdentityClientConfiguration configuration )
protected virtual async Task < TokenResponse > GetTokenResponse ( IdentityClientConfiguration configuration )
{
var tokenEndpoint = await GetTokenEndpoint ( configuration ) ;
using ( var httpClient = HttpClientFactory . CreateClient ( HttpClientName ) )
{
AddHeaders ( httpClient ) ;
@ -140,12 +180,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 :
@ -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
{
Address = discoveryResponse . T okenEndpoint,
Address = t okenEndpoint,
Scope = configuration . Scope ,
ClientId = configuration . ClientId ,
ClientSecret = configuration . ClientSecret ,
@ -172,13 +212,11 @@ namespace Volo.Abp.IdentityModel
return Task . FromResult ( request ) ;
}
protected virtual Task < ClientCredentialsTokenRequest > CreateClientCredentialsTokenRequestAsync (
DiscoveryDocumentResponse discoveryResponse ,
IdentityClientConfiguration configuration )
protected virtual Task < ClientCredentialsTokenRequest > CreateClientCredentialsTokenRequestAsync ( string tokenEndpoint , IdentityClientConfiguration configuration )
{
var request = new ClientCredentialsTokenRequest
{
Address = discoveryResponse . T okenEndpoint,
Address = t okenEndpoint,
Scope = configuration . Scope ,
ClientId = configuration . ClientId ,
ClientSecret = configuration . ClientSecret
@ -209,5 +247,15 @@ namespace Volo.Abp.IdentityModel
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 ) ;
}
}
}