原文:https://chrissainty.com/securing-your-blazor-apps-configuring-policy-based-authorization-with-blazor/
在上一篇文章中,我展示了如何向Blazor WebAssembly(Blazor客户端)应用程序添加基于角色的授权。在这篇文章中,我将向您展示如何使用Blazor配置基于策略的授权。
基于策略的授权
ASP.NET Core基于策略的授权允许一种更加灵活的方式来创建授权规则。策略授权由三个概念组成:
- Policy - 策略有一个或者多个要求。
- Requirement - 策略用于评估当前用户主体的数据参数集合。
- Handler - 处理程序用于确定当前用户主体是否有权访问所请求的资源。
策略通常在应用程序启动时在Startup
类的ConfigureService
方法中注册。
services.AddAuthorization(config => { config.AddPolicy("IsDeveloper", policy => policy.RequireClaim("IsDeveloper", "true")); });
在上面的示例中,策略IsDeveloper
要求用户需要有IsDeveloper
声明,并且值为true
。
与角色授权一样,您一样可以使用Authorize
属性应用于策略授权。
[Route("api/[controller]")] [ApiController] public class SystemController { [Authorize(Policy = “IsDeveloper”)] public IActionResult LoadDebugInfo() { // ... } }
Blazors指令和组件也一样可以使用策略。
@page "/debug" @attribute [Authorize(Policy = "IsDeveloper")]
<AuthorizeView Policy="IsDeveloper"> <p>You can only see this if you satisfy the IsDeveloper policy.</p> </AuthorizeView>
更容易管理
基于策略的授权的最大优点就是改进应用程序中的授权管理。使用基于角色的授权,如果我们有两个角色被允许访问受保护资源 - 比如admin
和moderator
。我们需要在每个被允许的访问的资源添加一个Authorize
属性。
[Authorize(Roles = "admin,moderator")]
这在一开始看起来不是很糟糕,但是如果出现一个新的需求,第三个角色superuser
,需要相同的访问权限,该怎么办呢?现在我们需要在每个被访问资源更新所有角色。通过基于策略的验证,我们可以避免这种情况。
我们可以在一个定义一个策略,然后将其应用于需要它的所有资源。当需要添加额外角色时,我们只需更新这个策略,而不需要更新各个资源。
public void ConfigureServices(IServiceCollection services) { services.AddAuthorization(config => { config.AddPolicy("IsAdmin", policy => policy.RequireRole("admin", "moderator", "superuser")); }); }
[Authorize(Policy = "IsAdmin")]
创建自定义需求
策略授权非常灵活,您可以基于角色、声明创建需求,甚至可以创建自定义需求。让我们来看看如何创建自定义需求。
通常,当您有复杂的逻辑时,会使用自定义需求。如上所述,我们需要顶一个需求和一个处理程序来使用策略授权。
我们来创建一个检查用户的电子邮件地址是否使用公司域的需求。我们需要创建授权需求类,这个类需要实现IAuthorizationRequirement
接口,这只是一个空的标记接口。
public class CompanyDomainRequirement : IAuthorizationRequirement { public string CompanyDomain { get; } public CompanyDomainRequirement(string companyDomain) { CompanyDomain = companyDomain; } }
接下来,我们需要为我们的需求创建一个继承自AuthorizationHandler
的处理程序,T
就是要处理的需求。
public class CompanyDomainHandler : AuthorizationHandler<CompanyDomainRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CompanyDomainRequirement requirement) { if (!context.User.HasClaim(c => c.Type == ClaimTypes.Email)) { return Task.CompletedTask; } var emailAddress = context.User.FindFirst(c => c.Type == ClaimTypes.Email).Value; if (emailAddress.EndsWith(requirement.CompanyDomain)) { return context.Succeed(requirement); } return Task.CompletedTask; } }
在上面代码中,我们检查是否存在电子邮件声明。如果存在,那么我们检查它是否按要求中指定的域结束,如果是,则返回成功,否则就是失败。
我们只需要将我们的要求与一个策略关联起来,并将CompanyDomainHandler
注册到依赖注入容器中。
public void ConfigureServices(IServiceCollection services) { services.AddAuthorization(config => { config.AddPolicy("IsCompanyUser", policy => policy.Requirements.Add(new CompanyDomainRequirement("newco.com"))); }); services.AddSingleton<IAuthorizationHandler, CompanyDomainHandler>(); }
要了解更多关于自定义要求的详细信息,建议查看官方文档。
Blazor中使用策略
现在我们已经了解了什么是策略,让我们看看如何在应用程序中使用它们。
我们将把上一篇文章的中Blazor应用程序切换到基于策略的授权。作为这项工作的一部分,我们将看到基于策略的授权的另一个优点,即能够在共享项目中定义策略并在服务端和客户端引用它们。
创建共享策略
在share项目中创建策略之前,我们需要先从NuGet安装Microsoft.AspNetCore.Authorization
这个包。
安装之后,使用以下代码创建一个名为Policies
的类。
public static class Policies { public const string IsAdmin = "IsAdmin"; public const string IsUser = "IsUser"; public static AuthorizationPolicy IsAdminPolicy() { return new AuthorizationPolicyBuilder().RequireAuthenticatedUser() .RequireRole("Admin") .Build(); } public static AuthorizationPolicy IsUserPolicy() { return new AuthorizationPolicyBuilder().RequireAuthenticatedUser() .RequireRole("User") .Build(); } }
我们首先定义了两个常量IsAdmin
和IsUser
。我们将在注册策略时候使用它们。接下来是策略本身,IsAdminPolicy
和IsUserPolicy
。这里我使用AuthorizationPolicyBuilder
来定义每个策略,这两个策略都需要用户进行身份验证,然后根据策略的不同,用户可以是Admin
角色和User
角色。
配置服务端
现在我们已经定义了策略,我们需要让服务端使用它们。首先,在Startup
类中的ConfigureServices
方法注册策略,在AddAuthentication
之后添加以下代码。
services.AddAuthorization(config => {
config.AddPolicy(Policies.IsAdmin, Policies.IsAdminPolicy());
config.AddPolicy(Policies.IsUser, Policies.IsUserPolicy());
});
代码非常容易理解,我们使用Policies
类中定义的常量来声明它们的名称,并注册每个策略,避免使用魔法字符串。
在WeatherForecastController
则可以使用IsAdmin策略代旧的角色。
[ApiController] [Route("[controller]")] [Authorize(Policy = Policies.IsAdmin)] public class WeatherForecastController : ControllerBase
同样,我们可以使用名称常量来避免魔法字符串。
配置客户端
现在服务端可以使用我们定义的新策略,接下来就是在Blazor客户端使用他们。
和服务端一样,我们也在在Startup
类中的ConfigureServices
方法注册策略。之前我们已经调用了AddAuthorizationCore
,所以只需要更新它。
services.AddAuthorizationCore(config => {
config.AddPolicy(Policies.IsAdmin, Policies.IsAdminPolicy());
config.AddPolicy(Policies.IsUser, Policies.IsUserPolicy());
});
在Index.razor
,使用策略更新AuthorizeView
组件 - 一样要避免使用魔法字符串。
<AuthorizeView Policy="@Policies.IsUser"> <p>You can only see this if you satisfy the IsUser policy.</p> </AuthorizeView> <AuthorizeView Policy="@Policies.IsAdmin"> <p>You can only see this if you satisfy the IsAdmin policy.</p> </AuthorizeView>
最后,更新FetchData.razor
的Authorize
属性。
@attribute [Authorize(Policy = Policies.IsAdmin)]
就是这样!我们的应用程序现在转移到基于策略的授权。我们现在有一个更灵活的授权系统,可以使用角色、声明、自定义策略或者上述任何组合。
关于服务端Blazor
我并没有专门讨论服务端Blazor,原因很简单,我们上面所做的应该可以毫无问题的转移到服务端Blazor。
总结
在这篇文章中,我们讨论了ASP.NET Core和Blazor基于策略的授权。我们也了解了使用基于策略的授权相对于基于角色的授权的一些优点并且我们将应用程序从基于角色的验证迁移到了基于策略的验证。
最后还是代码(GITHUB)