Blazor应用程序基于角色的授权
原文:https://chrissainty.com/securing-your-blazor-apps-configuring-role-based-authorization-with-client-side-blazor/
什么是基于角色的授权?
当涉及ASP.NET Core授权时,我们有两种选择,基于角色和基于策略(也有基于声明的,但那只是基于策略的一种特殊类型)。
基于角色的授权最初是在ASP.NET(ASP.NET Core之前)中引入,这是一种限制对资源访问的声明性方法。
开发人员可以指定用户必须是其成员的特定角色的名称,以便访问特定的资源。一般是使用[Authorize]属性指定一个角色或角色列表[Authorize(Roles="Admin")]。用户可以是单个角色的成员,也可以是多个角色的成员。
如何创建和管理角色取决于所使用的备份存储。到目前为止我们一直使用ASP.NET Core Identity,我们将继续使用它来管理和存储我们的角色。
本文章代码将基于前一篇文章基础上搭建。
设置ASP.NET Core Identity角色
我们需要添加角色服务到我们的应用中。我们需要更新Startup类中的ConfigureService方法。
services.AddDefaultIdentity<IdentityUser>()
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
IdentityRole是ASP.NET Core Identity提供的默认角色类型。如果它无法满足你的需求,你可以提供其他的角色类型。
接下来我们将为数据库添加一些角色数据-添加一个用户和管理员角色。为此,我们将重载ApplicationDbContext中的方法OnModelCreating。
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions options) : base(options) {
} protected override void OnModelCreating(ModelBuilder builder) {
base.OnModelCreating(builder); builder.Entity<IdentityRole>().HasData(new IdentityRole { Name = "User", NormalizedName = "USER", Id = Guid.NewGuid().ToString(), ConcurrencyStamp = Guid.NewGuid().ToString() });
builder.Entity<IdentityRole>().HasData(new IdentityRole { Name = "Admin", NormalizedName = "ADMIN", Id = Guid.NewGuid().ToString(), ConcurrencyStamp = Guid.NewGuid().ToString() });
}
}
完成之后,我们需要生成迁移,然后将其应用到数据库。
Add-Migration SeedRoles
Update-Database
为角色分配用户
现在我们已经有一些可用的角色了,我们现在来更新账户控制器(Accounts controller)创建用户的动作。
在新增用户时候为其分配User角色。如果新用户的电子邮件以admin开头,则为其分配User和Admin角色组。
[HttpPost]
public async Task<IActionResult> Post([FromBody]RegisterModel model) {
var newUser = new IdentityUser { UserName = model.Email, Email = model.Email }; var result = await _userManager.CreateAsync(newUser, model.Password); if (!result.Succeeded) {
var errors = result.Errors.Select(x => x.Description); return BadRequest(new RegisterResult { Successful = false, Errors = errors }); } //为所有的新用户分配User角色
await _userManager.AddToRoleAsync(newUser, "User"); //如果电子邮件以'admin'开头则分配Admin角色
if (newUser.Email.StartsWith("admin")) {
await _userManager.AddToRoleAsync(newUser, "Admin");
} return Ok(new RegisterResult { Successful = true });
}
现在我们在用户注册时为其分配了角色,但我们需要将这些信息传递给Blazor。我们需要更新JSON Web Token中的声明来处理这个需求。
将角色声明添加到JWT
现在我们来更新登录控制器(Login controller)中的Login方法。先以下用于生成声明的代码。
var claims = new[]
{
new Claim(ClaimTypes.Name, login.Email)
};
并使用以下代码替换。
var user = await _signInManager.UserManager.FindByEmailAsync(login.Email);
var roles = await _signInManager.UserManager.GetRolesAsync(user); var claims= new List<Claim>(); claims.Add(new Claim(ClaimTypes.Name, login.Email)); foreach (var role in roles) {
claims.Add(new Claim(ClaimTypes.Role, role));
}
我们通过UserManager获取当前用户并获取用户拥有的角色。之前是将用户电子邮件添加到Name声明,现在如果用户拥有角色,我们则循环将角色添加到Role声明中。
关于角色声明有一点比较很重要问题。如果一个用户拥有两个角色,那么这两个角色声明会被添加到JWT中。
http://schemas.microsoft.com/ws/2008/06/identity/claims/role - "User"
http://schemas.microsoft.com/ws/2008/06/identity/claims/role - "Admin"
然后事实上并非如此,而是两个角色合并为一个数组。
http://schemas.microsoft.com/ws/2008/06/identity/claims/role - ["User", "Admin"]
关于这一点很重要,在Blazor客户端处理角色时需要注意。
在Blazor客户端使用角色
我们将角色分配给新用户,当他们登录时,我们通过JWT返回这些角色。那么在Blazor内部要如何使用角色呢?
在这个问题上目前微软官方并未提供任何可以帮助我们处理角色的东西,所以我们必须手动处理它。
private IEnumerable<Claim> ParseClaimsFromJwt(string jwt)
{
var claims = new List<Claim>();
var payload = jwt.Split('.')[];
var jsonBytes = ParseBase64WithoutPadding(payload);
var keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes); keyValuePairs.TryGetValue(ClaimTypes.Role, out object roles); if (roles != null)
{
if (roles.ToString().Trim().StartsWith("["))
{
var parsedRoles = JsonSerializer.Deserialize<string[]>(roles.ToString()); foreach (var parsedRole in parsedRoles)
{
claims.Add(new Claim(ClaimTypes.Role, parsedRole));
}
}
else
{
claims.Add(new Claim(ClaimTypes.Role, roles.ToString()));
} keyValuePairs.Remove(ClaimTypes.Role);
} claims.AddRange(keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString()))); return claims;
} private byte[] ParseBase64WithoutPadding(string base64)
{
switch (base64.Length % )
{
case : base64 += "=="; break;
case : base64 += "="; break;
}
return Convert.FromBase64String(base64);
}
上面代码对JWT进行解码、提取声明并返回声明。但我们没有涉及的是我对其进行了修改,以处理特殊情况下的角色。
如果存在角色声明,那么我们将检查第一个字符是否为[,表名它是一个JSON数组。如果找到roles声明,则解析提取角色名称,循环遍历角色名称,并将每个角色名称作为声明添加。如果roles不是一个数组,则作为单个角色声明添加。
这个方法不一定是最好的,但它确实实现了我们的目的。
我们需要更新MarkUserAsAuthenticated方法来调用ParseClaimsFromJwt。
public void MarkUserAsAuthenticated(string token) {
var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(token), "jwt"));
var authState = Task.FromResult(new AuthenticationState(authenticatedUser));
NotifyAuthenticationStateChanged(authState);
}
最后,我们需要更新AuthService中的Login方法,以便在调用MarkUserAsAuthenticated时传递令牌而不是电子邮件。
public async Task<LoginResult> Login(LoginModel loginModel) {
var result = await _httpClient.PostJsonAsync<LoginResult>("api/Login", loginModel);
if (result.Successful) {
await _localStorage.SetItemAsync("authToken", result.Token);
((ApiAuthenticationStateProvider)_authenticationStateProvider).MarkUserAsAuthenticated(result.Token);
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", result.Token);
return result;
}
return result;
}
现在,我们应该能够将基于角色的授权应用到我们的应用程序中。我们来关注下API的处理。
将基于角色的授权应用于API
将WeatherForecastController上的Get方法设置为仅对Admin角色中经过身份验证的用户可访问。我们使用Authorize属性并指定用于访问它的角色。(这里在默认生成模版与原文有出入)
[HttpGet]
[Authorize(Roles = "Admin")]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(, ).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-, ),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
如果您创建一个属于Admin角色的新用户,并在Blazor应用程序中访问Fetch Data页面,您应该可以看到一切都按预期的加载。

但你如果创建一个普通的用户并执行相同的操作,您应该会看到页面被卡在Loading...。

在Blazor中使用基于角色的授权
Blazor还可以使用Authorize属性来保护页面。这是通过使用@attribute指令来应该[Authorize]属性来实现的。您还可以使用AuthorizeView组件来限制对页面部分的访问。
在 Blazor WebAssembly 应用中,可以绕过授权检查,因为用户可以修改所有客户端代码。 所有客户端应用程序技术都是如此,其中包括 JavaScript SPA 框架或任何操作系统的本机应用程序。
始终对客户端应用程序访问的任何 API 终结点内的服务器执行授权检查。
由于预测数据只对管理员用户可用,所以我们使用Authorize属性限制对该页面的访问。
@page "/fetchdata"
@attribute [Authorize(Roles = "Admin")]
现在尝试使用管理用户登录到该页面。一切应该都正常加载。然后尝试使用普通用户登录,您应该会看到一条未经授权的消息。

我们来测试一下AuthorizeView,在主页(index.razor)添加如下代码。
<AuthorizeView Roles="User">
<p>You can only see this if you're in the User role.</p>
</AuthorizeView> <AuthorizeView Roles="Admin">
<p>You can only see this if you're in the Admin role.</p>
</AuthorizeView>
同样,使用管理员用户账户登录。您应该看到这两条消息,因为您同时拥有这两个角色权限。

如果您使用普通用户登录则只能看到第一条消息。

总结
在这篇文章中,我们了解了什么是基于角色的授权以及如何使用ASP.NET Core Identity来设置和管理角色。然后我们讨论了如何使用JSON Web Tokens将角色从API传递给客户端并处理在Blazor中的角色声明,最后在API和Blazor上实现一些基于角色的授权检查。
我只是想重申一下,您不能仅仅依赖于客户端身份验证或授权,客户端永远不能被信任。必须始终在服务器上执行身份验证和授权检查。
附上代码(Github)
Blazor应用程序基于角色的授权的更多相关文章
- Blazor应用程序基于策略的授权
原文:https://chrissainty.com/securing-your-blazor-apps-configuring-policy-based-authorization-with-bla ...
- Asp.Net Core--基于角色的授权
翻译如下: 当创建身份时,它可以属于一个或多个角色,例如Tracy可以属于管理员和用户角色,而Scott可以仅属于用户角色. 如何创建和管理这些角色取决于授权过程的后备存储. 角色通过ClaimsPr ...
- ASP.NET MVC 随想录——探索ASP.NET Identity 身份验证和基于角色的授权,中级篇
在前一篇文章中,我介绍了ASP.NET Identity 基本API的运用并创建了若干用户账号.那么在本篇文章中,我将继续ASP.NET Identity 之旅,向您展示如何运用ASP.NET Ide ...
- Security » Authorization » 基于角色的授权
Role based Authorization¶ 基于角色的授权 133 of 153 people found this helpful When an identity is created i ...
- Asp.net中基于Forms验证的角色验证授权
Asp.net的身份验证有有三种,分别是"Windows | Forms | Passport",其中又以Forms验证用的最多,也最灵活. Forms 验证方式对基于用户的验证授 ...
- ASP.NET Identity 身份验证和基于角色的授权
ASP.NET Identity 身份验证和基于角色的授权 阅读目录 探索身份验证与授权 使用ASP.NET Identity 身份验证 使用角色进行授权 初始化数据,Seeding 数据库 小结 在 ...
- ASP.NET Core 2.1中基于角色的授权
ASP.NET Core 2.1中基于角色的授权 授权是来描述用户能够做什么的过程.例如,只允许管理员用户可以在电脑上进行软件的安装以及卸载.而非管理员用户只能使用软件而不能进行软件的安装以及卸载.它 ...
- ASP.NET Core 使用 JWT 自定义角色/策略授权需要实现的接口
目录 ① 存储角色/用户所能访问的 API ② 实现 IAuthorizationRequirement 接口 ③ 实现 TokenValidationParameters ④ 生成 Token ⑤ ...
- java:shiro(认证,赋予角色,授权...)
1.shiro(权限框架(认证,赋予角色,授权...)): readme.txt(运行机制): 1.从jsp的form中的action属性跳转到springmvc的Handler中(controlle ...
随机推荐
- Vue基础语法(二)
class绑定 使用方式:v-bind:class="expression" expression的类型:字符串.数组.对象 style绑定 v-bind:style=" ...
- ASP.Net MVC 路由及路由调试工具RouteDebug
一.路由规则 1.可以创建多条路由规则,每条路由的name属性不相同 2.路由规则有优先级,最上面的路由规则优先级越高 App_Start文件下的:RouteConfig.cs public stat ...
- CSS filter滤镜试玩
1.模糊(blur). 用法:给相应元素设置高斯模糊,传入的px数值越大越模糊. 2.亮度(brightness). 用法:给元素设置亮度,0%为全黑,100%为元素原始亮度,>100%表示会比 ...
- 「SAP 技术」SAP MM 给合同的ITEM上传附件以及附件查询
SAP MM 给合同的ITEM上传附件以及附件查询 1,使用事务代码 CV01N为合同上传附件, Document:输入6100000829, Document type 101 (contract) ...
- NetCoreAPI添加Swagger
public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; ...
- 记录console的使用
一般信息:console.info("这是info") 除错信息:console.debug() 警告提示:console.warn() 错误提示:console.error() ...
- Jmeter之命令行生成HTML报告
其实每次使用jemter.bat文件启动JMeter时,命令行窗口都会提示我们不要使用GUI窗口进行测试,除非是进行调试脚本 使用命令行生成结果也很测试报告也很简单 jmeter -n -t [jmx ...
- PHP 循环引用的问题
问题 为了引出问题, 先来看下面一段代码: <?php $arr = [ 'a', 'b', 'c', 'd', ]; foreach ($arr as &$each){ echo $e ...
- pdfium ppm demo
#include "fpdfview.h" #include <iostream> #include <string> #include <strin ...
- hadoop自带RPC的使用 代码demo
引入的三方包 <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop- ...