175 lines
7.5 KiB
C#
175 lines
7.5 KiB
C#
|
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;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|