大家好,我是沙漠盡頭的狼,今天介紹一個WPF開源項目-NodeNetwork,它可以幫助我們快速構建和定製網路拓撲圖。 一、前言 在現代軟體開發中,數據可視化和可交互性越來越受到關註。為了實現這一點,通常需要使用各種圖表、表格、網路拓撲圖等控制項。然而,對於某些特殊的場景,這些控制項可能無法滿足需求,此 ...
學習ASP.NET Core Blazor編程系列文章之目錄 學習ASP.NET Core Blazor編程系列一——綜述 學習ASP.NET Core Blazor編程系列二——第一個Blazor應用程式(上)
學習ASP.NET Core Blazor編程系列三——實體 學習ASP.NET Core Blazor編程系列五——列表頁面 學習ASP.NET Core Blazor編程系列七——新增圖書 學習ASP.NET Core Blazor編程系列八——數據校驗 學習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”按鈕。如下圖。