ASP.NET Core 1.1迁移至2.0后自定义Cookie认证异常求助
从你的日志和代码来看,问题出在ASP.NET Core 2.0对认证系统的重构上——当授权失败触发ForbidResult时,框架需要明确知道使用哪个认证方案来处理"禁止"操作,而你的自定义认证中间件没有配置对应的认证服务选项,导致框架找不到默认的ForbidScheme,从而抛出InvalidOperationException,最终返回500错误。
问题根源
在ASP.NET Core 1.1中,授权系统对认证方案的要求没那么严格;但2.0版本重构了认证体系,当AuthorizeFilter触发授权失败时,会调用ForbidAsync,此时必须指定认证方案,或者配置全局的DefaultForbidScheme,否则就会抛出你看到的异常。你的自定义中间件只是手动设置了context.User,但没有在认证服务中注册对应的scheme,框架无法识别你的"local"认证类型。
解决方案
需要两步修改,让你的自定义认证和ASP.NET Core 2.0的认证系统对齐:
1. 在ConfigureServices中配置认证服务
添加认证服务注册,指定默认的认证和禁止方案为你中间件中使用的"local",并配置对应的Cookie认证处理逻辑:
public void ConfigureServices(IServiceCollection services) { // 其他现有代码... // 添加认证配置 services.AddAuthentication(options => { options.DefaultAuthenticateScheme = "local"; options.DefaultForbidScheme = "local"; }) .AddCookie("local", options => { // 配置禁止请求的处理逻辑,和你中间件里的逻辑保持一致 options.Events = new CookieAuthenticationEvents { OnForbidden = context => { if (Utility.IsAjaxRequest(context.Request)) { context.Response.StatusCode = 401; context.Response.Headers.Add("X-Team-Login", "1"); } else { context.Response.Redirect("/Login"); } return Task.CompletedTask; } }; // 如果你不需要框架自动处理登录路径,可以保留中间件的跳转逻辑,这里可以不用设置LoginPath }); // 其他现有代码... }
2. 修改自定义认证中间件的用户认证逻辑
把你手动设置context.User的代码,替换为框架的SignInAsync方法,这样框架会正确记录认证scheme信息:
// 原代码: // ClaimsPrincipal principal = new ClaimsPrincipal(new ClaimsIdentity(userClaims, "local")); // context.User = principal; // 修改为: ClaimsPrincipal principal = new ClaimsPrincipal(new ClaimsIdentity(userClaims, "local")); await context.SignInAsync("local", principal);
3. 调整中间件顺序
确保在Configure方法中,app.UseAuthentication()(框架的认证中间件)在app.UseMvc()之前,并且你的自定义app.UseTeamAuthentication()可以放在它之前(如果你的中间件负责token验证,建议放在UseAuthentication之前):
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { // 其他现有代码... app.UseStaticFiles(); app.UseTeamDatabaseSelector(); app.UseTeamAuthentication(); // 添加框架的认证中间件 app.UseAuthentication(); var localizationOptions = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>(); app.UseRequestLocalization(localizationOptions.Value); app.UseSession(); app.UseMvc(routes => { /* 路由配置 */ }); }
为什么这样修改有效
- 通过
AddAuthentication注册了"local"认证scheme,并设置为默认的认证和禁止方案,框架在处理ForbidResult时就知道该用哪个scheme来处理。 - 使用
SignInAsync而不是直接赋值context.User,会让框架的认证系统正确记录用户的认证状态和scheme信息,避免授权时出现scheme缺失的异常。 - 配置
OnForbidden事件,让授权失败时的行为和你原来的中间件逻辑保持一致,确保返回401(AJAX请求)或者重定向到登录页(普通请求),而不是抛出500错误。
做完这些修改后,再测试未授权访问的场景,应该会正确返回401(AJAX请求)或者重定向到登录页(普通请求),不再出现500错误。
内容的提问来源于stack exchange,提问作者Marko

