如果自学Blazor ,因为增加了许多知识点,有许多问题会是一头雾水,难以理清。Blazor Server 的登录与退出就是一个非常难以理清的问题,因为我有强迫症,既然用了Blazor,我难道又要退回去用Razor Page?新建立的Blazor Server程序,使用Identity的个人标识账户,它的登录与退出默认使用的是Razor Page,因为它能非常清晰地得到ClaimsPrincipal 的User,
UserManager和SignInManager几乎包办了一切用户操作而不用怎么操心。而令人遗憾的是
UserManager
和
SignInManager
不支持Razor组件,特别是
SignInManager不支持用于Service,
所以想抛开Razor Page,它的登录与退出只能另想办法,好像一切要推倒重来。虽然Mvc也有User,所以在网上查找大都是用Api Controller。
然而,这2天它又可气的新出了Maui,它的功用更加强大,但学起来不知道又会是怎样?
Blazor的数据操作其实是简便的,它不用Razor Page那样在客户端进行对象字段一一匹配,然后在服务端拼接生成对象,万一那个没有匹配到,一切玩完。Blazor是对对象直接操作,通过时时连通的通道,通过注册的Service,所以,登录与退出其实也是这样,把账户和密码传过去,或操作退出的服务,就可以了。那么,
SignInManager它还做好多工作,所以,在Blazor Server 的登录与退出中,要加上一些其他的动作,其实就是实现AuthenticationStateProvider,在网上查找,这个没有让我满意的结果。
直接上代码:
public class RevalidatingIdentityAuthenticationStateProvider<TUser>
: RevalidatingServerAuthenticationStateProvider where TUser : class
private readonly ILoggerFactory _loggerFactory;
private readonly IServiceScopeFactory _scopeFactory;
private readonly IdentityOptions _options;
private readonly ILocalStorageService _localStorage;
private readonly HttpClient _httpClient;
private readonly IUserClaimsPrincipalFactory<TUser> _principalFactory;
private readonly IOptionsMonitor<JwtOption> _jwtOpt;
private const string authToken = "xxxxxxToken";
public RevalidatingIdentityAuthenticationStateProvider(
ILoggerFactory loggerFactory,
IServiceScopeFactory scopeFactory,
IOptions<IdentityOptions> optionsAccessor,
ILocalStorageService localStorage,
HttpClient httpClient,
IUserClaimsPrincipalFactory<TUser> principalFactory,
IOptionsMonitor<JwtOption> jwtOpt)
: base(loggerFactory)
_loggerFactory = loggerFactory;
_scopeFactory = scopeFactory;
_options = optionsAccessor.Value;
_localStorage = localStorage;
_httpClient = httpClient;
_principalFactory = principalFactory;
_jwtOpt = jwtOpt;
protected override TimeSpan RevalidationInterval => TimeSpan.FromMinutes(30);
protected override async Task<bool> ValidateAuthenticationStateAsync(
AuthenticationState authenticationState, CancellationToken cancellationToken)
// Get the user manager from a new scope to ensure it fetches fresh data
var scope = _scopeFactory.CreateScope();
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<TUser>>();
return await ValidateSecurityStampAsync(userManager, authenticationState.User);
finally
if (scope is IAsyncDisposable asyncDisposable)
await asyncDisposable.DisposeAsync();
scope.Dispose();
private async Task<bool> ValidateSecurityStampAsync(UserManager<TUser> userManager, ClaimsPrincipal principal)
var user = await userManager.GetUserAsync(principal);
if (user == null)
return false;
else if (!userManager.SupportsUserSecurityStamp)
return true;
var principalStamp = principal.FindFirstValue(_options.ClaimsIdentity.SecurityStampClaimType);
var userStamp = await userManager.GetSecurityStampAsync(user);
return principalStamp == userStamp;
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
var savedToken = await _localStorage.GetItemAsync<string>(authToken);
if (string.IsNullOrWhiteSpace(savedToken))
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", savedToken);
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(savedToken), "jwt")));
public async Task<bool> MarkUserAsAuthenticated(LoginDto rqtDto)
var scope = _scopeFactory.CreateScope();
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<TUser>>();
var user = await userManager.FindByNameAsync(rqtDto.UserName);
if (user != null)
var result = await userManager.CheckPasswordAsync(user, rqtDto.Password);
if (result)
var principalUser = await _principalFactory.CreateAsync(user);
if (principalUser != null)
var jwtSetting = _jwtOpt.CurrentValue;
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSetting.Key));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var expiry = DateTime.Now.AddHours(jwtSetting.ExpiryInHours);
var token = new JwtSecurityToken(jwtSetting.Issuer, jwtSetting.Audience, principalUser.Claims, expires: expiry, signingCredentials: creds);
var tokenText = new JwtSecurityTokenHandler().WriteToken(token);
await _localStorage.SetItemAsync(authToken, tokenText);
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenText);
var authState = Task.FromResult(new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(tokenText), "jwt"))));
NotifyAuthenticationStateChanged(authState);
_loggerFactory.CreateLogger(rqtDto.UserName + " 已登录。");
return true;
return false;
finally
if (scope is IAsyncDisposable asyncDisposable)
await asyncDisposable.DisposeAsync();
scope.Dispose();
public async Task MarkUserAsLoggedOutAsync()
await _localStorage.RemoveItemAsync(authToken);
_httpClient.DefaultRequestHeaders.Authorization = null;
var anonymousUser = new ClaimsPrincipal(new ClaimsIdentity());
var authState = Task.FromResult(new AuthenticationState(anonymousUser));
NotifyAuthenticationStateChanged(authState);
private static IEnumerable<Claim> ParseClaimsFromJwt(string jwt)
var claims = new List<Claim>();
var payload = jwt.Split('.')[1];
var jsonBytes = ParseBase64WithoutPadding(payload);
var keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);
if (keyValuePairs != null)
if (keyValuePairs.TryGetValue(ClaimTypes.Role, out object? roles) && roles is string rolesText)
if (rolesText.StartsWith('['))
var parsedRoles = JsonSerializer.Deserialize<string[]>(rolesText);
if (parsedRoles != null)
foreach (var parsedRole in parsedRoles)
claims.Add(new Claim(ClaimTypes.Role, parsedRole));
claims.Add(new Claim(ClaimTypes.Role, rolesText));
keyValuePairs.Remove(ClaimTypes.Role);
claims.AddRange(keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString())));
return claims;
private static byte[] ParseBase64WithoutPadding(string base64)
switch (base64.Length % 4)
case 2: base64 += "=="; break;
case 3: base64 += "="; break;
return Convert.FromBase64String(base64);
这个类是继承于Microsoft.AspNetCore.Components.Server中的RevalidatingServerAuthenticationStateProvider,是添加Identity标识自动添加并已注册服务,在Areas/Identity下面,在此基础上修改的,使其具有登录退出功能和JWT令牌生成授权功能。另外说一点,_Host.cshtml中的render-mode设为Server,否则ILocalStorageService获取token会发生错误。
注意:Key要求有一定的长度,建议复制新生成的Guid.NewGuid().ToString();上篇已经实现了登录与退出的主服务类,其他还需要一些具体的东西。1、引用Blazored.LocalStorage包;7、登录Razor组件(略)。注意:需要注册服务。...
之前用 Blazor 做了几个小应用,但是一直没有去做身份验证系统,之前也没有ASP.NET的基础,C#也是最近学的,而且重要的是,网上居然几乎没有相关的资料,于是咱就结合网上的一些信息和微软的官方文档(微软的文档是我见过最好的文档了)做了一个身份鉴权的小Demo。在 Provider 下写自定义的基础认证服务,这里使用的session的形式做鉴权认证。Blazor 是微软的新型的web开发方案,用来做全栈开发真是太爽了。然后在项目的目录下创建Auth目录存放鉴权的基础设施。一个简单的登录页面示例;
本示例项目为.Net Core 7.04 Blazor Server 版本。
功能:适合企业内部的账户分发(非开放公众注册)的App。
1、身份控制:分发账户为初始密码,使用初始密码登录后必须修改密码才能使用相应身份的功能;根据身份提供相应菜单。
2、定时检测:使用JWT功能,包括生成和验证jwt,可定义jwt生效时长、验证时间间隔,失效后自动转到登录页面。
3、账户管理:包括添加、编辑、离职管理示例。
本项目只在提供示例功能演示,力求简单明了和安全稳定高效,界面简单美化。
如有错误和不足,请博客中留言,谢谢!
书接上文,昨天我们快速的走了一遍wasm的开发流程(我的『MVP.Blazor』快速创建与部署),总体来说还是很不错的,无论是从技术上,还是从开发上,重点是用C#来开启前端时代,可以开发...
James: 《使用Blazor开发内部后台》系列是技术社区中一位朋友投稿的系列文章,介绍自己为公司的 WebForm 遗留系统使用 Blazor 重写前端 UI 的经历。本文为第三篇,如...
在2016年, 本人就开始了一个内部项目, 其特点就是用C#构建DOM树, 然后把DOM同步到浏览器中显示. 并且在一些小工程中使用.
3年下来, 效果很不错, 但因为是使用C#来构建控件树, 在没有特定语法的情况下, 代码风格不是那么好.
典型的风格大概是这样的:
这个模式挺好的, 有点嫌弃C#代码占比太高, HTML代码靠字符串来完成, 在界面的设计上, 比较吃力.
在2019年秋, Asp.Net 3.0出来了, Blazor Server Side 也正式公布, 可以在VS2019中使用.
当时我就去尝尝鲜, 发现这东西, 和我的框架很接近. 不同的是,
Blaozr Server
Blazor Server组件入门
文章目录Blazor Server组件入门一、组件是什么?二、使用步骤1.新建Blazor Server项目2.新建一个文件夹命名为Components3.右键Components文件夹创建一个Razor组件并命名(组件首字符大写:如MyComponents)4.编辑组件内容,这里我直接使用了自动生成的代码5.使用组件6.Ctrl + F5运行项目7.组件传参更多
一、组件是什么?
组件(Component)是对数据和方法的简单封装。通俗的讲,我们可以把一个网页比作一个玩
转载技术社区中一位朋友最新的文章,介绍自己为公司的 WebForm 遗留系统使用 Blazor 重写前端 UI 的经历。前言啊,又好久没写文章了,这一年一直在接触新的领域,扩展了一下技术面...
欢迎转载、使用、重新发布,但务必保留文章署名AlexChow,不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。本文来自博客园,作者:周创琳 AlexChow,转载请注明原文链接.Maui Blazor 中文社区 QQ群:645660665。BA & Blazor QQ群:795206915。FreeSql QQ群:4336577。作为IDS数据库连接。
现在处于大更新时期,Menu组件要改为泛型模式。本来我们的这一篇应该是把Layout改了,但是改Layout肯定要涉及到菜单,如果现在写了呢,就进入一个发布就过时的状态,就很尴尬,所以后面的就稍微拖一拖。加上昨天有人说我用违反单一性原则,要用策略,所以这里我们说下策略怎么做。
相信很多Blazor的用户在开发内部系统上基本上都选择速度更快,加载更快的模式。但是由于是SignalR实现,所以在访问的时候会建立WebSocket通道,用于js交互和界面渲染,但是由于WebSocket是长连接,这样就会导致用户在界面的时候会一直建立链接,导致服务器宽度占用,所以微软默认会在无操作的情况下自动断开链接,然后会加上该死的重新链接的一个ui,很难看,导致很多用户看到灰色的效果。当然,微软也提供了如何处理这个情况的方案,下面我们会使用微软提供的方案解决这个问题。
将Blazor Server应用使用如http://***.com/admin这样的Url访问.无目录的跟原来一样使用MVC.这种方式不是在二级目录部署,只是URL上区别..比较适合MVC做Api或网站前台页面,Blazor server做后台管理,或者是同一个站点部署多个Blazor Server应用的情况。
这里注意一下,跳转的时候一定不能用Blazor的NavigationManager,因为我们必须刷新一次浏览器,服务端才能拿到对应的Cookie,所以这里还是使用Ajax组件的Goto去跳转,这种跳转实际上是浏览器刷新,会重走一次MVC的逻辑。有个小区别是必须使用MVC去登录,Blazor本身是登录不了的。如果str是空的,或者code不是上面返回的20000,即登录成功的话,我们就使用飘窗报错。首先,因为我们说过,登录需要使用MVC的方式,所以这里需要使用浏览器发送Ajax请求的方式来登录。...