Blazor Server 的登录与退出(一)

您所在的位置:网站首页 一加自带浏览器怎么退出登录 Blazor Server 的登录与退出(一)

Blazor Server 的登录与退出(一)

2024-07-14 15:44| 来源: 网络整理| 查看: 265

    如果自学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 : 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 _principalFactory; private readonly IOptionsMonitor _jwtOpt; private const string authToken = "xxxxxxToken"; public RevalidatingIdentityAuthenticationStateProvider( ILoggerFactory loggerFactory, IServiceScopeFactory scopeFactory, IOptions optionsAccessor, ILocalStorageService localStorage, HttpClient httpClient, IUserClaimsPrincipalFactory principalFactory, IOptionsMonitor 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 ValidateAuthenticationStateAsync( AuthenticationState authenticationState, CancellationToken cancellationToken) { // Get the user manager from a new scope to ensure it fetches fresh data var scope = _scopeFactory.CreateScope(); try { var userManager = scope.ServiceProvider.GetRequiredService(); return await ValidateSecurityStampAsync(userManager, authenticationState.User); } finally { if (scope is IAsyncDisposable asyncDisposable) { await asyncDisposable.DisposeAsync(); } else { scope.Dispose(); } } } private async Task ValidateSecurityStampAsync(UserManager userManager, ClaimsPrincipal principal) { var user = await userManager.GetUserAsync(principal); if (user == null) { return false; } else if (!userManager.SupportsUserSecurityStamp) { return true; } else { var principalStamp = principal.FindFirstValue(_options.ClaimsIdentity.SecurityStampClaimType); var userStamp = await userManager.GetSecurityStampAsync(user); return principalStamp == userStamp; } } public override async Task GetAuthenticationStateAsync() { var savedToken = await _localStorage.GetItemAsync(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 MarkUserAsAuthenticated(LoginDto rqtDto) { var scope = _scopeFactory.CreateScope(); try { var userManager = scope.ServiceProvider.GetRequiredService(); 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(); } else { 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 ParseClaimsFromJwt(string jwt) { var claims = new List(); var payload = jwt.Split('.')[1]; var jsonBytes = ParseBase64WithoutPadding(payload); var keyValuePairs = JsonSerializer.Deserialize(jsonBytes); if (keyValuePairs != null) { if (keyValuePairs.TryGetValue(ClaimTypes.Role, out object? roles) && roles is string rolesText) { if (rolesText.StartsWith('[')) { var parsedRoles = JsonSerializer.Deserialize(rolesText); if (parsedRoles != null) { foreach (var parsedRole in parsedRoles) { claims.Add(new Claim(ClaimTypes.Role, parsedRole)); } } } else { 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会发生错误。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3