简易Blazor 登录授权
主要参考学习张善友大神的提示,使用Blazored.LocalStorage包本地cookie方式
本文将以Server Side的方式介绍,WASM方式仅需修改少数代码即可完成移植,不再赘述。下文介绍的是基于Token的内网用户名/密码认证,出于登录演示机制的考虑,并不保证代码在安全逻辑层面是可靠的。不要使用未加改造的本文代码,使用在生产网络中!
-
Nuget安装Blazored.LocalStorage包。此包使用JS与客户端环境交互,保存/读取本地数据。
- 注册认证和授权服务。
1 //ConfigureServices 2 services.AddAuthentication(); 3 services.AddAuthorization(); 4 services.AddControllers(); 5 services.AddHttpClient(); 6 services.AddBlazoredLocalStorage(); 7 //Configure 8 app.UseAuthentication(); 9 app.UseAuthorization(); 10 app.UseEndpoints(endpoints => { 11 //... 12 endpoints.MapControllers(); 13 //... 14 })
- Data新建CurrentUser.cs
using Blazored.LocalStorage; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Text.Json; using System.Security.Cryptography; using System.IO; using System.Text; namespace BlazorAuthoriation.Data { public class CurrentUser { static string LOCAL_STORAGE_KEY = "BlazorAuthorization"; public static async Task<CurrentUser> Load(ILocalStorageService localStorage) { var s = await localStorage.GetItemAsync<string>(LOCAL_STORAGE_KEY); return FromStorageString(s); } public static CurrentUser FromStorageString(string s) { return StringToObj(s); } public static async Task Remove(Blazored.LocalStorage.ILocalStorageService localStorage) { await localStorage.RemoveItemAsync(LOCAL_STORAGE_KEY); } public static async Task Login(Blazored.LocalStorage.ILocalStorageService localStorage,string name, string pwd) { CurrentUser user = new CurrentUser() { UserName = name, PassWord = pwd,StatuTime = System.DateTime.Now }; await localStorage.SetItemAsync<string>(LOCAL_STORAGE_KEY, user.ToString()); } public async Task UpdateTime(Blazored.LocalStorage.ILocalStorageService localStorage) { this.StatuTime = System.DateTime.Now; await localStorage.SetItemAsync<string>(LOCAL_STORAGE_KEY, this.ToString()); } public string UserName { set; get; } public string PassWord { set; get; } public DateTime StatuTime { set; get; } public override string ToString() { return DesEncrypt(JsonSerializer.Serialize(this),"this is special key"); } public static CurrentUser StringToObj(string buff) { if (buff == null) return null; CurrentUser user = JsonSerializer.Deserialize<CurrentUser>(DesDecrypt(buff, "this is special key")); //if (user?.StatuTime + new TimeSpan(0, 0, 10) < System.DateTime.Now) // return null; return JsonSerializer.Deserialize<CurrentUser>(DesDecrypt(buff, "this is special key")); } /// <summary> /// 加密字符串 /// 注意:密钥必须为8位 /// </summary> /// <param name="strText">字符串</param> /// <param name="encryptKey">密钥</param> /// <param name="encryptKey">返回加密后的字符串</param> private static string DesEncrypt(string inputString, string encryptKey) { byte[] byKey = null; byte[] IV = { 0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF }; try { byKey = System.Text.Encoding.UTF8.GetBytes(encryptKey.Substring(0, 8)); DESCryptoServiceProvider des = new DESCryptoServiceProvider(); byte[] inputByteArray = Encoding.UTF8.GetBytes(inputString); MemoryStream ms = new MemoryStream(); CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(byKey, IV), CryptoStreamMode.Write); cs.Write(inputByteArray, 0, inputByteArray.Length); cs.FlushFinalBlock(); return Convert.ToBase64String(ms.ToArray()); } catch (System.Exception error) { //return error.Message; return null; } } /// <summary> /// 解密字符串 /// </summary> /// <param name="this.inputString">加了密的字符串</param> /// <param name="decryptKey">密钥</param> /// <param name="decryptKey">返回解密后的字符串</param> private static string DesDecrypt(string inputString, string decryptKey) { byte[] byKey = null; byte[] IV = { 0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF }; try { byte[] inputByteArray = new Byte[inputString.Length]; byKey = System.Text.Encoding.UTF8.GetBytes(decryptKey.Substring(0, 8)); DESCryptoServiceProvider des = new DESCryptoServiceProvider(); inputByteArray = Convert.FromBase64String(inputString); MemoryStream ms = new MemoryStream(); CryptoStream cs = new CryptoStream(ms, des.CreateDecryptor(byKey, IV), CryptoStreamMode.Write); cs.Write(inputByteArray, 0, inputByteArray.Length); cs.FlushFinalBlock(); System.Text.Encoding encoding = new System.Text.UTF8Encoding(); return encoding.GetString(ms.ToArray()); } catch (System.Exception error) { //return error.Message; return null; } } } }
- 增加Login.razor
1 @page "/login" 2 @using BlazorAuthoriation.Data 3 @inject NavigationManager nav 4 @*@inject Blazored.SessionStorage.ISessionStorageService storage*@ 5 @inject Blazored.LocalStorage.ILocalStorageService storage 6 7 @if (null == currUser) 8 { 9 <h3> 10 账号:<input @bind-value="user" /> 11 密码:<input @bind-value="pwd"/> 12 <button @onclick="login">确定</button> 13 </h3> 14 } 15 else 16 { 17 <h3> 18 Welcome,<b> @currUser.UserName </b>! 19 <button @onclick="logout">退出</button> 20 </h3> 21 } 22 @code { 23 [CascadingParameter] 24 public CurrentUser currUser { get; set; } 25 26 [Parameter] 27 public EventCallback<string> LoginEvents { get; set; } 28 [Parameter] 29 public EventCallback LogoutEvents { get; set; } 30 31 private string user; 32 private string pwd; 33 private async Task RaiseLoginEvent() 34 { 35 if (LoginEvents.HasDelegate) 36 { 37 await LoginEvents.InvokeAsync(null); 38 StateHasChanged(); 39 } 40 } 41 private async Task RaiseLogoutEvent() 42 { 43 if (LogoutEvents.HasDelegate) 44 { 45 await LogoutEvents.InvokeAsync(null); 46 StateHasChanged(); 47 } 48 } 49 50 private async Task logout() 51 { 52 await CurrentUser.Remove(storage); 53 await RaiseLogoutEvent(); 54 //nav.NavigateTo("/"); 55 } 56 private async void login() 57 { 58 await CurrentUser.Login(storage,user,pwd); 59 await RaiseLoginEvent(); 60 //nav.NavigateTo("/"); 61 } 62 }
- 修改Mainlayout.razor
1 @inherits LayoutComponentBase 2 @inject NavigationManager nav 3 @inject Blazored.LocalStorage.ILocalStorageService storage 4 @using BlazorAuthoriation.Data 5 @using BlazorAuthoriation.Pages 6 7 <div class="sidebar"> 8 <NavMenu /> 9 </div> 10 <CascadingValue Value="currUser"> 11 <div class="main"> 12 <div class="top-row px-4"> 13 @*<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>*@ 14 <Login LoginEvents="(e)=>LoginEvent(e)" LogoutEvents="(e)=>LoginEvent(e)" /> 15 </div> 16 <div class="content px-4"> 17 @if (currUser == null) 18 { 19 <h3>请登录以后再操作内容!!</h3> 20 } 21 else 22 @Body 23 </div> 24 </div> 25 </CascadingValue> 26 @code{ 27 /// <summary> 28 /// 强制刷新标志 29 /// </summary> 30 private bool forceRender { get; set; } = false; 31 private void LogoutEvent(object username) 32 { 33 //msg.Info($"See you later {(string)username}"); 34 //通过 LoginControl 组件回调 接收强制刷新消息 35 //loginControl.ChildEvents.InvokeAsync("this is key"); 36 //currUser = null; 37 forceRender = true; 38 } 39 private void LoginEvent(object username) 40 { 41 //msg.Info($"See you later {(string)username}"); 42 //通过 LoginControl 组件回调 接收强制刷新消息 43 //loginControl.ChildEvents.InvokeAsync("this is key"); 44 //currUser = null; 45 forceRender = true; 46 } 47 48 protected override async Task OnAfterRenderAsync(bool firstRender) 49 { 50 base.OnAfterRender(firstRender); 51 //if (firstRender || forceRender) 52 { 53 54 currUser = await CurrentUser.Load(storage); 55 if (currUser?.StatuTime < System.DateTime.Now - new TimeSpan(0, 0, 10)) 56 currUser = null; 57 if (null == currUser) 58 //nav.NavigateTo("/Login"); 59 return; 60 else 61 { 62 if (firstRender || forceRender) 63 { 64 forceRender = false; 65 StateHasChanged(); 66 } 67 //await currUser.UpdateTime(storage); 68 } 69 70 } 71 } 72 /// <summary> 73 /// 有 CascadingValue 加持, 所有子组件可以通过 [CascadingParameter] 继承读取 74 /// </summary> 75 private CurrentUser currUser { get; set; } 76 }