Blazor WebAssembly使用 AuthenticationStateProvider 自定义身份认证
本文章以客户端基础,实现类似后台系统,进入后台控制台页面需要经过登录身份验证才可访问情况
简单来时就是实现前后端分离,前端通过 token和用户信息进行身份认证,或者在 AuthenticationStateProvider 实现方法 GetAuthenticationStateAsync 中调用后台接口进行身份验证
安装依赖Microsoft.AspNetCore.Components.Authorization、Blazored.LocalStorage
自定义AuthenticationStateProvider
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | using Blazored.LocalStorage; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Authorization; using Model.Entity; using System.Net.Http.Headers; using System.Security.Claims; namespace font.Authorization { public class HAuthenticationStateProvider : AuthenticationStateProvider { public HttpClient? _httpClient { set ; get ; } private AuthenticationService service; public HAuthenticationStateProvider(AuthenticationService service, HttpClient _httpClient) { this ._httpClient = _httpClient; this .service = service; service.UserChanged += (newUser) => { NotifyAuthenticationStateChanged( Task.FromResult( new AuthenticationState(newUser))); }; service.LoginInState += MarkUserAsAuthenticated; service.LogoutState += MarkUserAsLoggedOut; } /// <summary> /// 身份认证提供 /// </summary> /// <returns></returns> /// <exception cref="NotImplementedException"></exception> public override Task<AuthenticationState> GetAuthenticationStateAsync() { var savedToken = service.GetAccessToken(); if ( string .IsNullOrWhiteSpace(savedToken)) { return Task.FromResult( new AuthenticationState( new ClaimsPrincipal( new ClaimsIdentity()))); } // 已认证过请求时带上token _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(service._bearer, savedToken); var user = service.GetUserDetail(); if (user == null ) { var anonymousUser = new ClaimsPrincipal( new ClaimsIdentity()); return Task.FromResult( new AuthenticationState(anonymousUser)); } return Task.FromResult( new AuthenticationState( new ClaimsPrincipal( new ClaimsIdentity( new []{ new Claim(ClaimTypes.Name, user.UserName), new Claim( "userId" , user.UserId.ToString()) }, service._authentication)))); } /// <summary> /// 辅助登录后刷新认证状态 /// </summary> /// <param name="token"></param> public void MarkUserAsAuthenticated( string token, UserDetail user) { var authenticatedUser = new AuthenticationState( new ClaimsPrincipal( new ClaimsIdentity( new [] { new Claim(ClaimTypes.Name, user.UserName), new Claim( "userId" , user.Id.ToString()) }, service._authentication))); var authState = Task.FromResult(authenticatedUser); NotifyAuthenticationStateChanged(authState); } /// <summary> /// 退出登录后刷新状态 /// </summary> public void MarkUserAsLoggedOut() { var anonymousUser = new ClaimsPrincipal( new ClaimsIdentity()); var authState = Task.FromResult( new AuthenticationState(anonymousUser)); NotifyAuthenticationStateChanged(authState); } } } |
自定义 AuthenticationService 管理登录和注销操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | using Blazored.LocalStorage; using Microsoft.AspNetCore.Components; using Model.Entity; using System.Security.Claims; namespace font.Authorization { public class AuthenticationService { public readonly string _token = "accessToken" ; public readonly string _bearer = "bearer" ; public readonly string _userDetail = "userDetail" ; public readonly string _authentication = "User Authentication" ; public event Action<ClaimsPrincipal>? UserChanged; public event Action< string , UserDetail>? LoginInState; public event Action? LogoutState; public readonly ISyncLocalStorageService localStorageService; private ClaimsPrincipal? currentUser; public ClaimsPrincipal CurrentUser { get { return currentUser ?? new (); } set { currentUser = value; if (UserChanged is not null ) { UserChanged(currentUser); } } } public AuthenticationService(ISyncLocalStorageService _syncLocalStorageService) { this .localStorageService = _syncLocalStorageService; } public void LoginIn( string accessToken, UserDetail userDetail) { SetAccessToken(accessToken); SetUserDetail(userDetail); LoginInState(accessToken, userDetail); } public void Logout() { RemoveAccessToken(); RemoveUserDetail(); LogoutState(); } public ClaimsPrincipal GetUserClaimsPrincipal(UserDetail user) { var identity = new ClaimsIdentity( new [] { new Claim(ClaimTypes.Name, user.UserName), new Claim( "Id" , user.Id.ToString()), new Claim( "userId" , user.UserId.ToString()), }, _authentication); return new ClaimsPrincipal(identity); } public void RemoveAccessToken() { localStorageService.RemoveItem(_token); } public string ? GetAccessToken() { return localStorageService.GetItemAsString(_token); } public void SetAccessToken( string accessToken) { localStorageService.SetItemAsString(_token, accessToken); } public UserDetail? GetUserDetail() { return localStorageService.GetItem<UserDetail>(_userDetail); } public void RemoveUserDetail() { localStorageService.RemoveItem(_userDetail); } public void SetUserDetail(UserDetail userDetail) { localStorageService.SetItem<UserDetail>(_userDetail, userDetail); } } } |
Program.cs
1 2 3 4 | builder.Services.AddOptions(); builder.Services.AddAuthorizationCore(); builder.Services.AddScoped<AuthenticationService>(); builder.Services.AddScoped<AuthenticationStateProvider, HAuthenticationStateProvider>(); |
在布局组件或者具体组件内使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <CascadingAuthenticationState> <AuthorizeView> <Authorized> <Layout> <Sider class = "main-container" @bind-Collapsed=@collapsed NoTrigger OnCollapse= "OnCollapse" > <div class = "logo1" /> <NavMenu collapsed= "@collapsed" /> </Sider> <Layout class = "site-layout" > <Header class = "site-layout-background" Style= "padding: 0;" > @ if (collapsed) { <Icon Type= "menu-unfold" Theme= "outline" class = "trigger" OnClick= "toggle" Style= " font-size: 30px;margin-left: 10px;" /> } else { <Icon Type= "menu-fold" Theme= "outline" class = "trigger" OnClick= "toggle" Style= " font-size: 30px;margin-left: 10px;" /> } </Header> <Content class = "site-layout-background content-container" Style= "margin: 24px 16px;padding: 24px;min-height: 280px;" > @Body </Content> </Layout> </Layout> </Authorized> <NotAuthorized> <Login /> </NotAuthorized> </AuthorizeView> </CascadingAuthenticationState> |
自定义登录组件和授权后的页面,注入 进行登录和注销测试
再刷新页面测试 和注销测试
已授权首页代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | @page "/" @inject IMessageService _message @inject AuthenticationService serivce <PageTitle>Home</PageTitle> <h1>Hello, world!</h1> Welcome to your new app. <Button Type= "primary" >Primary</Button> <Button Type= "primary" >Primary</Button> <br /> <Button Type= "primary" OnClick= "@OnClick" > Display normal message </Button> @code{ private void OnClick() { Console.WriteLine( "onclick" ); _message.Info( "退出登录成功!" ); serivce.Logout(); } } |
登录组件代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | @layout EmptyLayout @page "/login" @inject AuthenticationService au @inject IMessageService message <h3>Login</h3> <Button Type= "@ButtonType.Primary" OnClick= "@login" Size= "30" Shape= "@ButtonShape.Round" >登录</Button> @code { void login() { UserDetail userDetail = new UserDetail() { Id = 1, UserId = 12, UserName = "hygge" }; au.LoginIn( "jwerqwert" , userDetail); message.Success( "登录成功!" ); } } |
简单的登录认证就完成了
用户详情代码
1 2 3 4 5 6 7 | public class UserDetail { public int Id { get ; set ; } public int UserId { get ; set ; } public string UserName { get ; set ; } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?