《进击吧!Blazor!》是自己与张善友老师单干的 Blazor 零根底入门教程视频,此教程能让一个从未接触过 Blazor 的程序员把握开发 Blazor 利用的能力。
视频地址:https://space.bilibili.com/48…
Blazor WebAssembly 是单页利用 (SPA) 框架,用于应用 .NET 生成交互式客户端 Web 利用,采纳 C# 代替 JavaScript 来编写前端代码
本系列文章因篇幅无限,省略了局部代码,残缺示例代码:https://github.com/TimChen44/…
作者:陈超超
Ant Design Blazor 我的项目贡献者,领有十多年从业教训,长期基于.Net 技术栈进行架构与开发产品的工作,现就职于正泰团体。
邮箱:timchen@live.com
欢送各位读者有任何问题分割我,咱们共同进步。
我的的 ToDo 利用基本功能曾经实现,然而本人的待办当然只有本人晓得,所以咱们这次给咱们的利用减少一些平安方面的性能。
Blazor 身份验证与受权
身份验证
Blazor Server 利用和 Blazor WebAssembly 利用的平安计划有所不同。
- Blazor WebAssembly
Blazor WebAssembly 利用在客户端上运行。因为用户可绕过客户端查看,因为用户可批改所有客户端代码,因而受权仅用于确定要显示的 UI 选项,所有客户端应用程序技术都是如此。
- Blazor Server
Blazor Server 利用通过应用 SignalR 创立的实时连贯运行。建设连贯后,将解决基于 SignalR 的利用的身份验证。可基于 cookie 或一些其余持有者令牌进行身份验证。
受权
AuthorizeView
组件依据用户是否取得受权来选择性地显示 UI 内容。如果只须要为用户显示数据,而不须要在过程逻辑中应用用户的标识,那么此办法很有用。
<AuthorizeView>
<Authorized>
<!-- 验证通过显示 -->
</Authorized>
<NotAuthorized>
<!-- 验证不通过显示 -->
</NotAuthorized>
</AuthorizeView>
Blazor 中应用 Token
在 Blazor WebAssembly 模式下,因为利用都在客户端运行,所以应用 Token 作为身份认证的形式是一个比拟好的抉择。
根本的应用时序图如下
对于平安要求不高的利用采纳这个办法简略、易保护,齐全没有问题。
然而 Token 自身在安全性上存在以下两个危险:
- Token 无奈登记,所以能够在 Token 有效期内发送的非法申请,服务端无能为力。
- Token 通过 AES 加密存储在客户端,实践上能够进行离线破解,破解后就能任意伪造 Token。
因而遇到平安要求十分高的利用时,咱们须要认证服务进行 Token 的有效性验证
革新 ToDo
接着咱们对之前的 ToDo 我的项目进行革新,让他反对登录性能。
ToDo.Shared
先把前后端交互所需的 Dto 创立了
public class LoginDto
{public string UserName { get; set;}
public string Password {get; set;}
}
public class UserDto
{public string Name { get; set;}
public string Token {get; set;}
}
ToDo.Server
先革新服务端,增加必要援用,编写身份认证代码等
增加援用
- Microsoft.AspNetCore.Authentication.JwtBearer
Startup.cs
增加 JwtBearer 配置
public void ConfigureServices(IServiceCollection services)
{
//......
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,// 是否验证 Issuer
ValidateAudience = true,// 是否验证 Audience
ValidateLifetime = true,// 是否验证生效工夫
ValidateIssuerSigningKey = true,// 是否验证 SecurityKey
ValidAudience = "guetClient",//Audience
ValidIssuer = "guetServer",//Issuer,这两项和签发 jwt 的设置统一
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("123456789012345678901234567890123456789"))// 拿到 SecurityKey
};
});
}
此处定义了 Token 的密钥,规定等,理论我的项目时能够将这些信息放到配置中。
AuthController.cs
行政验证控制器,用于验证用户身份,创立 Token 等。
[ApiController]
[Route("api/[controller]/[action]")]
public class AuthController : ControllerBase
{
// 登录
[HttpPost]
public UserDto Login(LoginDto dto)
{
// 模仿取得 Token
var jwtToken = GetToken(dto.UserName);
return new() { Name = dto.UserName, Token = jwtToken};
}
// 取得用户,当页面客户端页面刷新时调用以取得用户信息
[HttpGet]
public UserDto GetUser()
{if (User.Identity.IsAuthenticated)// 如果 Token 无效
{var name = User.Claims.First(x => x.Type == ClaimTypes.Name).Value;// 从 Token 中拿出用户 ID
// 模仿取得 Token
var jwtToken = GetToken(name);
return new UserDto() { Name = name, Token = jwtToken};
}
else
{return new UserDto() {Name = null, Token = null};
}
}
public string GetToken(string name)
{
// 此处退出账号密码验证代码
var claims = new Claim[]
{new Claim(ClaimTypes.Name,name),
new Claim(ClaimTypes.Role,"Admin"),
};
var key = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes("123456789012345678901234567890123456789"));
var expires = DateTime.Now.AddDays(30);
var token = new JwtSecurityToken(
issuer: "guetServer",
audience: "guetClient",
claims: claims,
notBefore: DateTime.Now,
expires: expires,
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
ToDo.Client
革新客户端,让客户端反对身份认证
增加援用
- Microsoft.AspNetCore.Components.Authorization
AuthenticationStateProvider
AuthenticationStateProvider
是 AuthorizeView
组件和 CascadingAuthenticationState
组件用于获取身份验证状态的根底服务。
通常不间接应用 AuthenticationStateProvider
,间接应用次要毛病是,如果根底身份验证状态数据产生更改,不会主动告诉组件。其次是我的项目中总会有一些自定义的认证逻辑。
所以咱们通常写一个类继承他,并重写一些咱们本人的逻辑。
//AuthProvider.cs
public class AuthProvider : AuthenticationStateProvider
{
private readonly HttpClient HttpClient;
public string UserName {get; set;}
public AuthProvider(HttpClient httpClient)
{HttpClient = httpClient;}
public async override Task<AuthenticationState> GetAuthenticationStateAsync()
{
// 这里取得用户登录状态
var result = await HttpClient.GetFromJsonAsync<UserDto>($"api/Auth/GetUser");
if (result?.Name == null)
{MarkUserAsLoggedOut();
return new AuthenticationState(new ClaimsPrincipal());
}
else
{var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.Name, result.Name));
var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(claims, "apiauth"));
return new AuthenticationState(authenticatedUser);
}
}
/// <summary>
/// 标记受权
/// </summary>
/// <param name="loginModel"></param>
/// <returns></returns>
public void MarkUserAsAuthenticated(UserDto userDto)
{HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", userDto.Token);
UserName = userDto.Name;
// 此处应该依据服务器的返回的内容进行配置本地策略,作为演示,默认增加了“Admin”var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.Name, userDto.Name));
claims.Add(new Claim("Admin", "Admin"));
var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(claims, "apiauth"));
var authState = Task.FromResult(new AuthenticationState(authenticatedUser));
NotifyAuthenticationStateChanged(authState);
// 慈湖能够能够将 Token 存储在本地存储中,实现页面刷新无需登录
}
/// <summary>
/// 标记登记
/// </summary>
public void MarkUserAsLoggedOut()
{
HttpClient.DefaultRequestHeaders.Authorization = null;
UserName = null;
var anonymousUser = new ClaimsPrincipal(new ClaimsIdentity());
var authState = Task.FromResult(new AuthenticationState(anonymousUser));
NotifyAuthenticationStateChanged(authState);
}
}
NotifyAuthenticationStateChanged
办法会告诉身份验证状态数据(例如 AuthorizeView)使用者应用新数据从新出现。HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", userDto.Token);
将 HTTP 申请头中退出 Token,这样之后所有的申请都会带上 Token。
在 Program
中注入 AuthProvider
服务,以便于其余中央应用
//Program.cs
builder.Services.AddScoped<AuthenticationStateProvider, AuthProvider>();
在 Program
中配置反对的策略
builder.Services.AddAuthorizationCore(option =>
{option.AddPolicy("Admin", policy => policy.RequireClaim("Admin"));
});
登录界面
增加 Login.razor
组件,代码如下
<div style="margin:100px">
<Spin Spinning="isLoading">
@if (model != null) {
<form
OnFinish="OnSave"
Model="@model"
LabelCol="new ColLayoutParam() {Span = 6}"
>
<FormItem Label="用户名">
<input @bind-Value="context.UserName" />
</FormItem>
<FormItem Label="明码">
<input @bind-Value="context.Password" type="password" />
</FormItem>
<FormItem WrapperColOffset="6">
<button type="@ButtonType.Primary" HtmlType="submit"> 登录 </button>
</FormItem>
</form>
}
</Spin>
</div>
public partial class Login
{[Inject] public HttpClient Http {get; set;}
[Inject] public MessageService MsgSvr {get; set;}
[Inject] public AuthenticationStateProvider AuthProvider {get; set;}
LoginDto model = new LoginDto();
bool isLoading;
async void OnLogin()
{
isLoading = true;
var httpResponse = await Http.PostAsJsonAsync<LoginDto>($"api/Auth/Login", model);
UserDto result = await httpResponse.Content.ReadFromJsonAsync<UserDto>();
if (string.IsNullOrWhiteSpace(result?.Token) == false )
{MsgSvr.Success($"登录胜利");
((AuthProvider)AuthProvider).MarkUserAsAuthenticated(result);
}
else
{MsgSvr.Error($"用户名或明码谬误");
}
isLoading = false;
InvokeAsync(StateHasChanged);
}
}
登录界面代码很简略,就是向 api/Auth/Login
申请,依据返回的后果判断是否登入胜利。((AuthProvider)AuthProvider).MarkUserAsAuthenticated(result);
标记身份认证状态曾经批改。
批改布局
批改 MainLayout.razor
文件
<CascadingAuthenticationState>
<AuthorizeView>
<Authorized>
<Layout>
<Sider Style="overflow: auto;height: 100vh;position: fixed;left: 0;">
<div class="logo"> 进击吧!Blazor!</div>
<menu Theme="MenuTheme.Dark" Mode="@MenuMode.Inline">
<menuitem RouterLink="/"> 主页 </menuitem>
<menuitem RouterLink="/today" RouterMatch="NavLinkMatch.Prefix">
我的一天
</menuitem>
<menuitem RouterLink="/star" RouterMatch="NavLinkMatch.Prefix">
重要工作
</menuitem>
<menuitem RouterLink="/search" RouterMatch="NavLinkMatch.Prefix">
全副
</menuitem>
</menu>
</Sider>
<Layout Class="site-layout"> @Body </Layout>
</Layout>
</Authorized>
<NotAuthorized>
<ToDo.Client.Pages.Login></ToDo.Client.Pages.Login>
</NotAuthorized>
</AuthorizeView>
</CascadingAuthenticationState>
当受权通过后显示 <AuthorizeView>
中<Authorized>
的菜单及主页,反之显示 <NotAuthorized>
的Login
组件内容。
当须要依据权限显示不同内容,能够应用 <AuthorizeView>
的Policy
属性实现,具体是在 AuthenticationStateProvider
中通过配置策略,比方示例中 claims.Add(new Claim("Admin", "Admin"));
就增加了 Admin
策略,在页面上只需 <AuthorizeView Policy="Admin">
就能够管制只有 Admin
策略的账户显示其内容了。CascadingAuthenticationState
级联身份状态,它采纳了 Balzor 组件中级联机制,这样咱们能够在任意层级的组件中应用 AuthorizeView
来管制 UI 了 AuthorizeView
组件依据用户是否取得受权来选择性地显示 UI 内容。Authorized
组件中的内容只有在取得受权时显示。NotAuthorized
组件中的内容只有在未经受权时显示。
批改 _Imports.razor
文件,增加必要的援用
@using Microsoft.AspNetCore.Components.Authorization
运行查看成果
更多对于平安
平安是一个很大的话题,这个章节只是介绍了其最简略的实现形式,还有更多内容举荐浏览官网文档:
https://docs.microsoft.com/zh…
次回预报
咱们通过几张图表,将咱们 ToDo 利用中工作状况做个完满统计。
学习材料
更多对于 Blazor 学习材料:https://aka.ms/LearnBlazor