上篇講了Blazor WebAssembly 實現登錄以及獲取Token,本篇講一下如何實現在前端這塊的權限控制。
Blazor 實現權限控制主要實現以下兩個:
1、實現判斷是否有權限
2、打開沒有權限頁面跳轉到登錄頁面
3、沒有權限的菜單不顯示
記住一點,客戶端實現的權限控制不是真的控制不能使用某個功能,因為客戶端是可以被破解的。所以想控制某個功能不被使用還是要依靠服務后端來控制。
現在依次說下以上三點如何實現,第一點我利用了asp.net core 和 blazor提供的基於策略的權限框架。做了一些改造,主要是因為完全使用他們那種與我做的Token機制不能有效結合,還好比較容易擴展。改造的地方也不多。主要是做了一個自定義的CustomAuthStateProvider
public class CustomAuthStateProvider : AuthenticationStateProvider { private readonly TokenUtil _tokenUtil; public CustomAuthStateProvider(TokenUtil tokenUtil) { _tokenUtil = tokenUtil; } public override async Task<AuthenticationState> GetAuthenticationStateAsync() { try { string tokenJson = await _tokenUtil.GetAccessToken(); if (!String.IsNullOrEmpty(tokenJson)) { TokenInfo token = JsonConvert.DeserializeObject<TokenInfo>(tokenJson); if (token != null && token.TokenExpire>DateTime.Now) { var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(token.TokenValue), "jwt")); return new AuthenticationState(authenticatedUser); } else { return new AuthenticationState(new ClaimsPrincipal()); } } else { return new AuthenticationState(new ClaimsPrincipal()); } } catch (Exception ex) { return new AuthenticationState(new ClaimsPrincipal()); } } public void NotifyAuthenticationState() { NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); } private IEnumerable<Claim> ParseClaimsFromJwt(string jwt) { var claims = new List<Claim>(); var payload = jwt.Split('.')[1]; var jsonBytes = System.Text.Encoding.Default.GetString(ParseBase64WithoutPadding(payload)); var keyValuePairs = JsonConvert.DeserializeObject<Dictionary<string, object>>(jsonBytes); keyValuePairs.TryGetValue(JwtClaimTypes.Name, out object roles); if (roles != null) { if (roles.ToString().Trim().StartsWith("[")) { var parsedRoles = JsonConvert.DeserializeObject<string[]>(roles.ToString()); foreach (var parsedRole in parsedRoles) { claims.Add(new Claim(JwtClaimTypes.Name, parsedRole)); } } else { claims.Add(new Claim(JwtClaimTypes.Name, roles.ToString())); } keyValuePairs.Remove(JwtClaimTypes.Name); } claims.AddRange(keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString()))); return claims; } private byte[] ParseBase64WithoutPadding(string base64) { switch (base64.Length % 4) { case 2: base64 += "=="; break; case 3: base64 += "="; break; } return Convert.FromBase64String(base64); } }
這里邊主要是通過 重寫了
GetAuthenticationStateAsync方法來返回Token所具有的權限,代碼僅供參考。因為ParseClaimsFromJwt 這步解析的我僅僅把JwtClaimTypes.Name的解析出來了。並沒有完全解析出來。使用的時候根據自己個人情況來寫權限策略。
寫好這個后在program里寫一下注入相關的代碼
public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add<App>("app"); //builder.Services.AddTransient<CryptoKeyConfig>(); builder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri("https://localhost:44319") }); builder.Services.AddTransient<TokenUtil>(); builder.Services.AddTransient<TokenHttpClient>(); builder.Services.AddScoped<CustomAuthStateProvider>(); builder.Services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<CustomAuthStateProvider>()); //在wasm中沒有默認配置,所以需要設置一下 builder.Services.AddOptions(); builder.Services.AddAuthorizationCore(); builder.Services.AddAntDesign(); builder.Services.AddOidcAuthentication(options => { // Configure your authentication provider options here. // For more information, see https://aka.ms/blazor-standalone-auth builder.Configuration.Bind("Local", options.ProviderOptions); }); await builder.Build().RunAsync(); } share 文件夾下加一下無權限時跳轉到登錄頁面的一個layout 名字叫 RedirectToLogin.razor @inject NavigationManager Navigation @using Microsoft.AspNetCore.Components.WebAssembly.Authentication @code { protected override void OnInitialized() { Navigation.NavigateTo($"/login?returnUrl={Uri.EscapeDataString(Navigation.Uri)}"); } }
App.razor 改成如下
注意,這里實現了沒有權限時跳轉到登錄窗口的代碼
<Router AppAssembly="@typeof(Program).Assembly"> <Found Context="routeData"> <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"> <NotAuthorized> @if (!context.User.Identity.IsAuthenticated) { <RedirectToLogin /> } else { <p>You are not authorized to access this resource.</p> } </NotAuthorized> </AuthorizeRouteView> </Found> <NotFound> <LayoutView Layout="@typeof(MainLayout)"> <p>Sorry, there's nothing at this address.</p> </LayoutView> </NotFound> </Router> <AntContainer />
這樣基本的權限控制就實現了。下篇講一下如何用AuthorizeView 組件 和[Authorize] 屬性 實現頁面內組件和頁面本身的權限控制。也就是開頭說的2 和 3如何實現。