Blazor WebAssembly使用 AuthenticationStateProvider 自定义身份认证
本文章以客户端基础,实现类似后台系统,进入后台控制台页面需要经过登录身份验证才可访问情况
简单来时就是实现前后端分离,前端通过 token和用户信息进行身份认证,或者在 AuthenticationStateProvider 实现方法 GetAuthenticationStateAsync 中调用后台接口进行身份验证
安装依赖Microsoft.AspNetCore.Components.Authorization、Blazored.LocalStorage
自定义AuthenticationStateProvider
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 管理登录和注销操作
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
builder.Services.AddOptions(); builder.Services.AddAuthorizationCore(); builder.Services.AddScoped<AuthenticationService>(); builder.Services.AddScoped<AuthenticationStateProvider, HAuthenticationStateProvider>();
在布局组件或者具体组件内使用
<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>
自定义登录组件和授权后的页面,注入 进行登录和注销测试
再刷新页面测试 和注销测试
已授权首页代码
@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(); } }
登录组件代码
@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("登录成功!"); } }
简单的登录认证就完成了
用户详情代码
public class UserDetail { public int Id { get; set; } public int UserId { get; set; } public string UserName { get; set; } }