從零開始Blazor Server(6)--基於策略的許可權驗證

2022-08-03 12:00:25

寫這個的原因

現在BootstrapBlazor處於大更新時期,Menu元件要改為泛型模式。


本來我們的這一篇應該是把Layout改了,但是改Layout肯定要涉及到選單,如果現在寫了呢,就進入一個釋出就過時的狀態,就很尷尬,所以後面的就稍微拖一拖。


加上昨天有人說我用OnNavigateAsync違反單一性原則,要用策略,所以這裡我們說下策略怎麼做。

新增策略相關的程式碼

首先我們要有一個實現IAuthorizationRequirement介面的類,這個類沒有什麼特別的要求,我們就寫一個空類來處理。

public class AdminRequirement : IAuthorizationRequirement
{
    
}

然後要寫一個Handler,來繼承這個AuthorizationHandler<AdminRequirement>,其中泛型是我們上面的實現介面的類。

public class AdminRequirementHandler : AuthorizationHandler<AdminRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AdminRequirement requirement)
    {
        
        context.Succeed(requirement);
        return Task.CompletedTask;
    }
}

實現HandleRequirementAsync方法,這個方法就是我們的關鍵方法,授權的實現就在這裡面。


其中預設的授權狀態是Fail,如果我們希望允許通過,就執行context.Succeed(requirement);來告訴策略我們認證成功了。

新增授權認證

Program.cs裡我們需要把這兩個都註冊進去,首先註冊我們的Handler

builder.Services.AddSingleton<IAuthorizationHandler, AdminRequirementHandler>();

然後註冊我們的授權策略

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("Admin", policy => policy.Requirements.Add(new AdminRequirement()));
});

這裡的Admin就是我們的策略名字。

使用策略

在我們需要認證的位置增加特性@attribute [Authorize(Policy = "Admin")],然後在我們的授權策略裡打斷點,應該就會發現斷點進入了。

將RouteData傳入

因為Blazor裡面我們拿不到HttpContext,所以沒法用Request.Path的方式來拿到url,所以只能使用將RouteData作為Resource傳入,然後使用attribute的方式拿到。


這裡我們在App.razor裡傳入routeData

<AuthorizeRouteView Resource="@routeData" RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
                <NotAuthorized>
                    <RedirectToLogin></RedirectToLogin>
                </NotAuthorized>
            </AuthorizeRouteView>

然後修改HandleRequirementAsync

if (context.User.Identity?.IsAuthenticated != true)
        {
            return Task.CompletedTask;
        }

        if (!int.TryParse(context.User.FindFirst(ClaimTypes.Role)?.Value, out var roleId))
        {
            return Task.CompletedTask;
        }
        if (context.Resource is RouteData routeData)
        {
            var routeAttr = routeData.PageType.CustomAttributes.FirstOrDefault(x =>
                x.AttributeType == typeof(RouteAttribute));
            if (routeAttr == null)
            {
                context.Succeed(requirement);
            }
            else
            {
                var url = routeAttr.ConstructorArguments[0].Value as string;
                var permission = PermissionEntity
                    .Where(x => x.Roles!.Any(y => y.Id == roleId) && x.Url == url).First();
                if (permission != null)
                {
                    context.Succeed(requirement);
                }
            }
        }
        
        return Task.CompletedTask;
    }

這裡跟上一篇的處理思路整體一樣,首先我們判斷如果使用者都沒登入,那就直接失敗,如果登入了我們就去拿RoleId,拿不到自然就失敗。


不同點在下面,我們沒法直接拿到Path,所以我們只能去找RouteAttribute,其實就是我們的@page路由。這裡我們也可以自己定義一個Attribute取自己的。


如果我們沒找到這個,證明這應該不是個blazor頁面,我們就暫時讓它成功。


如果找到了,那麼我們就找routeAttr.ConstructorArguments[0].Value as string,這裡面就是對應的路由地址了。

下面就跟之前一樣,用路由地址來判斷是否是又許可權就行了。


原始碼在github: https://github.com/j4587698/BlazorLearn,分支lesson6