为什么 ASP.NET Core Identity 2.0 Authorize 过滤器导致我得到 404?

Why is ASP.NET Core Identity 2.0 Authorize filter causing me to get a 404?

我有一个控制器,我只想将其限制为特定角色,比方说 admin。在为用户设置 admin 角色后,我可以使用 IsInRoleAsync 方法(returns 为真)验证他是否在该角色上。当使用 [Authorize(Roles = "admin")] 设置属性时,我得到一个 404 with that very same user 。我正在使用不记名令牌(我认为这无关紧要,但无论如何),这是我尝试调试所做的工作:

Controller w/o [Authorize] : 资源返回。 [确定]

带有 [Authorize] 的控制器:当我使用 Authentication: Bearer [access token] [OK]

时,仅 返回资源

具有 [Authorize(Roles = "admin")] 的控制器:即使在使用设置了角色的用户登录后,我也会收到 404 [NOK]

我不知道我是否遗漏了一些配置,但这是我的 ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    // Add framework services.
    services.AddDbContext<ApplicationDbContext>(options =>
    {
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
        options.UseOpenIddict();
    });
    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    services.AddOpenIddict(opt =>
    {
        opt.AddEntityFrameworkCoreStores<ApplicationDbContext>();
        opt.AddMvcBinders();
        opt.EnableTokenEndpoint("/api/token");
        opt.AllowPasswordFlow();
        opt.DisableHttpsRequirement(); //for dev only!
        opt.UseJsonWebTokens();
        opt.AddEphemeralSigningKey();
        opt.AllowRefreshTokenFlow();
        opt.SetAccessTokenLifetime(TimeSpan.FromMinutes(5));
    });

    services.AddAuthentication(options =>
    {
        options.DefaultScheme = OAuthValidationDefaults.AuthenticationScheme;
        options.DefaultAuthenticateScheme = OAuthValidationConstants.Schemes.Bearer;
        options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
    })
       .AddJwtBearer(options =>
       {
           options.Authority = "http://localhost:44337/";
           options.Audience = "resource_server";
           options.RequireHttpsMetadata = false;
           options.TokenValidationParameters = new TokenValidationParameters
           {
               NameClaimType = OpenIdConnectConstants.Claims.Subject,
               RoleClaimType = OpenIdConnectConstants.Claims.Role
           };                   
       });
    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;

        // Lockout settings
        options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
        options.Lockout.MaxFailedAccessAttempts = 10;
        // User settings
        options.User.RequireUniqueEmail = true;
        // Add application services.
        options.ClaimsIdentity.UserNameClaimType= OpenIdConnectConstants.Claims.Name;
        options.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject;
        options.ClaimsIdentity.RoleClaimType = OpenIdConnectConstants.Claims.Role;
    });

    services.AddSingleton(typeof(RoleManager<ApplicationUser>));
    // Add application services.
    services.AddTransient<IEmailSender, AuthMessageSender>();
    services.AddTransient<ISmsSender, AuthMessageSender>();

我认为您需要检查声明,而不是角色。添加一个 AuthorizeAttribute 例如:

[Authorize(Policy = "AdminOnly")]

然后配置需要声明的策略:

services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy =>
                      policy.RequireClaim(OpenIdConnectConstants.Claims.Role, "Admin"));
});

或者,为了调试目的或更高级的验证,您可以:

services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy =>
                      policy.RequireAssertion(ctx =>
   {
       //do your checks
       return true;
   }));
});

您可能会收到 404 响应,因为身份 - services.AddIdentity() 自动配置为默认身份验证的 sign-in/sign-out 和 challenge/forbidden 方案 - 试图将您重定向到 "access denied page"(Account/AccessDenied 默认情况下),您的应用程序中可能不存在。

尝试覆盖默认的 challenge/forbidden 方案,看看它是否能解决您的问题:

services.AddAuthentication(options =>
{
    // ...
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultForbidScheme = JwtBearerDefaults.AuthenticationScheme;
});

要解决第二个问题,请确保禁用 JWT 声明映射功能。如果不是,JWT 处理程序将 "convert" 您对 ClaimTypes.Role 的所有 role 声明,这将不起作用,因为您将其配置为使用 role 作为ClaimsPrincipal.IsInRole(...) (RoleClaimType = OpenIdConnectConstants.Claims.Role).

services.AddAuthentication(options =>
{
    options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    // ...
    options.SecurityTokenValidators.Clear();
    options.SecurityTokenValidators.Add(new JwtSecurityTokenHandler
    {
        // Disable the built-in JWT claims mapping feature.
        InboundClaimTypeMap = new Dictionary<string, string>()
    });
});