Blazor极简登录模型(更新NET6附代码)

Blazor极简登录模型(更新NET6附代码)

2023年6月26日发(作者:)

Blazor极简登录模型(更新NET6附代码)Blazor 极简登录模型(适⽤Server Side和WASM Client)不少介绍Blazor⽹站包括微软⾃⼰的⽂档⽹站,对Blazor采⽤的认证/授权机制有详细的介绍,但是往往给出的是Identity Server的例⼦。搜索引擎可以找到的如:但是如笔者的场景,没有SQL Server,没有OCID机制的企业内部⽹络,想实现⾃⼰登录机制,在⽹络上并没有多少资料。下⽂介绍的是基于Token的内⽹⽤户名/密码认证,出于登录演⽰机制的考虑,并不保证代码在安全逻辑层⾯是可靠的。不要使⽤未加改造的本⽂代码,使⽤在⽣产⽹络中!本⽂将以Server Side的⽅式介绍,WASM⽅式仅需修改少数代码即可完成移植,不再赘述。0. 准备1. Nuget安装torage包。此包使⽤JS与客户端环境交互,保存/读取本地数据。2. 注册认证和授权服务。1. 机制不同于(以及core,MVC等)模型,Blazor使⽤的服务器/浏览器通讯是SignalR技术,基于WebSockets。SignalR技术是⼀种长连接通讯,这就和普通的BS登录模型产⽣了理解上的冲突——长连接通讯断开以后,会试图重连,⽹络层会⾃动透过IP地址端⼝等要素验证,似乎不需要解决已经登录⽽有别的⽤户通过此连接接管的问题。更要命的是,SignalR技术并没有普通的HTTP Cookie概念。所以我们现在所说的基于Token的登录,仅仅是使⽤MVC模型的HTTP登录;然⽽如何让SignalR知道此⽤户是被授权访问的?答案是Blazor提供的AuthenticationStateProvider。如果razor视图使⽤CascadingAuthenticationState,Blazor在渲染前会检查AuthorizeRouteView中的/AuthorizeView/Authorized, NotAuthorized, Authorizing标签,并根据客户端得到的授权状态渲染。2. 扩展认证状态提供程序AuthenticationStateProvider认证状态提供程序的最核⼼是

Task GetAuthenticationStateAsync()⽅法。基于最简单的登录机制,我们的扩展提供程序如下。public class CustomStateProvider : AuthenticationStateProvider { private readonly IAuthService api; public CustomStateProvider(IAuthService _api) => api = _api; //DI

public override async Task

GetAuthenticationStateAsync() { var identity = new ClaimsIdentity(); var currentUser = await GetCurrentUser(); if (enticated) { List claims = new List(); (new Claim(, [])); for (int i = 0; i < ; i++) { (new Claim(, [i])); } identity = new ClaimsIdentity(claims, "Basic Password"); } return new AuthenticationState(new ClaimsPrincipal(identity)); }

private async Task GetCurrentUser() => await tUserInfo();

public async Task Logout(LogoutRequest request) { var response = await (request); NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); return response; }

public async Task Login(LoginRequest request) { var response = await (request); NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); return response; }}3. 扩展认证服务IAuthService我们使⽤AuthService来与服务端进⾏交互,实现认证机制。public interface IAuthService { Task Login(LoginRequest request); Task Logout(LogoutRequest request); Task CurrentUserInfo();}public class AuthService : IAuthService { private readonly HttpClient httpClient; private readonly NavigationManager navigationManager; private readonly StorageService storage;

public AuthService(HttpClient _httpClient, NavigationManager _navigationManager, StorageService _storage){ httpClient = _httpClient; navigationManager = _navigationManager; storage = _storage; dress = new Uri(i); }

public async Task CurrentUserInfo() { CurrentUser user = new CurrentUser() { IsAuthenticated = false }; string token = ; try { // 浏览器还未加载完js时,不能使⽤LocalStorage token = await mAsStringAsync("Token"); } catch (Exception ex) { ine(e); return user; }

if(!OrEmpty(token)) { try { user = await mJsonAsync($"Auth/Current/{token}"); if (red) { await ItemAsync("Token"); } } catch( Exception ex) { ine(e); } } return user; }

public async Task Login(LoginRequest request) { var from = new FormUrlEncodedContent(new Dictionary() { ["UserId"] = , ["Password"] = rdHashed }); var result = await ync("Auth/Login", form); if (essStatusCode) { var response = await omJsonAsync(); if (ess) { await mAsync("Token", ); return response; } } return new LoginResponse() { IsSuccess = false }; }

//Logout代码从略}从安全上来说,以上机制情况下,客户端拿到Token以后,可以在别的机器透过仅上传Token来使服务端验证,所以应该在服务端保存客户端的⼀些信息来验证并实现复杂的安全机制。不要使⽤上述代码在⽣产环境中!上述代码完成编写以后,需要透过注册服务的机制来让Blazor使⽤。ped();ped(implementationFactory =>

uiredService());ped();4. 使⽤客户端在中编写登录页⾯。UI组件使⽤了 正在刷新授权信息... 页⾯需要注⼊以下服务:@inject CustomStateProvider AuthStateProvider;@inject StorageService Storage;编写登录按钮的处理事件:async Task OnLogin() { isAuthLoading = true; try { var response = await (new LoginRequest() { UserId = username, PasswordHashed = (password) }); password = ; if (ess) { await s("成功登录", .15D); } else { await g(e); } } catch (Exception ex) { await (e); } finally { isAuthLoading = false; }}5. 填坑之旅1. 可以在Razor页中使⽤LocalStorage存储Token吗?——不可以,会造成成功登录以后页⾯需要再刷新⼀次才能渲染登录成功的UI,似乎是认证状态提供程序没有及时得到Claim造成的。2. 在AuthService中使⽤中间变量似乎也可以实现此机制。——AuthService运⾏在服务端,Token保存在服务端没有意义。6. 更新现在NET6出来了, 我使⽤Minimal API稍微重构了⼀下代码. 代码传送门在此:代码的变动不⼤,就是将Controller更换了和t⽅法。public static WebApplication MapMinimalAuth(this WebApplication webApplication) { ("/Auth/Current/{token}", async (string token, [FromServices]UserService service, HttpContext context) => { //实现CurrentUser的代码 } t("/auth/login", (LoginRequest request, UserService service) => (request)); t("/Auth/Logout", (LogoutRequest request, [FromServices] UserService service) =>(request)); return webApplication;}另外由于NET6的⼀些特性,有⼏点需要说明的:6.1

Minimal API⽬前还不⽀持[FromForm]标注参见:如果在Map⽅法中参数使⽤[FromForm]标注,则在框架层直接返回HTTP 400 Bad Request,所以代码暂且使⽤了Json格式传递登录/登出的数据。如果在⽣产情况下最好能对密码字段进⾏加密传输(虽然前端加密聊胜于⽆)。如果NET6能⽀持[FromForm]的话,将会更新代码,毕竟需要⽀持多⽅客户端上传⽅式。6.2

Minimal API的参数注⼊很好很强⼤6.3 夹带私货/UserRole这个页⾯放了⼀个简单的,使⽤AntDesign Blazor控件写的权限配置页⾯。也算⼀个简单的⽰例代码吧,毕竟没有⼈写需求。6.4 ⽰例解说在MockData中, 我们建⽴两个⽤户Adam和Betty. 其中Adam有两个权限配置0001:录⼊和0002:审核,

Betty有两个权限配置0000:超级⽤户和0001:录⼊.以下代码仅供演⽰使⽤, 不要将类似代码上线⽣产环境!userStore = new ConcurrentDictionary() { ["Adam"] = new UserStore() { Password = "123456", UserId = "Adam", Roles = new List { "0001", "0002" }, IsOnline = false }, ["Betty"] = new UserStore() { Password = "000000", UserId = "Betty", Roles = new List { "0000", "0001" }, IsOnline = false }};6.4.1 Adam登录6.4.2 Betty登录以及简单权限管理

发布者:admin,转转请注明出处:http://www.yc00.com/news/1687775050a43025.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信