从零开始一起学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如何实现。

posted @ 2021-09-27 09:47  颗粒归仓  阅读(984)  评论(0编辑  收藏  举报