學習ASP.NET Core Blazor程式設計系列三十——JWT登入(4)

2023-03-19 15:00:20
學習ASP.NET Core Blazor程式設計系列一——綜述
學習ASP.NET Core Blazor程式設計系列八——資料校驗
學習ASP.NET Core Blazor程式設計系列十三——路由(完)
學習ASP.NET Core Blazor程式設計系列十五——查詢
學習ASP.NET Core Blazor程式設計系列二十——檔案上傳(完)
 學習ASP.NET Core Blazor程式設計系列二十二——登入(1)
學習ASP.NET Core Blazor程式設計系列二十七——JWT登入(1)
 

十三、實現登出

    至此關於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("/");
    }

}
2. 在Visual Studio 2022的解決方案管理器中,使用滑鼠左鍵,雙擊TokenManager.cs檔案,對程式碼進行修改。具體程式碼如下:

 

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; } } }

 

LogoutAsync方法:
  • 將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); } } }

 

6. 在Visual Studio 2022的解決方案管理器中,使用滑鼠左鍵,雙擊MainLayout.razor檔案,對程式碼進行修改。具體程式碼如下:

 

@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」按鈕。如下圖。