从零开始Blazor Server(6)--基于策略的权限验证
写这个的原因
现在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