diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Volo.Abp.AspNetCore.Authentication.OpenIdConnect.csproj b/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Volo.Abp.AspNetCore.Authentication.OpenIdConnect.csproj index 4ee09824cf..9f1051fbd5 100644 --- a/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Volo.Abp.AspNetCore.Authentication.OpenIdConnect.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Volo.Abp.AspNetCore.Authentication.OpenIdConnect.csproj @@ -8,10 +8,6 @@ - - - - diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server/Microsoft/AspNetCore/Authentication/Cookies/CookieAuthenticationOptionsExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Components.Server/Microsoft/AspNetCore/Authentication/Cookies/CookieAuthenticationOptionsExtensions.cs index 61c064b376..48065bbac2 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.Server/Microsoft/AspNetCore/Authentication/Cookies/CookieAuthenticationOptionsExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server/Microsoft/AspNetCore/Authentication/Cookies/CookieAuthenticationOptionsExtensions.cs @@ -1,7 +1,9 @@ using System; +using System.Threading.Tasks; using IdentityModel.Client; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Authentication.Cookies; @@ -16,41 +18,67 @@ public static class CookieAuthenticationOptionsExtensions /// public static CookieAuthenticationOptions IntrospectAccessToken(this CookieAuthenticationOptions options, string oidcAuthenticationScheme = "oidc") { - var originalHandler = options.Events.OnValidatePrincipal; options.Events.OnValidatePrincipal = async principalContext => { - originalHandler?.Invoke(principalContext); + if (principalContext.Principal == null || principalContext.Principal.Identity == null || !principalContext.Principal.Identity.IsAuthenticated) + { + return; + } + + var logger = principalContext.HttpContext.RequestServices.GetRequiredService>(); - if (principalContext.Principal != null && principalContext.Principal.Identity != null && principalContext.Principal.Identity.IsAuthenticated) + var accessToken = principalContext.Properties.GetTokenValue("access_token"); + if (!accessToken.IsNullOrWhiteSpace()) { - var accessToken = principalContext.Properties.GetTokenValue("access_token"); - if (!accessToken.IsNullOrWhiteSpace()) + var openIdConnectOptions = await GetOpenIdConnectOptions(principalContext, oidcAuthenticationScheme); + var response = await openIdConnectOptions.Backchannel.IntrospectTokenAsync(new TokenIntrospectionRequest + { + Address = openIdConnectOptions.Configuration?.IntrospectionEndpoint ?? openIdConnectOptions.Authority.EnsureEndsWith('/') + "connect/introspect", + ClientId = openIdConnectOptions.ClientId, + ClientSecret = openIdConnectOptions.ClientSecret, + Token = accessToken + }); + + if (response.IsError) { - var openIdConnectOptions = principalContext.HttpContext.RequestServices.GetRequiredService>().Get(oidcAuthenticationScheme); - if (openIdConnectOptions.Configuration == null && openIdConnectOptions.ConfigurationManager != null) - { - openIdConnectOptions.Configuration = await openIdConnectOptions.ConfigurationManager.GetConfigurationAsync(principalContext.HttpContext.RequestAborted); - } - - var response = await openIdConnectOptions.Backchannel.IntrospectTokenAsync(new TokenIntrospectionRequest - { - Address = openIdConnectOptions.Configuration?.IntrospectionEndpoint ?? openIdConnectOptions.Authority.EnsureEndsWith('/') + "connect/introspect", - ClientId = openIdConnectOptions.ClientId, - ClientSecret = openIdConnectOptions.ClientSecret, - Token = accessToken - }); - - if (response.IsActive) - { - return; - } + logger.LogError(response.Error); + await SignOutAsync(principalContext); + return; } - principalContext.RejectPrincipal(); - await principalContext.HttpContext.SignOutAsync(principalContext.Scheme.Name); + if (!response.IsActive) + { + logger.LogError("The access_token is not active."); + await SignOutAsync(principalContext); + return; + } + + logger.LogInformation("The access_token is active."); + } + else + { + logger.LogError("The access_token is not found in the cookie properties, Please make sure SaveTokens of OpenIdConnectOptions is set as true."); + await SignOutAsync(principalContext); } }; return options; } + + private async static Task GetOpenIdConnectOptions(CookieValidatePrincipalContext principalContext, string oidcAuthenticationScheme) + { + var openIdConnectOptions = principalContext.HttpContext.RequestServices.GetRequiredService>().Get(oidcAuthenticationScheme); + if (openIdConnectOptions.Configuration == null && openIdConnectOptions.ConfigurationManager != null) + { + openIdConnectOptions.Configuration = await openIdConnectOptions.ConfigurationManager.GetConfigurationAsync(principalContext.HttpContext.RequestAborted); + } + + return openIdConnectOptions; + } + + private async static Task SignOutAsync(CookieValidatePrincipalContext principalContext) + { + principalContext.RejectPrincipal(); + await principalContext.HttpContext.SignOutAsync(principalContext.Scheme.Name); + } } diff --git a/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/CookieAuthenticationOptionsExtensions.cs b/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/CookieAuthenticationOptionsExtensions.cs new file mode 100644 index 0000000000..f873f3762b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/CookieAuthenticationOptionsExtensions.cs @@ -0,0 +1,100 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using IdentityModel.Client; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class CookieAuthenticationOptionsExtensions +{ + /// + /// Check the access_token is expired or inactive. + /// + public static CookieAuthenticationOptions CheckTokenExpiration(this CookieAuthenticationOptions options, string oidcAuthenticationScheme = "oidc", TimeSpan? advance = null, TimeSpan? validationInterval = null) + { + advance ??= TimeSpan.FromMinutes(3); + validationInterval ??= TimeSpan.FromMinutes(1); + options.Events.OnValidatePrincipal = async principalContext => + { + if (principalContext.Principal == null || principalContext.Principal.Identity == null || !principalContext.Principal.Identity.IsAuthenticated) + { + return; + } + + var logger = principalContext.HttpContext.RequestServices.GetRequiredService>(); + + var tokenExpiresAt = principalContext.Properties.Items[".Token.expires_at"]; + if (DateTimeOffset.TryParseExact(tokenExpiresAt, "o", null, DateTimeStyles.RoundtripKind, out var expiresAt) && + expiresAt < DateTimeOffset.UtcNow.Subtract(advance.Value)) + { + logger.LogInformation("The access_token is expired."); + await SignOutAsync(principalContext); + return; + } + + if (principalContext.Properties.IssuedUtc != null && DateTimeOffset.UtcNow.Subtract(principalContext.Properties.IssuedUtc.Value) > validationInterval) + { + logger.LogInformation($"Check the access_token is active every {validationInterval.Value.TotalSeconds} seconds."); + var accessToken = principalContext.Properties.GetTokenValue("access_token"); + if (!accessToken.IsNullOrWhiteSpace()) + { + var openIdConnectOptions = await GetOpenIdConnectOptions(principalContext, oidcAuthenticationScheme); + + var response = await openIdConnectOptions.Backchannel.IntrospectTokenAsync(new TokenIntrospectionRequest + { + Address = openIdConnectOptions.Configuration?.IntrospectionEndpoint ?? openIdConnectOptions.Authority.EnsureEndsWith('/') + "connect/introspect", + ClientId = openIdConnectOptions.ClientId, + ClientSecret = openIdConnectOptions.ClientSecret, + Token = accessToken + }); + + if (response.IsError) + { + logger.LogError(response.Error); + await SignOutAsync(principalContext); + return; + } + + if (!response.IsActive) + { + logger.LogError("The access_token is not active."); + await SignOutAsync(principalContext); + return; + } + + logger.LogInformation("The access_token is active."); + principalContext.ShouldRenew = true; + } + else + { + logger.LogError("The access_token is not found in the cookie properties, Please make sure SaveTokens of OpenIdConnectOptions is set as true."); + await SignOutAsync(principalContext); + } + } + }; + + return options; + } + + private async static Task GetOpenIdConnectOptions(CookieValidatePrincipalContext principalContext, string oidcAuthenticationScheme) + { + var openIdConnectOptions = principalContext.HttpContext.RequestServices.GetRequiredService>().Get(oidcAuthenticationScheme); + if (openIdConnectOptions.Configuration == null && openIdConnectOptions.ConfigurationManager != null) + { + openIdConnectOptions.Configuration = await openIdConnectOptions.ConfigurationManager.GetConfigurationAsync(principalContext.HttpContext.RequestAborted); + } + + return openIdConnectOptions; + } + + private async static Task SignOutAsync(CookieValidatePrincipalContext principalContext) + { + principalContext.RejectPrincipal(); + await principalContext.HttpContext.SignOutAsync(principalContext.Scheme.Name); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore/Volo.Abp.AspNetCore.csproj b/framework/src/Volo.Abp.AspNetCore/Volo.Abp.AspNetCore.csproj index aa1090e204..0fe01e9f2b 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo.Abp.AspNetCore.csproj +++ b/framework/src/Volo.Abp.AspNetCore/Volo.Abp.AspNetCore.csproj @@ -26,6 +26,8 @@ + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebModule.cs index 55302dde61..8e8ac6c694 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebModule.cs @@ -145,6 +145,7 @@ public class MyProjectNameWebModule : AbpModule .AddCookie("Cookies", options => { options.ExpireTimeSpan = TimeSpan.FromDays(365); + options.CheckTokenExpiration(); }) .AddAbpOpenIdConnect("oidc", options => { @@ -232,7 +233,7 @@ public class MyProjectNameWebModule : AbpModule dataProtectionBuilder.PersistKeysToStackExchangeRedis(redis, "MyProjectName-Protection-Keys"); } } - + private void ConfigureDistributedLocking( ServiceConfigurationContext context, IConfiguration configuration)