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 = "")]。
我更新了问题以反映我的错误。
我有一个 .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 = "")]。
我更新了问题以反映我的错误。