.net - .NET Core 2.0 -> 5.0 API 身份验证 - 多种方案
问题描述
我有一个使用 MVC 和标准 Identity 的 .NET Core(以前是 2.0,现在逐步升级到 5)Web 应用程序。它有一个基于 Web 的登录/后端 UI。升级过程为此运行良好,并且一切正常。
但是,我也有一组使用 JWT Bearer Token 的 WebAPI 控制器 - 这些控制器已经停止工作,现在都抛出 401 错误。
我很确定我需要以某种方式注册额外的授权方案,但我不知道该怎么做。
这是控制器的注释方式
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[Route("api/[controller]")]
这是我的 Startup.cs 的摘录
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("TechsportiseDB")));
//options.UseInMemoryDatabase("Techsportise"));
services.AddIdentity<ApplicationUser, IdentityRole>(config =>
{
config.SignIn.RequireConfirmedEmail = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.Configure<IdentityOptions>(options =>
{
// Omitted
});
services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
options.SlidingExpiration = true;
});
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Techsportise API", Version = "v1" });
c.OperationFilter<AddRequiredHeaderParameter>();
var filePath = Path.Combine(PlatformServices.Default.Application.ApplicationBasePath, "TechsportiseOnline.xml");
c.IncludeXmlComments(filePath);
});
services.Configure<JWTSettings>(Configuration.GetSection("JWTSettings"));
services.AddAuthentication()
.AddCookie()
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.IncludeErrorDetails = true;
var secretKey = Configuration.GetSection("JWTSettings:SecretKey").Value;
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretKey));
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = Configuration.GetSection("JWTSettings:Issuer").Value,
ValidateAudience = true,
ValidAudience = Configuration.GetSection("JWTSettings:Audience").Value,
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
};
});
services.AddAuthorization();
services.AddMvcCore(option => option.EnableEndpointRouting = false)
.AddViewLocalization(
LanguageViewLocationExpanderFormat.Suffix,
opts => { opts.ResourcesPath = "Resources"; })
.AddDataAnnotationsLocalization()
.AddApiExplorer();
services.AddAntiforgery();
var skipSSL = Configuration.GetValue<bool>("LocalTest:skipSSL");
// requires using Microsoft.AspNetCore.Mvc;
services.Configure<MvcOptions>(options =>
{
// Set LocalTest:skipSSL to true to skip SSL requrement in
// debug mode. This is useful when not using Visual Studio.
if (!skipSSL)
{
options.Filters.Add(new RequireHttpsAttribute());
}
});
services.AddSingleton<SharedViewLocalizer>();
services.AddSingleton<SharedViewHtmlLocalizer>();
services.AddControllersWithViews();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.EnvironmentName == "Development")
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS etc.), specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Techsportise API V1");
});
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
解决方案
如果您了解错误的原因可能会更好,所以我将在这里解释一下。
我们都使用app.UseAuthentication();
in out 启动类,它的幕后流程是这样的。
在你上面的配置中,你提供了中间件2个方案来处理
services.AddAuthentication()
.AddCookie()
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.IncludeErrorDetails = true;
var secretKey = Configuration.GetSection("JWTSettings:SecretKey").Value;
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretKey));
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = Configuration.GetSection("JWTSettings:Issuer").Value,
ValidateAudience = true,
ValidAudience = Configuration.GetSection("JWTSettings:Audience").Value,
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
};
});
第一个是CookieAuthenticationDefaults.AuthenticationScheme
,第二个是JwtBearerDefaults.AuthenticationScheme
。然后他们都得到了执行(我知道你指出了这个方案,但你没有设置默认方案,所以Schemes.GetDefaultAuthenticateSchemeAsync()
会返回一些可能不是我们想要的东西)。
解决方案:使用默认方案,并放置一些逻辑将管道转发到正确的处理器!
services.AddAuthentication(opts =>
{
opts.DefaultScheme = "multi-scheme-election";
opts.DefaultChallengeScheme = "multi-scheme-election";
})
.AddCookie()
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.IncludeErrorDetails = true;
var secretKey = configuration.GetSection("JWTSettings:SecretKey").Value;
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretKey));
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = configuration.GetSection("JWTSettings:Issuer").Value,
ValidateAudience = true,
ValidAudience = configuration.GetSection("JWTSettings:Audience").Value,
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey
};
})
.AddPolicyScheme("multi-scheme-election", "Your Election scheme processor here",
cfgOpts => cfgOpts.ForwardDefaultSelector = ctx =>
ctx.Request.Headers.ContainsKey("Authorization")
? JwtBearerDefaults.AuthenticationScheme
: CookieAuthenticationDefaults.AuthenticationScheme);
完成了,试一试
[HttpGet("TestAuthentication")]
[Authorize] // No need to specify the scheme here, the default policy will take us the right one
public IActionResult TestAuthentication()
{
return Ok("Authenticated!");
}
P/s:我现在也在用这个。