十三、實現登出
至此關於Blazor的內容,先寫到這裏, 我們基本上完成了登入、增加、刪除、查詢、修改等功能,應對一般的應用,已經足夠。今天實現登錄功能。有登入,必然要有登出,本文我們來介紹一下如何登出。
1. 在Visual Studio 2022的解決方案資源管理器中,鼠標左鍵選中“Pages”文件夾,右鍵單擊,在彈出菜單中選擇“添加—>Razor組件…”,並將組件命名爲“Logout.razor”。登出組件的功能是用於退出登入,返回首面。其代碼如下:
@page "/Logout"
@using BlazorAppDemo.Auth;
@inject IAuthService authService
@inject NavigationManager navigation
@code {
protected override async Task OnInitializedAsync()
{
await authService.LogoutAsync();
navigation.NavigateTo("/");
}
}
using BlazorAppDemo.Models;
using System.Collections.Concurrent;
namespace BlazorAppDemo.Utils
{
public class TokenManager
{
private const string TOKEN = "authToken";
private static readonly ConcurrentDictionary<string, UserToken> tokenManager;
static TokenManager()
{
tokenManager=new ConcurrentDictionary<string, UserToken>();
}
public static ConcurrentDictionary<string, UserToken> Instance { get { return tokenManager; } }
public static string Token { get { return TOKEN; } }
public static bool RemoveToken(string token)
{
if (tokenManager.TryRemove(token,out UserToken delUserToken))
{
Console.WriteLine($"delete token {delUserToken.Token}");
return true;
}
else
{
Console.WriteLine($"unable delete token {delUserToken.Token}");
return false;
}
}
}
}
3.在Visual Studio 2022的解決方案資源管理器中,鼠標左鍵雙擊“Api”文件夾中的 “AuthController.cs”文件,將此文件中的Logout方法的代碼補全。代碼如下:
using BlazorAppDemo.Models;
using BlazorAppDemo.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json.Linq;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace BlazorAppDemo.Api
{
[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
private readonly IJWTHelper jwtHelper;
public AuthController(IJWTHelper _IJWTHelper)
{
this.jwtHelper = _IJWTHelper;
}
[HttpPost("Login")]
public async Task<ActionResult<UserToken>> Login(UserInfo userInfo)
{
//Demo用
if (userInfo.UserName == "admin" && userInfo.Password == "111111")
{
return BuildToken(userInfo);
}
else
{
UserToken userToken = new UserToken()
{
StatusCode = System.Net.HttpStatusCode.Unauthorized,
IsSuccess = false
};
return userToken;
}
}
/// <summary>
/// 建立Token
/// </summary>
/// <param name="userInfo"></param>
/// <returns></returns>
private UserToken BuildToken(UserInfo userInfo)
{
string jwtToken = jwtHelper.CreateJwtToken<UserInfo>(userInfo);
//建立UserToken,回傳客戶端
UserToken userToken = new UserToken()
{
StatusCode = System.Net.HttpStatusCode.OK,
Token = jwtToken,
ExpireTime = DateTime.Now.AddMinutes(30),
IsSuccess= true
};
return userToken;
}
[HttpGet("Logout")]
public async Task<ActionResult<UserToken>> Logout()
{
bool flag= TokenManager.RemoveToken(TokenManager.Token);
var response = new UserToken();
response.IsSuccess = !flag;
return response;
}
}
}
4.在Visual Studio 2022的解決方案資源管理器中,鼠標左鍵選中“Auth”文件夾中的 “AuthService.cs”文件,將此文件中的LogoutAsync方法中添加如下代碼:
using BlazorAppDemo.Models;
using BlazorAppDemo.Utils;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Identity;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Collections.Concurrent;
using System.Net.Http;
using System.Text;
namespace BlazorAppDemo.Auth
{
public class AuthService : IAuthService
{
private readonly HttpClient httpClient;
private readonly AuthenticationStateProvider authenticationStateProvider;
private readonly IConfiguration configuration;
private readonly Api.AuthController authController;
private readonly string currentUserUrl, loginUrl, logoutUrl;
public AuthService( HttpClient httpClient, AuthenticationStateProvider authenticationStateProvider,
IConfiguration configuration,Api.AuthController authController)
{
this.authController = authController;
this.httpClient = httpClient;
this.authenticationStateProvider = authenticationStateProvider;
this.configuration = configuration;
currentUserUrl = configuration["AuthUrl:Current"] ?? "Auth/Current/";
loginUrl = configuration["AuthUrl:Login"] ?? "api/Auth/Login";
logoutUrl = configuration["AuthUrl:Logout"] ?? "/api/Auth/Logout/";
}
public async Task<UserToken> LoginAsync(UserInfo userInfo)
{
response.Content.ReadFromJsonAsync<UserToken>();
var result = authController.Login(userInfo);
var loginResponse = result.Result.Value;
if (loginResponse != null && loginResponse.IsSuccess)
{
TokenManager.Instance.TryAdd(TokenManager.Token, loginResponse);
((ImitateAuthStateProvider)authenticationStateProvider).NotifyUserAuthentication(loginResponse.Token);
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer",
loginResponse.Token);
return loginResponse;
}
return new UserToken() { IsSuccess = false };
}
public async Task<UserToken> LogoutAsync()
{
var result = authController.Logout();
var logoutResponse = result.Result.Value;
((ImitateAuthStateProvider)authenticationStateProvider).NotifyUserLogOut();
httpClient.DefaultRequestHeaders.Authorization = null;
return logoutResponse;
}
}
}
- 將token從TokenManger實例中移除
- 通知前面頁面更新登錄狀態
- 將request中的header參數bearer token移除。
5. 在Visual Studio 2022的解決方案管理器中,使用鼠標左鍵,雙擊ImitateAuthStateProvider.cs文件,對代碼進行修改。具體代碼如下:
using BlazorAppDemo.Models;
using BlazorAppDemo.Utils;
using Microsoft.AspNetCore.Components.Authorization;
using System.Net.Http;
using System.Security.Claims;
namespace BlazorAppDemo.Auth
{
public class ImitateAuthStateProvider : AuthenticationStateProvider
{
private readonly IJWTHelper jwt;
private AuthenticationState anonymous;
private readonly HttpClient httpClient;
public ImitateAuthStateProvider(IJWTHelper _jwt, HttpClient httpClient)
{
anonymous = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
jwt = _jwt;
this.httpClient = httpClient;
}
bool isLogin = false;
string token = string.Empty;
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
//確認是否已經登錄
UserToken userToken;
TokenManager.Instance.TryGetValue(TokenManager.Token,out userToken);
string tokenInLocalStorage=string.Empty;
if (userToken != null)
{
tokenInLocalStorage = userToken.Token;
}
if (string.IsNullOrEmpty(tokenInLocalStorage))
{
//沒有登錄,則返回匿名登錄者
return Task.FromResult(anonymous);
}
//將token取出轉換爲claim
var claims = jwt.ParseToken(tokenInLocalStorage);
//在每次request的header中都將加入bearer token
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer",
tokenInLocalStorage);
//回傳帶有user claim的AuthenticationState
return Task.FromResult(new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(claims, "jwt"))));
}
public void Login(UserInfo request)
{
//1.驗證用戶賬號密碼是否正確
if (request == null)
{
isLogin=false;
}
if (request.UserName == "user" && request.Password == "111111")
{
isLogin = true;
token= jwt.CreateJwtToken<UserInfo>(request);
Console.WriteLine($"JWT Token={token}");
}
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
}
public void NotifyUserAuthentication(string token)
{
var claims = jwt.ParseToken(token);
var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(claims, "jwt"));
var authState = Task.FromResult(new AuthenticationState(authenticatedUser));
NotifyAuthenticationStateChanged(authState);
}
public void NotifyUserLogOut()
{
var authState = Task.FromResult(anonymous);
NotifyAuthenticationStateChanged(authState);
}
}
}
@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> <div class ="col-3 oi-align-right"> 你好, @context.User.Identity.Name!<a href="/Logout">Logout</a> </div> </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>
7. 在Visual Studio 2022的菜單欄上,找到“調試-->開始調試”或是按F5鍵,Visual Studio 2022會生成BlazorAppDemo應用程序。瀏覽器會打開登錄頁面。我們在登錄頁面的用戶名輸入框中輸入用戶名,在密碼輸入框中輸入密碼,點擊“登錄”按鈕,進行登錄。我們進入了系統,在頁面的右上角處,會出現登錄用戶的用戶名,與一個“Logout”按鈕。如下圖。