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