Add project files.
This commit is contained in:
parent
09e99a3f96
commit
d06df142e8
25
PoweredSoft.DynamicJwtBearer.sln
Normal file
25
PoweredSoft.DynamicJwtBearer.sln
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.29503.13
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoweredSoft.DynamicJwtBearer", "PoweredSoft.DynamicJwtBearer\PoweredSoft.DynamicJwtBearer.csproj", "{0A15F002-66C4-44D1-8162-563F860C49E4}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{0A15F002-66C4-44D1-8162-563F860C49E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0A15F002-66C4-44D1-8162-563F860C49E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0A15F002-66C4-44D1-8162-563F860C49E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0A15F002-66C4-44D1-8162-563F860C49E4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {9D366086-7F19-4F1E-A5B5-DAEF1EA56D90}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
22
PoweredSoft.DynamicJwtBearer/DynamicJwtBearerExtensions.cs
Normal file
22
PoweredSoft.DynamicJwtBearer/DynamicJwtBearerExtensions.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
|
||||
namespace PoweredSoft.DynamicJwtBearer
|
||||
{
|
||||
public static class DynamicJwtBearerExtensions
|
||||
{
|
||||
public static AuthenticationBuilder AddDynamicJwtBearer(this AuthenticationBuilder builder, string authenticationScheme, Action<JwtBearerOptions> action = null)
|
||||
{
|
||||
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<JwtBearerOptions>, JwtBearerPostConfigureOptions>());
|
||||
|
||||
if (action != null)
|
||||
return builder.AddScheme<JwtBearerOptions, DynamicJwtBearerHandler>(authenticationScheme, null, action);
|
||||
|
||||
return builder.AddScheme<JwtBearerOptions, DynamicJwtBearerHandler>(authenticationScheme, null, _ => { });
|
||||
}
|
||||
}
|
||||
}
|
174
PoweredSoft.DynamicJwtBearer/DynamicJwtBearerHandler.cs
Normal file
174
PoweredSoft.DynamicJwtBearer/DynamicJwtBearerHandler.cs
Normal file
@ -0,0 +1,174 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PoweredSoft.DynamicJwtBearer
|
||||
{
|
||||
public class DynamicJwtBearerHandler : JwtBearerHandler
|
||||
{
|
||||
private readonly IDynamicJwtBearerHanderConfigurationResolver dynamicJwtBearerHanderConfigurationResolver;
|
||||
|
||||
public DynamicJwtBearerHandler(IOptionsMonitor<JwtBearerOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IDynamicJwtBearerHanderConfigurationResolver dynamicJwtBearerHanderConfigurationResolver) : base(options, logger, encoder, clock)
|
||||
{
|
||||
this.dynamicJwtBearerHanderConfigurationResolver = dynamicJwtBearerHanderConfigurationResolver;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches the 'Authorization' header for a 'Bearer' token. If the 'Bearer' token is found, it is validated using <see cref="TokenValidationParameters"/> set in the options.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
string token = null;
|
||||
try
|
||||
{
|
||||
// Give application opportunity to find from a different location, adjust, or reject token
|
||||
var messageReceivedContext = new MessageReceivedContext(Context, Scheme, Options);
|
||||
|
||||
// event can set the token
|
||||
await Events.MessageReceived(messageReceivedContext);
|
||||
if (messageReceivedContext.Result != null)
|
||||
{
|
||||
return messageReceivedContext.Result;
|
||||
}
|
||||
|
||||
// If application retrieved token from somewhere else, use that.
|
||||
token = messageReceivedContext.Token;
|
||||
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
string authorization = Request.Headers[HeaderNames.Authorization];
|
||||
|
||||
// If no authorization header found, nothing to process further
|
||||
if (string.IsNullOrEmpty(authorization))
|
||||
{
|
||||
return AuthenticateResult.NoResult();
|
||||
}
|
||||
|
||||
if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
token = authorization.Substring("Bearer ".Length).Trim();
|
||||
}
|
||||
|
||||
// If no token found, no further work possible
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
return AuthenticateResult.NoResult();
|
||||
}
|
||||
}
|
||||
|
||||
var currentConfiguration = await this.dynamicJwtBearerHanderConfigurationResolver.ResolveCurrentOpenIdConfiguration(Context);
|
||||
var validationParameters = Options.TokenValidationParameters.Clone();
|
||||
if (currentConfiguration != null)
|
||||
{
|
||||
var issuers = new[] { currentConfiguration.Issuer };
|
||||
validationParameters.ValidIssuers = validationParameters.ValidIssuers?.Concat(issuers) ?? issuers;
|
||||
|
||||
validationParameters.IssuerSigningKeys = validationParameters.IssuerSigningKeys?.Concat(currentConfiguration.SigningKeys)
|
||||
?? currentConfiguration.SigningKeys;
|
||||
}
|
||||
|
||||
List<Exception> validationFailures = null;
|
||||
SecurityToken validatedToken;
|
||||
foreach (var validator in Options.SecurityTokenValidators)
|
||||
{
|
||||
if (validator.CanReadToken(token))
|
||||
{
|
||||
ClaimsPrincipal principal;
|
||||
try
|
||||
{
|
||||
principal = validator.ValidateToken(token, validationParameters, out validatedToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.TokenValidationFailed(ex);
|
||||
|
||||
// Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the event.
|
||||
if (Options.RefreshOnIssuerKeyNotFound && Options.ConfigurationManager != null
|
||||
&& ex is SecurityTokenSignatureKeyNotFoundException)
|
||||
{
|
||||
Options.ConfigurationManager.RequestRefresh();
|
||||
}
|
||||
|
||||
if (validationFailures == null)
|
||||
{
|
||||
validationFailures = new List<Exception>(1);
|
||||
}
|
||||
validationFailures.Add(ex);
|
||||
continue;
|
||||
}
|
||||
|
||||
Logger.TokenValidationSucceeded();
|
||||
|
||||
var tokenValidatedContext = new TokenValidatedContext(Context, Scheme, Options)
|
||||
{
|
||||
Principal = principal,
|
||||
SecurityToken = validatedToken
|
||||
};
|
||||
|
||||
await Events.TokenValidated(tokenValidatedContext);
|
||||
if (tokenValidatedContext.Result != null)
|
||||
{
|
||||
return tokenValidatedContext.Result;
|
||||
}
|
||||
|
||||
if (Options.SaveToken)
|
||||
{
|
||||
tokenValidatedContext.Properties.StoreTokens(new[]
|
||||
{
|
||||
new AuthenticationToken { Name = "access_token", Value = token }
|
||||
});
|
||||
}
|
||||
|
||||
tokenValidatedContext.Success();
|
||||
return tokenValidatedContext.Result;
|
||||
}
|
||||
}
|
||||
|
||||
if (validationFailures != null)
|
||||
{
|
||||
var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options)
|
||||
{
|
||||
Exception = (validationFailures.Count == 1) ? validationFailures[0] : new AggregateException(validationFailures)
|
||||
};
|
||||
|
||||
await Events.AuthenticationFailed(authenticationFailedContext);
|
||||
if (authenticationFailedContext.Result != null)
|
||||
{
|
||||
return authenticationFailedContext.Result;
|
||||
}
|
||||
|
||||
return AuthenticateResult.Fail(authenticationFailedContext.Exception);
|
||||
}
|
||||
|
||||
return AuthenticateResult.Fail("No SecurityTokenValidator available for token: " + token ?? "[null]");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.ErrorProcessingMessage(ex);
|
||||
|
||||
var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options)
|
||||
{
|
||||
Exception = ex
|
||||
};
|
||||
|
||||
await Events.AuthenticationFailed(authenticationFailedContext);
|
||||
if (authenticationFailedContext.Result != null)
|
||||
{
|
||||
return authenticationFailedContext.Result;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PoweredSoft.DynamicJwtBearer
|
||||
{
|
||||
public interface IDynamicJwtBearerHanderConfigurationResolver
|
||||
{
|
||||
Task<OpenIdConnectConfiguration> ResolveCurrentOpenIdConfiguration(HttpContext context);
|
||||
}
|
||||
}
|
38
PoweredSoft.DynamicJwtBearer/LoggingExtensions.cs
Normal file
38
PoweredSoft.DynamicJwtBearer/LoggingExtensions.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
|
||||
namespace PoweredSoft.DynamicJwtBearer
|
||||
{
|
||||
|
||||
internal static class LoggingExtensions
|
||||
{
|
||||
private static Action<ILogger, Exception> _tokenValidationFailed;
|
||||
private static Action<ILogger, Exception> _tokenValidationSucceeded;
|
||||
private static Action<ILogger, Exception> _errorProcessingMessage;
|
||||
|
||||
static LoggingExtensions()
|
||||
{
|
||||
_tokenValidationFailed = LoggerMessage.Define(
|
||||
eventId: new EventId(1, "TokenValidationFailed"),
|
||||
logLevel: LogLevel.Information,
|
||||
formatString: "Failed to validate the token.");
|
||||
_tokenValidationSucceeded = LoggerMessage.Define(
|
||||
eventId: new EventId(2, "TokenValidationSucceeded"),
|
||||
logLevel: LogLevel.Information,
|
||||
formatString: "Successfully validated the token.");
|
||||
_errorProcessingMessage = LoggerMessage.Define(
|
||||
eventId: new EventId(3, "ProcessingMessageFailed"),
|
||||
logLevel: LogLevel.Error,
|
||||
formatString: "Exception occurred while processing message.");
|
||||
}
|
||||
|
||||
public static void TokenValidationFailed(this ILogger logger, Exception ex)
|
||||
=> _tokenValidationFailed(logger, ex);
|
||||
|
||||
public static void TokenValidationSucceeded(this ILogger logger)
|
||||
=> _tokenValidationSucceeded(logger, null);
|
||||
|
||||
public static void ErrorProcessingMessage(this ILogger logger, Exception ex)
|
||||
=> _errorProcessingMessage(logger, ex);
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
Loading…
Reference in New Issue
Block a user