从零开始一起学Blazor WebAssembly 开发(5_1)权限控制初识
上篇讲了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如何实现。