九、模擬登入
登入的本質原理同網頁應用是一樣的,一般的流程是:
使用者開啟登頁--》輸入賬號密碼後提交表單--》服務端驗證成功後生成cookie資訊寫入瀏覽器--》之後使用者訪問頁面時瀏覽器會帶上此cookie資訊作為使用者標識--》服務端解析此cookie資訊就能識別這個使用者了。
在webapi出現之後,出現了JWT這樣的認證方式,原理大同小異,相同的是, 認證資訊都是儲存在請求頭中傳遞的,不同是JWT中的認證資訊需要編碼寫入請求頭之後再傳送請求,不像瀏覽器,傳送請求時會自動帶上cookie資訊,不需要編碼。
Blazor中的登入流程可以分成幾下幾個步驟:
- 啟用驗證
- 製作自定義AuthenticationStateProvider
- 修改App.razor
- 使用AuthorizeView和進行登入驗證和角色授權
自定義AuthenticationStateProvider
首先來理解一下什麼是AuthenticationStateProvider。AuthenticationStateProvider是一個抽象類,由Microsoft.AspNetCore.Components.Authorization類庫提供,主要用來提供當前使用者的認證狀態資訊。既然是抽象類,我們需要自定義一個它的子類,由於是模擬登入,進行登入流程的驗證,因此我們先來做一個測試的Provider來試試。
1. 在Visual Studio 2022的解決方案資源管理器中,在專案名稱“BlazorAppDemo”上單擊滑鼠右鍵,在彈出選單中選擇“新增—>新建資料夾”,並將之命名為“Auth”。如下圖。
2. 在Visual Studio 2022的解決方案資源管理器中,滑鼠左鍵選中“Auth”資料夾,右鍵單擊,在彈出選單中選擇“新增—>類”,並將類命名為“ImitateAuthStateProvider”。 AuthStateProvider類的最核心方法是 Task<AuthenticationState> GetAuthenticationStateAsync()。基於最簡單的登入機制,我們的擴充套件提供程式如下。具體程式碼如下:
using BlazorAppDemo.Models;
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
namespace BlazorAppDemo.Auth
{
public class ImitateAuthStateProvider : AuthenticationStateProvider
{
bool isLogin = false;
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
if (isLogin)
{
var claims = new List<Claim>()
{
new Claim(ClaimTypes.Name,"user"),
new Claim(ClaimTypes.Role, "admin")
};
var anonymous = new ClaimsIdentity(claims, "testAuthType");
return Task.FromResult(new AuthenticationState(new ClaimsPrincipal(anonymous)));
}
else
{
var anonymous = new ClaimsIdentity();
return Task.FromResult(new AuthenticationState(new ClaimsPrincipal(anonymous)));
}
}
public void Login(UserInfo request)
{
if (request.UserName == "user" && request.Password == "111111")
isLogin = true;
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
}
}
}
- var anonymous = new ClaimsIdentity();:我們現在進行模擬登入,先做一個匿名的使用者,所以ClaimsIdentity的構造方法中不傳引數。
- 返回AuthenticationState。
- 我們給ClaimsIdentity一個List<Claim>屬性,其中有使用者的名字和角色,表示我們已登入成功。
3. 在Visual Studio 2022的解決方案資源管理器中,使用滑鼠雙擊在文字編輯器中開啟Program.cs檔案,使用
builder.Services.AddScoped<ImitateAuthStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(implementationFactory =>
implementationFactory.GetRequiredService<ImitateAuthStateProvider>());
;這二行程式碼使用DI方式注入ImitateAuthStateProvider。具體程式碼如下:
using BlazorAppDemo.Data;
using BlazorAppDemo.Models;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.Extensions.Configuration;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Components.Authorization;
using BlazorAppDemo.Auth;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
System.Console.WriteLine(ConfigHelper.Configuration["ConnectionStrings:BookContext"]);
builder.Services.AddDbContextFactory<BookContext>(opt =>
opt.UseSqlServer(ConfigHelper.Configuration["ConnectionStrings:BookContext"]));
//builder.Services.AddScoped<AuthenticationStateProvider, ImitateAuthStateProvider>();
builder.Services.AddScoped<ImitateAuthStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(implementationFactory =>
implementationFactory.GetRequiredService<ImitateAuthStateProvider>());
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
Console.WriteLine("資料庫開始初始化。");
var context = services.GetRequiredService<BookContext>();
// requires using Microsoft.EntityFrameworkCore;
context.Database.Migrate();
// Requires using RazorPagesMovie.Models;
SeedData.Initialize(services);
Console.WriteLine("資料庫初始化結束。");
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "資料庫資料初始化錯誤.");
}
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
修改App.razor
現在我們已經完成了登入認證的Provider了,接下來要做的事情,就是讓我們的頁面上的元件,可以獲取登入資訊,來決定是登入使用者是否已經授權。這一步請引數之前的文章學習ASP.NET Core Blazor程式設計系列二十三——登入(3)之中的“七、修改路由與啟動頁面”。
修改Login.razor頁面進行登入
在Visual Studio 2022的文字編輯器中開啟Login.razor元件檔案,我們將滑鼠定位到@code中的SubmitHandler方法 ,寫上我們登入成功的程式碼。具體程式碼如下:
@page "/Login"
@using BlazorAppDemo.Auth;
@using BlazorAppDemo.Models
@using BlazorAppDemo.Utils
@layout LoginLayout
@inject NavigationManager NavigationManager
@inject ImitateAuthStateProvider AuthStateProvider;
<div class="card align-items-center">
<div class="card-body my-2">
<h3>Login</h3>
<hr />
<EditForm Model="loginModel" OnValidSubmit="SubmitHandler" OnInvalidSubmit="InvalidHandler">
<DataAnnotationsValidator />
<div class="form-group">
<label for="userName"> @HtmlHelper.GetDisplayName(loginModel ,m=> m.UserName)</label>
<InputText @bind-Value="loginModel.UserName" class="form-control" id="userName" />
<ValidationMessage For="()=>loginModel.UserName" />
</div>
<div class="form-group">
<label for="pwd"> @HtmlHelper.GetDisplayName(loginModel ,m=> m.Password)</label>
<InputPassword @bind-Value="loginModel.Password" class="form-control" id="pwd" />
<ValidationMessage For="()=>loginModel.Password" />
</div>
<span class="form-control-plaintext"></span>
<div class="form-group row">
<div class="col-sm-10">
<button class="btn btn-primary">登入</button>
</div>
</div>
</EditForm>
</div>
</div>
@code {
private UserInfo loginModel = new UserInfo();
bool isAuthLoading = false;
private void SubmitHandler()
{
Console.WriteLine($"使用者名稱:{loginModel.UserName} ,密碼:{loginModel.Password}");
isAuthLoading = true;
try {
AuthStateProvider.Login(new UserInfo() {
UserName = loginModel.UserName,
Password =loginModel.Password
});
NavigationManager.NavigateTo("/Index");
} catch (Exception ex) {
Console.WriteLine(ex.Message);
} finally {
isAuthLoading = false;
}
}
private void InvalidHandler()
{
Console.WriteLine($"使用者名稱: {loginModel.UserName} ,密碼:{loginModel.Password}");
}
}
登入並顯示當前使用者
1.在Visual Studio 2022的文字編輯器中開啟MainLayout.razor元件檔案,在AuthorizeView中顯示當前登入使用者,具體程式碼如下:
2.在Visual Studio 2022的選單欄上,找到“除錯-->開始除錯”或是按F5鍵,Visual Studio 2022會生成BlazorAppDemo應用程式,瀏覽器中會Login登入頁面。@using BlazorAppDemo.Pages @inherits LayoutComponentBase <PageTitle>BlazorAppDemo</PageTitle> <div class="page"> <div class="sidebar"> <NavMenu /> </div> <main> <AuthorizeView> <Authorized> <div class="top-row px-4"> <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a> <p> 你好, @context.User.Identity.Name! </p> </div> <article class="content px-4"> @Body </article> </Authorized> <NotAuthorized> <div style="margin: 120px 0; width:100%; text-align: center; color: red;"> <span style="font-size:20px">檢測到登入超時,請重新<a href="/login" style="text-decoration:underline">登入</a>!
</span> </div> <RedirectToLogin></RedirectToLogin> </NotAuthorized> </AuthorizeView> </main> </div>
3.我們在使用者名稱輸入框中輸入使用者名稱,在密碼輸入框中輸入密碼,點選“登入”按鈕,進行模擬登入。我們進入了系統。如下圖。
到此為止,我們已經實現了Blazor的登入認證流程,接下來我們要來實現透過jwt進行登入。