JWT Auth 使用 Authorize Attribute 但不使用 [Authorize (Policy = "Administrator")]

JWT Auth works with Authorize Attribute but not with [Authorize (Policy = "Administrator")]

我有一个 .NetCore 2.2 应用程序使用 Json Web 令牌来验证和授权用户。

当我将 [Authorize] 属性添加到我的控制器时,我能够将 Bearer Token 添加到对这些控制器的任何请求并与数据交互。

当我更改 Auth 属性以包含角色时,例如[授权 (Policy="Administrator")] 请求总是 return 403。

User.cs 模型包含一个值为 User/Administrator 的 Role 枚举。

在 Startup.cs 中我添加了 RequireRole/RequireAuthenticatedUser。

参见 Startup.cs

    public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors();

        services.AddMvc()
            .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
            .AddJsonOptions(options => { options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; });

        // In production, the Angular files will be served from this directory
        services.AddSpaStaticFiles(configuration =>
        {
            configuration.RootPath = "ClientApp/dist";
        });

        #region JWT
        // Configure AppSettings and add to DI  
        var appSettingsSection = Configuration.GetSection("AppSettings");
        services.Configure<AppSettings>(appSettingsSection);

        // Configure jwt authentication
        var appSettings = appSettingsSection.Get<AppSettings>();
        var key = Encoding.ASCII.GetBytes(appSettings.Secret);

        // Add Jwt Authentication Service
        services.AddAuthentication(x =>
        {
            x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(x =>
        {
            x.RequireHttpsMetadata = false;
            x.SaveToken = true;
            x.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(key),
                ValidateIssuer = false,
                ValidateAudience = false
            };
        });
        #endregion

        #region Add Transient DI
        services.AddTransient<IPlayerService, PlayerService>();
        #endregion

        #region Add Authorization
        services.AddAuthorization(options =>
        {
            options.AddPolicy("Administrator",
                p => p.RequireAuthenticatedUser().RequireRole(Role.Administrator.ToString())
            );
            options.AddPolicy("User",
                p => p.RequireAuthenticatedUser().RequireRole(
                    new[] { Role.User.ToString(), Role.User.ToString() }
                )
            );
        });
        #endregion

        #region Cookies
        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.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
            .AddCookie(options => {
        options.AccessDeniedPath = "/User/ErrorNotAuthorised";
        options.LoginPath = "/User/ErrorNotAuthenticated";
    });
        #endregion
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            // seeder recreates and seeds database on each execution
            new DataSeeder(new PlayerService(), new ClubService(), new TeamService(), new TeamPlayerService(), new UserService()).Seed();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseSpaStaticFiles();
        app.UseCookiePolicy();

        app.UseCors(x => x
        .AllowAnyOrigin()
        .AllowAnyMethod()
        .AllowAnyHeader());

        app.UseAuthentication();

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller}/{action=Index}/{id?}");
        });


        app.UseSpa(spa =>
        {
            // To learn more about options for serving an Angular SPA from ASP.NET Core,
            // see https://go.microsoft.com/fwlink/?linkid=864501

            spa.Options.SourcePath = "ClientApp";

            if (env.IsDevelopment())
            {
                spa.UseAngularCliServer(npmScript: "start");
            }
        });
    }
}

示例控制器方法:

    // POST: api/Player
    [Authorize(Policy="Administrator")]
    [HttpPost]
    [ValidateAntiForgeryToken]
    public void Post([FromBody] Player player)
    {
        _service.AddPlayer(player);
    }

此控制器方法 return 是来自所有交互的 403 未经授权请求。我认为我的 JWT 令牌不包含 Role 值,但我不确定如何检查或如何包含它。

感谢任何帮助。

编辑:

Watch on Users

用户class

    public enum Role
{
    Administrator,
    User
}

public class User
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string UserName { get; set; }
    public string Password { get; set; }
    public Team Team { get; set; }
    public Role Role { get; set; }
    public string Token { get; set; }
}

编辑 2:

因此,JWT 使用角色作为身份验证形式真正需要的所有内容都包含在下面的 Startup.cs 函数 ConfigureServices 中。我省略了 JWT class,并将其包含在下面。

我更改了控制器上的 auth 属性以查找 Roles = "Administrator" 而不是 Policies。

Startup.cs

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

        // Configure AppSettings and add to DI
        var appSettingsSection = Configuration.GetSection("AppSettings");
        services.Configure<AppSettings>(appSettingsSection);

        // Configure jwt authentication
        var appSettings = appSettingsSection.Get<AppSettings>();
        var key = Encoding.ASCII.GetBytes(appSettings.Secret);

        // Add Jwt Authentication Service
        services.AddAuthentication(x =>
        {
            x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(x =>
        {
            x.RequireHttpsMetadata = false;
            x.SaveToken = true;
            x.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(key),
                ValidateIssuer = false,
                ValidateAudience = false
            };
        });

JWT Helper class 之前没看懂:

    {       
     // generate Jwt token
        var tokenHandler = new JwtSecurityTokenHandler();
        var key = Encoding.ASCII.GetBytes(secret);
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(new Claim[]
            {
                new Claim(ClaimTypes.Role, user.Role.ToString()),
                new Claim(ClaimTypes.Sid, user.Id.ToString())
            }),
            Expires = DateTime.UtcNow.AddDays(50),
            SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
        };
        var token = tokenHandler.CreateToken(tokenDescriptor);
        user.Token = tokenHandler.WriteToken(token);
         return user;

}

具有角色属性的控制器示例:

            [Authorize(Roles = "Administrator")]
    [HttpPost]
    public void Post([FromBody] Player player)
    {
        _service.AddPlayer(player);
    }

最后,其中大部分是显而易见的,在我开始这个项目之前我应该​​知道不要介意这个 post - 但更新以便将来遇到这个的任何人都能看到更合适的路线。

确保 Role 声明是从 JWT 令牌中提取的。 Role claim name可以这样设置:

.AddJwtBearer(x =>
{
    x.RequireHttpsMetadata = false;
    x.SaveToken = true;
    x.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(key),
        ValidateIssuer = false,
        ValidateAudience = false,

        RoleClaimType = "role" // same name as in your JWT token, as by default it is 
        // "http://schemas.microsoft.com/ws/2008/06/identity/claims/role" 
    };
    options.Events = new JwtBearerEvents
    {
        OnTokenValidated = context =>
        {
            var jwt = (context.SecurityToken as JwtSecurityToken)?.ToString();
            // get your JWT token here if you need to decode it e.g on https://jwt.io
            // And you can re-add role claim if it has different name in token compared to what you want to use in your ClaimIdentity:  
            AddRoleClaims(context.Principal);
            return Task.CompletedTask;
        }
    };

});

private static void AddRoleClaims(ClaimsPrincipal principal)
{
    var claimsIdentity = principal.Identity as ClaimsIdentity;
    if (claimsIdentity != null)
    {
        if (claimsIdentity.HasClaim("role", "AdminRoleNameFromToken"))
        {
            if (!claimsIdentity.HasClaim("role", Role.Administrator.ToString()))
            {
                claimsIdentity.AddClaim(new Claim("role", Role.Administrator.ToString()));
            }
        }
    }
}

我会将您的策略​​重新配置为

options.AddPolicy("Administrator", policy => policy.RequireAssertion(context =>
                    context.User.IsInRole(Role.Administrator.ToString())
                ));

我误用了授权属性的策略扩展。

我应该一直在使用 [Authorize(Roles = "")]。

我更新了问题以反映我的错误。