前序
本文章以客户端基础,实现类似后台系统,进入后台控制台页面需要经过登录身份验证才可访问情况
效果
简单来说就是实现前后端分离,前端通过 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; }
}