Blazor進階: Blazor Server and the Logout Problem
Blazor Server App, 多 TabPage, 多 Tab Page,
參考文章
重點資訊
完整文章請參考原文 Blazor Server and the Logout Problem。重新描述重點如下:
灰色登入狀態情境:開啟網頁登入後,再多次開啟成多個不同 TabPage 時,此時所有 TabPage 都是登入狀態,當我們在其中一個 TabPage 執行登出時其它 TabPage 卻仍一直處在登入狀態,直到 reload page 時才會發現早已登出。這是嚴重的問題。該『灰色登入狀態』就前端來說真的仍處在登入狀態,而後端已是登出了。若此時數據操作不當可能在明明已經登出了卻還能執行登入操作。
原因:以 Blazor Server App 來說若以 Cookie Authentication 來登入驗證。該 Cookie 狀態只有在 reload page 時才會更新,平常都是處與 SPA/SSR 狀態。在多 TabPage 情境下其中一個登出時頂多強制 reload 本身網頁其它 TabPage 是無感的。
所以:原則上不要在前端元件隨意的使用 IHttpContextAccessor 來存取 HttpContext,要謹慎的使用因為 SignalR/WebSocket 與 HTTP 通訊協定原則上是不能直接互通的。不過在 reload page 時還是會重新刷新 cookie 這個 HTTP 協定的狀態。
解決方案
一、後端部份:在後端操作數據時,一定要再檢查使用者是否處在登入狀態。這只能自己處理。
二、前端部份:因為多 TagPage 下各個 TabPage 都是獨立的個體。現在(2023/10) Blazor Server App 的標準答案是每隔幾秒在後端檢查若使用已登入就強制登出當前的 TabPage。
關鍵程式碼
在前端部份,實作 CustomAuthenticationStateProvider 的繼承父親由 AuthenticationStateProvider 改成 RevalidatingServerAuthenticationStateProvider。
其中 override property: RevalidationInterval --- 指定每間隔幾秒要去確認使用者是否已登出。
其中 override method: ValidateAuthenticationStateAsync --- 確認使用者是否已登出。
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Http;
/// <summary>
/// 加值預設的 AuthenticationStateProvider。
/// </summary>
internal class CustomAuthenticationStateProvider : RevalidatingServerAuthenticationStateProvider
{
//# inject
readonly IHttpContextAccessor _http;
//※ 不要在前端元件使用 IHttpContextAccesser 因為 HttpContext 的狀態並非即時反應的。
readonly AccountService _accSvc;
// resource
readonly AuthenticationState anonymousUser;
public CustomAuthenticationStateProvider(ILoggerFactory loggerFactory, IHttpContextAccessor http, AccountService accSvc) : base(loggerFactory)
{
_http = http;
_accSvc = accSvc;
anonymousUser = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
}
/// <summary>
/// 每隔幾秒查看是否還在登入狀態。
/// 因為 HttpContext 的 Cookie 並非即時反應的。這會導致在多畫面應用情境時,若一畫面登出其他畫面卻仍在登入狀態的不同步冏狀。
/// </summary>
protected override TimeSpan RevalidationInterval => TimeSpan.FromSeconds(7);
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
//※ --- 本例為 Cookie Authentication 仍會用到 HttpContext。
var userIdentity = _http.HttpContext?.User.Identity as ClaimsIdentity;
//# 沒有 AuthCookie,回傳anonymousUser。
if (userIdentity == null || !userIdentity.IsAuthenticated)
return Task.FromResult(anonymousUser);
//# 取授權資料
var authUser = _accSvc.GetAuthDataFromPool(userIdentity.Name!);
...
//## 加值授權資料
...
//## 登入完成
var userAuthState = new AuthenticationState(new ClaimsPrincipal(userIdentity));
return Task.FromResult(userAuthState);
}
/// <summary>
/// 查看是否還在登入狀態。與 RevalidationInterval 搭配。
/// </summary>
protected override Task<bool> ValidateAuthenticationStateAsync(AuthenticationState authState, CancellationToken cancellationToken)
{
string userId = authState.User.Identity?.Name ?? string.Empty;
bool isLogged = _accSvc.IsUserLoggedIn(userId); //--- 查看使用者登入狀態。
return Task.FromResult(isLogged);
/// ※ 確認使用者仍是登入,若不是後續將會強制登出。
}
}
(EOF)
Last updated