Я пытаюсь реализовать пользовательский JWTBearerHandler для авторизации некоторых запросов в моем API, потому что мне нужно добавить свой собственный валидатор токенов.
Итак, я реализовал AuthenticationHandler на основе реализации JwtBearerHandler :
public class CustomHandler: AuthenticationHandler<JwtBearerOptions>
{
private OpenIdConnectConfiguration _configuration;
public CustomHandler(
IOptionsMonitor<JwtBearerOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock)
: base(options, logger, encoder, clock) { }
/// <summary>
/// The handler calls methods on the events which give the application control at certain points where processing is occurring.
/// If it is not provided a default instance is supplied which does nothing when the methods are called.
/// </summary>
protected new JwtBearerEvents Events
{
get => (JwtBearerEvents)base.Events;
set => base.Events = value;
}
protected override Task<object> CreateEventsAsync()
=> Task.FromResult<object>(new JwtBearerEvents());
/// <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();
}
}
if (_configuration == null && Options.ConfigurationManager != null)
{
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
}
var validationParameters = Options.TokenValidationParameters.Clone();
if (_configuration != null)
{
var issuers = new[] { _configuration.Issuer };
validationParameters.ValidIssuers = validationParameters.ValidIssuers?.Concat(issuers) ?? issuers;
validationParameters.IssuerSigningKeys = validationParameters.IssuerSigningKeys?.Concat(_configuration.SigningKeys)
?? _configuration.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)
{
// TODO: Log
// 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;
}
// TODO: Same here
// 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)
{
// TODO: and here
// Logger.ErrorProcessingMessage(ex);
var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options)
{
Exception = ex
};
await Events.AuthenticationFailed(authenticationFailedContext);
if (authenticationFailedContext.Result != null)
{
return authenticationFailedContext.Result;
}
throw;
}
}
protected override Task HandleChallengeAsync(AuthenticationProperties properties)
{
return base.HandleChallengeAsync(properties);
}
protected override Task HandleForbiddenAsync(AuthenticationProperties properties)
{
return base.HandleForbiddenAsync(properties);
}
}
Я также реализовал расширение для его регистрации.
public static class AuthExtensions
{
public static AuthenticationBuilder AddJwtBearerForExternalAuth(this AuthenticationBuilder builder, string authenticationScheme, Action<JwtBearerOptions> configureOptions)
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<JwtBearerOptions>, JwtBearerPostConfigureOptions>());
return builder.AddScheme<JwtBearerOptions, CustomHandler>(authenticationScheme, null, configureOptions);
}
}
Там два сценария ios, если я регистрирую AuthenticationHandler следующим образом, он не достигает HandleAuthenticateAsyn c,
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearerForExternalAuth(JwtBearerDefaults.AuthenticationScheme, _ => { });
Но если я регистрируюсь, устанавливая значения по умолчанию явно, выдается исключение,
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearerForExternalAuth(JwtBearerDefaults.AuthenticationScheme, _ => { });
Выдается исключение InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found.
Затем я добавляю схему вызова по умолчанию к опциям
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearerForExternalAuth(JwtBearerDefaults.AuthenticationScheme, _ => { });
И она не достигает HandleAuthenticateAsyn c снова, он переходит прямо к HandleChallengeAsyn c.
Мой вопрос: почему я не могу связаться HandleAuthenticateAsyn c без производства экс ception? Я что-то упустил?
Заранее спасибо,