《进击吧!Blazor!》是本人与张善友老师合作的Blazor零基础入门教程视频,此教程能让一个从未接触过Blazor的程序员掌握开发Blazor应用的能力。
视频地址:
space.bilibili.com/483888821/c…
WebAssembly 是单页应用 (SPA) 框架,用于使用 .NET 生成交互式客户端 Web 应用,采用 C# 代替 JavaScript 来编写前端代码 本系列文章因篇幅有限,省略了部分代码,完整示例代码:
github.com/TimChen44/B…
作者:陈超超
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)
var jwtToken = GetToken(dto.UserName);
return new() { Name = dto.UserName, Token = jwtToken };
[HttpGet]
public UserDto GetUser()
if (User.Identity.IsAuthenticated)
var name = User.Claims.First(x => x.Type == ClaimTypes.Name).Value;
var jwtToken = GetToken(name);
return new UserDto() { Name = name, Token = jwtToken };
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
,直接使用主要缺点是,如果基础身份验证状态数据发生更改,不会自动通知组件。其次是项目中总会有一些自定义的认证逻辑。 所以我们通常写一个类继承他,并重写一些我们自己的逻辑。
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());
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);
public void MarkUserAsAuthenticated(UserDto userDto)
HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", userDto.Token);
UserName = userDto.Name;
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);
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
服务,以便于其他地方使用
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) {
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>
public partial class Login
[Inject] public HttpClient Http { get
[Inject] public MessageService MsgSvr { get
[Inject] public AuthenticationStateProvider AuthProvider { get
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);
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
运行查看效果
更多关于安全
安全是一个很大的话题,这个章节只是介绍了其最简单的实现方式,还有更多内容推荐阅读官方文档:docs.microsoft.com/zh-cn/aspne…
我们通过几张图表,将我们 ToDo 应用中任务情况做个完美统计。
更多关于Blazor学习资料:
[aka.ms/LearnBlazor…
](link.zhihu.com/?target=htt…)