ASP.NET 核心授权属性不适用于 JWT
ASP.NET Core Authorize attribute not working with JWT
我想在 ASP.Net Core 中实现 JWT-based 安全性。现在我想让它做的就是读取 Authorization
header 中的不记名令牌并根据我的标准验证它们。我不需要(也不想)包含 ASP.Net 身份。事实上,我尽量避免使用 MVC 添加的东西,除非我真的需要它们。
我创建了一个最小项目,它演示了这个问题。要查看原始代码,只需查看编辑历史即可。我原以为这个示例会拒绝对 /api/icons
的所有请求,除非它们向 Authorization
HTTP header 提供相应的不记名令牌。示例 实际上允许所有请求.
Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore.Routing;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using System;
using Newtonsoft.Json.Serialization;
namespace JWTSecurity
{
public class Startup
{
public IConfigurationRoot Configuration { get; set; }
public Startup(IHostingEnvironment env)
{
IConfigurationBuilder builder = new ConfigurationBuilder().SetBasePath(env.ContentRootPath);
Configuration = builder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.AddAuthentication();
services.AddMvcCore().AddJsonFormatters(options => options.ContractResolver = new CamelCasePropertyNamesContractResolver());
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("supersecretkey")),
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
}
});
app.UseMvc(routes => routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}"));
}
}
}
Controllers/IconsController.cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace JWTSecurity.Controllers
{
[Route("api/[controller]")]
public class IconsController : Controller
{
[Authorize]
public IActionResult Get()
{
return Ok("Some content");
}
}
}
您也在使用身份认证,它隐含地包含cookie认证。可能您使用身份方案登录并导致身份验证成功。
如果不需要(如果你只需要 jwt 身份验证),请删除身份验证,否则为 Authorize
属性指定 Bearer
方案,如下所示:
[Authorize(ActiveAuthenticationSchemes = "Bearer")]
找到了!
主要问题出在这一行:
services.AddMvcCore().AddJsonFormatters(options => options.ContractResolver = new CamelCasePropertyNamesContractResolver());
我注意到通过从 AddMvcCore() 切换到 AddMvc(),授权突然开始工作了!在深入了解 the ASP.NET source code 之后,为了了解 AddMvc()
的作用,我意识到我需要第二次调用 IMvcBuilder.AddAuthorization()
.
services.AddMvcCore()
.AddAuthorization() // Note - this is on the IMvcBuilder, not the service collection
.AddJsonFormatters(options => options.ContractResolver = new CamelCasePropertyNamesContractResolver());
对于那些甚至尝试了预览答案但没有解决问题的人,下面是我的问题是如何解决的。
[Authorize(AuthenticationSchemes="Bearer")]
找到了这个问题的完美解决方案
您的配置服务 class 应该如下所示
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>
(options => options.Stores.MaxLengthForKeys = 128)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultUI()
.AddDefaultTokenProviders();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
//options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
//options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
//options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
//options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddCookie(cfg => cfg.SlidingExpiration = true)
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = Configuration["JwtIssuer"],
ValidAudience = Configuration["JwtIssuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtKey"])),
ClockSkew = TimeSpan.Zero // remove delay of token when expire
};
});
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = false;
options.Password.RequiredUniqueChars = 6;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.AllowedForNewUsers = true;
// User settings
options.User.RequireUniqueEmail = true;
});
services.AddAuthentication().AddFacebook(facebookOptions =>
{
facebookOptions.AppId = Configuration["Authentication:Facebook:AppId"];
facebookOptions.AppSecret = Configuration["Authentication:Facebook:AppSecret"];
});
//Seting the Account Login page
services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
options.LoginPath = "/Account/Login"; // If the LoginPath is not set here, ASP.NET Core will default to /Account/Login
options.LogoutPath = "/Account/Logout"; // If the LogoutPath is not set here, ASP.NET Core will default to /Account/Logout
options.AccessDeniedPath = "/Account/AccessDenied"; // If the AccessDeniedPath is not set here, ASP.NET Core will default to /Account/AccessDenied
options.SlidingExpiration = true;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
您可以验证 Web API 如下所示的控制器
[Route("api/[controller]")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[ApiController]
public class TaskerController : ControllerBase
{
[HttpGet("[action]")]
//[AllowAnonymous]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
并且您可以像往常一样为 MVC 控制器使用基于身份的授权属性
public class TaskController : Controller
{
[Authorize]
public IActionResult Create()
{
}
}
关键解决方案是.AddCookie(cfg => cfg.SlidingExpiration = true)
在 JWT 身份验证之前添加,即 .AddJwtBearer(//removed for brevity)
将基于 Cookie 的授权设置为默认值,因此 [Authorize] 照常工作,每当您需要 JWT 时,您必须使用 [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
显式调用它
希望它能帮助那些想要将网站作为前端并将俱乐部移动就绪 Web API 作为后端的人。
如果您使用的是自定义方案,则必须使用
[Authorize(AuthenticationSchemes="your custom scheme")]
我刚刚遇到了类似的问题,结果是控制器级别的 [AllowAnonymous]
属性覆盖了应用于该控制器内任何操作的任何 [Authorize]
属性。这是我以前不知道的。
我想在 ASP.Net Core 中实现 JWT-based 安全性。现在我想让它做的就是读取 Authorization
header 中的不记名令牌并根据我的标准验证它们。我不需要(也不想)包含 ASP.Net 身份。事实上,我尽量避免使用 MVC 添加的东西,除非我真的需要它们。
我创建了一个最小项目,它演示了这个问题。要查看原始代码,只需查看编辑历史即可。我原以为这个示例会拒绝对 /api/icons
的所有请求,除非它们向 Authorization
HTTP header 提供相应的不记名令牌。示例 实际上允许所有请求.
Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore.Routing;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using System;
using Newtonsoft.Json.Serialization;
namespace JWTSecurity
{
public class Startup
{
public IConfigurationRoot Configuration { get; set; }
public Startup(IHostingEnvironment env)
{
IConfigurationBuilder builder = new ConfigurationBuilder().SetBasePath(env.ContentRootPath);
Configuration = builder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.AddAuthentication();
services.AddMvcCore().AddJsonFormatters(options => options.ContractResolver = new CamelCasePropertyNamesContractResolver());
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("supersecretkey")),
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
}
});
app.UseMvc(routes => routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}"));
}
}
}
Controllers/IconsController.cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace JWTSecurity.Controllers
{
[Route("api/[controller]")]
public class IconsController : Controller
{
[Authorize]
public IActionResult Get()
{
return Ok("Some content");
}
}
}
您也在使用身份认证,它隐含地包含cookie认证。可能您使用身份方案登录并导致身份验证成功。
如果不需要(如果你只需要 jwt 身份验证),请删除身份验证,否则为 Authorize
属性指定 Bearer
方案,如下所示:
[Authorize(ActiveAuthenticationSchemes = "Bearer")]
找到了!
主要问题出在这一行:
services.AddMvcCore().AddJsonFormatters(options => options.ContractResolver = new CamelCasePropertyNamesContractResolver());
我注意到通过从 AddMvcCore() 切换到 AddMvc(),授权突然开始工作了!在深入了解 the ASP.NET source code 之后,为了了解 AddMvc()
的作用,我意识到我需要第二次调用 IMvcBuilder.AddAuthorization()
.
services.AddMvcCore()
.AddAuthorization() // Note - this is on the IMvcBuilder, not the service collection
.AddJsonFormatters(options => options.ContractResolver = new CamelCasePropertyNamesContractResolver());
对于那些甚至尝试了预览答案但没有解决问题的人,下面是我的问题是如何解决的。
[Authorize(AuthenticationSchemes="Bearer")]
找到了这个问题的完美解决方案 您的配置服务 class 应该如下所示
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>
(options => options.Stores.MaxLengthForKeys = 128)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultUI()
.AddDefaultTokenProviders();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
//options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
//options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
//options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
//options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddCookie(cfg => cfg.SlidingExpiration = true)
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = Configuration["JwtIssuer"],
ValidAudience = Configuration["JwtIssuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtKey"])),
ClockSkew = TimeSpan.Zero // remove delay of token when expire
};
});
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = false;
options.Password.RequiredUniqueChars = 6;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.AllowedForNewUsers = true;
// User settings
options.User.RequireUniqueEmail = true;
});
services.AddAuthentication().AddFacebook(facebookOptions =>
{
facebookOptions.AppId = Configuration["Authentication:Facebook:AppId"];
facebookOptions.AppSecret = Configuration["Authentication:Facebook:AppSecret"];
});
//Seting the Account Login page
services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
options.LoginPath = "/Account/Login"; // If the LoginPath is not set here, ASP.NET Core will default to /Account/Login
options.LogoutPath = "/Account/Logout"; // If the LogoutPath is not set here, ASP.NET Core will default to /Account/Logout
options.AccessDeniedPath = "/Account/AccessDenied"; // If the AccessDeniedPath is not set here, ASP.NET Core will default to /Account/AccessDenied
options.SlidingExpiration = true;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
您可以验证 Web API 如下所示的控制器
[Route("api/[controller]")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[ApiController]
public class TaskerController : ControllerBase
{
[HttpGet("[action]")]
//[AllowAnonymous]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
并且您可以像往常一样为 MVC 控制器使用基于身份的授权属性
public class TaskController : Controller
{
[Authorize]
public IActionResult Create()
{
}
}
关键解决方案是.AddCookie(cfg => cfg.SlidingExpiration = true)
在 JWT 身份验证之前添加,即 .AddJwtBearer(//removed for brevity)
将基于 Cookie 的授权设置为默认值,因此 [Authorize] 照常工作,每当您需要 JWT 时,您必须使用 [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
希望它能帮助那些想要将网站作为前端并将俱乐部移动就绪 Web API 作为后端的人。
如果您使用的是自定义方案,则必须使用
[Authorize(AuthenticationSchemes="your custom scheme")]
我刚刚遇到了类似的问题,结果是控制器级别的 [AllowAnonymous]
属性覆盖了应用于该控制器内任何操作的任何 [Authorize]
属性。这是我以前不知道的。