【问题标题】:ASP.NET Core Authorize attribute not working with JWTASP.NET Core Authorize 属性不适用于 JWT
【发布时间】:2017-03-31 11:43:19
【问题描述】:

我想在 ASP.Net Core 中实现基于 JWT 的安全性。目前,我想做的就是读取Authorization 标头中的不记名令牌,并根据我的标准对其进行验证。我不需要(也不想)包含 ASP.Net Identity。事实上,除非我真的需要它们,否则我会尽可能多地避免使用 MVC 添加的东西。

我创建了一个最小的项目来演示这个问题。要查看原始代码,只需查看编辑历史记录。我期待这个示例拒绝所有对/api/icons 的请求,除非它们为Authorization HTTP 标头提供相应的不记名令牌。该示例实际上允许所有请求

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?}"));
        }
    }
}

控制器/图标控制器.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");
        }
    }
}

【问题讨论】:

  • 你能显示你应用了授权属性的方法和类吗?
  • 您的 JWT 令牌中间件在哪里?您的服务正在通过 /token 端点或类似的东西正确生成 JWT?
  • 此时,我不关心生成令牌,我只关心拒绝任何在标头中不提供令牌的内容。
  • 你现在拥有的和我拥有的非常相似,除了我有 ValidateIssuer = true 和 ValidateAudience = true,但我认为这些不是必需的。我没有 services.AddAuthentication();要么。
  • 是的,我只是尝试添加它们,没有区别。一定有我想念的东西...

标签: c# asp.net-core asp.net-core-webapi


【解决方案1】:

我刚刚遇到了类似的问题,结果发现控制器级别的 [AllowAnonymous] 属性会覆盖应用于该控制器内任何操作的任何 [Authorize] 属性。这是我以前不知道的。

【讨论】:

    【解决方案2】:

    如果您使用自定义方案,则必须使用

    [Authorize(AuthenticationSchemes="your custom scheme")]
    

    【讨论】:

      【解决方案3】:

      找到了这个问题的完美解决方案 您的配置服务类应如下所示

      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 =&gt; cfg.SlidingExpiration = true) 在 JWT 身份验证之前添加,即 .AddJwtBearer(//removed for brevity) 将基于 Cookie 的授权设置为默认值,因此 [Authorize] 照常工作,每当您需要 JWT 时,您必须使用 [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] 显式调用它

      希望它能帮助那些希望将网站作为前端并将移动就绪的 Web API 作为后端的人。

      【讨论】:

      • 问题中的关键点之一是I don't need (and don't want) to include ASP.Net Identity。使用标准配置设置 Asp.Net 很容易,但目标是创建一个使用 MvcCore 和 JwtAuthentication 的最小项目
      • 答案涵盖了两种类型的身份验证机制,您可以根据需要跳过其中任何一种。您只需注释掉您不需要的部分@AndrewWilliamson
      • 这不仅仅是注释掉不需要的部分的问题。您的ConfigureServices 方法调用.AddMvc() 而不是.AddMvcCore()。我的整个问题是基于当我使用.AddMvcCore() 时身份验证不起作用,我必须通读.AddMvc() 的源代码才能弄清楚有什么区别。当您提供答案时,它应包含最少 量的更改以回答问题。否则用户仍然需要弄清楚你的代码的哪一部分解决了问题
      • 感谢您愿意与他人分享您的经验 - 不要停下来,只记得专注于用最小的例子回答问题
      • 感谢[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] 属性。就我而言,对于 WebApi,我想使用 JWTAuth,对于 ViewController,我想使用 CookieAuth。然后我创建了双重 AddAuthentication()。但不幸的是,由于我使用的是[Authorize] 而不添加参数。未经授权的页面总是点击 JWTAuth 设置。但是在[Authorize] 上添加参数后,这解决了我的问题
      【解决方案4】:

      对于那些甚至尝试了预览答案但没有解决问题的人,下面是我的问题是如何解决的。

      [Authorize(AuthenticationSchemes="Bearer")]
      

      【讨论】:

      【解决方案5】:

      找到了!

      主要问题出在这一行:

      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());
      

      【讨论】:

      • 这也引起了我的注意(因为我使用的是AddMvcCore)。对 AddAuthorization() 的调用添加了 AuthorizationApplicationModelProvider,它在控制器上查找 Authorize/AllowAnonymous 并添加适当的策略。
      • 不要忘记AddDataAnnotations,如果您将它们用于请求验证!
      • Authorize 属性在使用services.AddMvcCore().AddAuthorization()后开始工作
      • 我想你可以查看这个答案stackoverflow.com/a/63446357/4307338
      • @HoqueMDZahidul 目的是使用MvcCore 生成一个最小示例。您链接到的答案显示了一个有效的 asp 网络配置,但它没有显示 使令牌身份验证工作的最低配置。如果我只想让身份验证工作,我可以继续使用.AddMvc() 而不是.AddMvcCore()。您的回答没有解决原来的问题
      【解决方案6】:

      您也在使用身份验证,它隐式包含 cookie 验证。可能您使用身份方案登录并导致身份验证成功。

      如果不需要身份认证,则删除身份认证(如果您只想要jwt认证),否则为Authorize属性指定Bearer方案,如下所示:

      [Authorize(ActiveAuthenticationSchemes = "Bearer")]
      

      【讨论】:

      • 我明天回去工作的时候试试这个,希望你是对的。关于这一点,我使用身份的原因纯粹是为了隐藏/抽象密码散列。你会建议什么替代方案?
      • Identity 是推荐的解决方案。如果你需要,那就去吧。据我了解,您想使用身份和 jwt 可选。在这种情况下,您应该指定要限制的身份验证方案。例如,如果您只需要 jwt 身份验证,则应该使用承载方案。
      • 您不需要 ActiveAuthenticationSchemes 来使用 JWT。只要您正确发送令牌,就可以在没有它的情况下工作。
      • @SledgeHammer 当然,通常你不需要它。但我假设是由身份认证引起的问题,并且还假设 OP 只想进行 jwt 认证。
      • 你是我的英雄
      猜你喜欢
      • 2019-08-12
      • 2018-11-08
      • 2023-03-15
      • 2021-05-20
      • 2021-11-24
      • 2017-07-27
      • 2018-08-19
      • 1970-01-01
      • 2017-03-15
      相关资源
      最近更新 更多