gpt4 book ai didi

c# - ASP.NET Core 2.0 app.UseAuthentication 未针对每个请求执行

转载 作者:行者123 更新时间:2023-11-30 23:02:05 33 4
gpt4 key购买 nike

我有一个非常奇怪的问题,我花了一整天的时间进行调试,但离解决还差得很远。我正在将我的应用程序从 ASP.NET Core 1.x 升级到 2.1。作为这样做的一部分,我必须重新连接身份验证和授权机制。我们使用 JWTBearer 身份验证,我正在使用 postman 触发 API 调用,它执行管道,我可以看到 AuthHandler 正在执行。但是,如果我再次发出相同的请求,则 AuthHandler 不会执行并且调试器“跳过”“context.AuthenticateAsync”调用并返回先前的结果。为了详细说明,我编写了一个自定义身份验证处理程序,它是 JWTAuthHandler 的复制粘贴。创建自定义处理程序的代码基于 answer在这里。

using Microsoft.AspNetCore.Authentication.JwtBearer;
public class CustomAuthOptions : JwtBearerOptions
{
}

using Microsoft.AspNetCore.Authentication;

public static class CustomAuthExtensions
{
public static AuthenticationBuilder AddCustomAuth(this AuthenticationBuilder builder, Action<CustomAuthOptions> configureOptions)
{
return builder.AddScheme<CustomAuthOptions, CustomAuthHandler>("CustomScheme", configureOptions);
}
}


public class CustomAuthHandler : AuthenticationHandler<CustomAuthOptions>
{
private OpenIdConnectConfiguration _configuration;

public CustomAuthHandler(IOptionsMonitor<CustomAuthOptions> 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["Authorization"];

// If no authorization header found, nothing to process further
if (string.IsNullOrEmpty(authorization))
{
return AuthenticateResult.NoResult();
}

if (authorization.StartsWith("CustomAuth ", StringComparison.OrdinalIgnoreCase))
{
token = authorization.Substring("CustomAuth ".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)
{
////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;
}
}

protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
{
var authResult = await HandleAuthenticateOnceSafeAsync();
var eventContext = new JwtBearerChallengeContext(Context, Scheme, Options, properties)
{
AuthenticateFailure = authResult?.Failure
};

// Avoid returning error=invalid_token if the error is not caused by an authentication failure (e.g missing token).
if (Options.IncludeErrorDetails && eventContext.AuthenticateFailure != null)
{
eventContext.Error = "invalid_token";
eventContext.ErrorDescription = CreateErrorDescription(eventContext.AuthenticateFailure);
}

await Events.Challenge(eventContext);
if (eventContext.Handled)
{
return;
}

Response.StatusCode = 401;

if (string.IsNullOrEmpty(eventContext.Error) &&
string.IsNullOrEmpty(eventContext.ErrorDescription) &&
string.IsNullOrEmpty(eventContext.ErrorUri))
{
Response.Headers.Append(HeaderNames.WWWAuthenticate, Options.Challenge);
}
else
{
// https://tools.ietf.org/html/rfc6750#section-3.1
// WWW-Authenticate: Bearer realm="example", error="invalid_token", error_description="The access token expired"
var builder = new StringBuilder(Options.Challenge);
if (Options.Challenge.IndexOf(" ", StringComparison.Ordinal) > 0)
{
// Only add a comma after the first param, if any
builder.Append(',');
builder.Append(',');
}
if (!string.IsNullOrEmpty(eventContext.Error))
{
builder.Append(" error=\"");
builder.Append(eventContext.Error);
builder.Append("\"");
}
if (!string.IsNullOrEmpty(eventContext.ErrorDescription))
{
if (!string.IsNullOrEmpty(eventContext.Error))
{
builder.Append(",");
}

builder.Append(" error_description=\"");
builder.Append(eventContext.ErrorDescription);
builder.Append('\"');
}
if (!string.IsNullOrEmpty(eventContext.ErrorUri))
{
if (!string.IsNullOrEmpty(eventContext.Error) ||
!string.IsNullOrEmpty(eventContext.ErrorDescription))
{
builder.Append(",");
}

builder.Append(" error_uri=\"");
builder.Append(eventContext.ErrorUri);
builder.Append('\"');
}

Response.Headers.Append(HeaderNames.WWWAuthenticate, builder.ToString());
}
}

private static string CreateErrorDescription(Exception authFailure)
{
IEnumerable<Exception> exceptions;
if (authFailure is AggregateException agEx)
{
exceptions = agEx.InnerExceptions;
}
else
{
exceptions = new[] { authFailure };
}

var messages = new List<string>();

foreach (var ex in exceptions)
{
// Order sensitive, some of these exceptions derive from others
// and we want to display the most specific message possible.
switch (ex)
{
case SecurityTokenInvalidAudienceException _:
messages.Add("The audience is invalid");
break;
case SecurityTokenInvalidIssuerException _:
messages.Add("The issuer is invalid");
break;
case SecurityTokenNoExpirationException _:
messages.Add("The token has no expiration");
break;
case SecurityTokenInvalidLifetimeException _:
messages.Add("The token lifetime is invalid");
break;
case SecurityTokenNotYetValidException _:
messages.Add("The token is not valid yet");
break;
case SecurityTokenExpiredException _:
messages.Add("The token is expired");
break;
case SecurityTokenSignatureKeyNotFoundException _:
messages.Add("The signature key was not found");
break;
case SecurityTokenInvalidSignatureException _:
messages.Add("The signature is invalid");
break;
}
}

return string.Join("; ", messages);
}
}

然后是 Startup.cs 来连接它:

public class Startup
{
/// <summary>
/// Initializes a new instance of the <see cref="Startup"/> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
public Startup(IConfiguration configuration)
{
this.Configuration = configuration;
ConfigureLogging();
}

/// <summary>
/// Gets the configuration.
/// </summary>
/// <value>
/// The configuration.
/// </value>
public IConfiguration Configuration { get; }

/// <summary>
/// Gets or sets the Container
/// </summary>
private IUnityContainer Container { get; set; }

public static void Main(string[] args)
{
BuildWebHost(args).Run();
}

public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();

public IServiceProvider ConfigureServices(IServiceCollection services)
{
var logger = Logger.For(this).ForAction(nameof(ConfigureServices));

services.Configure<GzipCompressionProviderOptions>(options => options.Level = CompressionLevel.Optimal);
services.AddResponseCompression();

logger.Info("Configuring JWT Bearer Token Authorization...");
services.AddAuthentication(options =>
{
// the scheme name has to match the value we're going to use in AuthenticationBuilder.AddScheme(...)
options.DefaultAuthenticateScheme = "CustomScheme";
options.DefaultChallengeScheme = "CustomScheme";
})
.AddCustomAuth(options => {
options.Audience = this.Configuration.ObtainConfiguredString(ConfigurationKeys.ValidAudienceId);
options.Authority = this.Configuration.ObtainConfiguredString(ConfigurationKeys.IssuerId);
options.SaveToken = false;
options.TokenValidationParameters = new TokenValidationParameters().WithConfiguredParameters(this.Configuration);
});

logger.Info("Adding Authorization policies to Services...");
services.AddAuthorization(
options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder("CustomScheme").RequireAuthenticatedUser().Build();
});

services.AddTransient<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<IAuthenticationHandler, CustomAuthHandler>();

EnableCors(services);

logger.Info("Adding MVC support to Services...");
services.AddMvc(config =>
{
var defaultPolicy = new AuthorizationPolicyBuilder(new[] { "CustomScheme" })
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(defaultPolicy));
});

Container = new UnityContainer();

logger.Info("Registering other Services with UnityContainer...");
Container.RegisterServices(Configuration);

// Configure Microsoft DI for Unity resolution
logger.Info("Configuring ASP.Net Core service resolution to use UnityContainer...");
return services.UseUnityResolution(Container, s => s.BuildServiceProvider());
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
/// <summary>
/// The Configure
/// </summary>
/// <param name="app">The app<see cref="IApplicationBuilder"/></param>
/// <param name="env">The env<see cref="IHostingEnvironment"/></param>
/// <param name="loggerFactory">The loggerFactory<see cref="ILoggerFactory"/></param>
/// <param name="memoryCache">The memoryCache<see cref="IMemoryCache"/></param>
/// <param name="contextAccessor">The contextAccessor<see cref="IHttpContextAccessor"/></param>
/// <param name="authzClient">The authzClient<see cref="IAuthzClient"/></param>
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory,
IMemoryCache memoryCache,
IHttpContextAccessor contextAccessor,
IAuthzClient authzClient)
{
var logger = Logger.For(this).ForAction(nameof(Configure));

logger.Info("Configuring ASP.Net Core logging framework...");

loggerFactory.AddConsole(this.Configuration.GetSection("Logging"));
loggerFactory.AddDebug();

var corsEnabled = this.Configuration.ObtainConfiguredBooleanWithDefault(ConfigurationKeys.EnableCors, false);
if (corsEnabled)
{
app.UseCors("CorsPolicy");
}

logger.Info("Configuring ASP.Net Core custom status page...");
app.UseStatusCodePagesWithReExecute("/error/{0}");

if (env.IsDevelopment())
{
logger.Info("Configuring development middle-ware...");
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}

logger.Info("Configuring standard ASP.Net Core behaviors...");
app.UseDefaultFiles();
app.UseStaticFiles();

////app.UseAuthentication();
app.Use(async (context, next) =>
{
if (!context.User.Identity.IsAuthenticated)
{
var result = await context.AuthenticateAsync("CustomScheme");
if (result?.Principal != null)
{
context.User = result.Principal;
}
}

await next.Invoke();
});

app.UseMvc();
app.WithRequestLogging();
}

private void EnableCors(IServiceCollection service)
{
var logger = Logger.For(this).ForAction(nameof(EnableCors));
var corsEnabled = this.Configuration.ObtainConfiguredBooleanWithDefault(ConfigurationKeys.EnableCors, false);

if (corsEnabled)
{
logger.Verbose("Configuring ASP.Net Core CORS support...");

service.AddCors(
options =>
{
options.AddPolicy("CorsPolicy",
builder =>
{
builder.AllowAnyOrigin();
builder.AllowAnyHeader();
builder.AllowAnyMethod();
builder.AllowCredentials();
});
});
}
}
}
}

有人可以告诉我我做错了什么吗?第一次使用正确的 AuthorizationHeader 和访问 token 触发 postman 请求时,此行执行 CustomAuthHandler:

var result = await context.AuthenticateAsync("CustomScheme");

然而,调试器第二次越过该代码?这让我难以自拔。我一定是遗漏了一些基本的东西!

编辑:在 Core 1.x 版本中,ConfigureServices 设置为

public IServiceProvider ConfigureServices(IServiceCollection services)
{
var logger = Logger.For(this).ForAction(nameof(ConfigureServices));

logger.Verbose("Adding MVC support to Services...");
// Add framework services.
services.AddMvc();

logger.Verbose("Adding Authorization policies to Services...");
services.AddAuthorization(
options =>
{
options.AddPolicy(
"SomePermission",
policy => policy.RequireClaim("claimUrl", "Some Permission"));
});

services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

container = new UnityContainer();

logger.Verbose("Registering other Services with UnityContainer...");
container.RegisterServices(Configuration);

// Configure Microsoft DI for Unity resolution
logger.Verbose("Configuring ASP.Net Core service resolution to use UnityContainer...");
return services.UseUnityResolution(container, s => s.BuildServiceProvider());
}

Configure() 的连接方式如下

 app.UseAuth0JwtBearerAuthentication(
new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters =
new TokenValidationParameters().WithConfiguredParameters(this.Configuration)
});

if (env.IsDevelopment())
{
logger.Verbose("Configuring development middleware...");
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}

logger.Verbose("Configuring standard ASP.Net Core behaviors...");
app.UseDefaultFiles();
app.UseMvc();
app.UseStaticFiles();

如果我执行 postman 调用,则使用此版本,然后我会为每个请求获得一个新的 ClaimsPrincipal。那么 ASP.NET Core 2.1 中发生了什么变化?

最佳答案

对于面临相同问题的任何人;我的问题原来是Unity。 ASP.NET Core 2.0 不支持开箱即用的 Unity,因为 ConfigureServices() Startup.cs 中的方法是第三方 DI 容器的替代品,如 Unity 或 Autofac。但是,如果您仍想使用 Unity,则需要 Nuget Unity.Microsoft.DependencyInjection到你的项目。 Github 存储库包含有关如何连接它的详细信息。

此外,所有其他相关项目都使用 Unity 4.0.1,它在 Microsoft.Practices.Unity 下有 IUnityContainer。 ,而从 Unity 5 开始,IUnityContainer 已移至 Unity命名空间。这是一个额外的陷阱,即使在按照 Github 存储库的说明设置 DI 容器之后,我也会遇到依赖项解析失败的异常。解决方法是使用 Microsoft.Practices.Unity 创建一个新的 UnityContainer ,让依赖项目引导它,然后将这些注册复制到 Unity 下的 IUnityContainer命名空间。

启动.cs

public void ConfigureContainer(IUnityContainer container)
{
container.RegisterServices(Configuration);
}

UnityRegistrations.cs

public static void RegisterServices(this IUnityContainer container, IConfiguration configuration)
{
// Microsoft.Practices.Unity
var currentContainer = new UnityContainer();
// Bootstrap this and register dependencies
// Then copy them over
foreach (var registration in currentContainer.Registrations)
{
container.RegisterType(registration.RegisteredType, registration.MappedToType);
}
}

关于c# - ASP.NET Core 2.0 app.UseAuthentication 未针对每个请求执行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50647513/

33 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com