Policy-Based Authorization in ASP.NET Core

Policy-Based Authorization in ASP.NET Core

Authorization 在 应用软件中 可以让确保用户是否可以获取资源,执行操作,或者对资源进行操作。 Asp.Net Core 中有两种方式: 基于 Role 或者 基于 Polocy。 前者在 ASP.NET 本身就有的, 后者是Asp.Net Core 新增的。

Authorize 属性

Role 是文本字符串,值被 security layer 当作元数据(在IPrincipal对象中检查是否存在) 以及在程序中给经过身份验证的用户 映射一组权限。 Asp.Net 中登录的用户是通过一个IPrincipal对象来标识的, 在 Asp.Net Core 中真正的类为 Claims Identity。 这个类公开一组 identity 集合, 每一个 identityIIdentiy对象标识,特别是 Claims Identity对象, 这意味着任何登录的用户都带有一个 Claim 列表, 这实际上是用户状态的声明。 UsernameRole 是连个在 Asp.Net Core 常用的两个 Claim. 然而 Role 的存在需要 后台存储Identity数据。就是说,通过 social 身份验证登录的用户看不到role的信息。

Authorization(授权) 比 Authentication (身份验证) 更进一步。 Authentication 是关于发现用户的Identity, 而Authorization 定义了用户调用应用程序接口的需求。 用户的 Role 信息通常存储在数据库中,并在验证用户凭据时检索取出,在此情况下,Role 信息将以某种方式附加到用户帐户。IIdentity接口提供了一个必须实现的Is In Role方法。 Claims Identity类通过检查Role Claim是否在Authentication过程产生的Claim集合中可用来实现这个方法。在任何情况下,当用户尝试调用受保护的Controller方法时,应该检查她的Role。如果没有,则拒绝用户调用任何安全受保护的方法。

Authorize 属性可以用来声明Controller 或者它的某些方法是受保护的。

[Authorize]
public class CustomerController : Controller
{
...
}

如果未指定参数,则该属性仅检查用户是否经过身份验证(Authentication)。 不止于此, 这个属性也支持其他的属性参数,比如 RolesRoles属性是说拥有所示Roles属性值列表中任何一个的用户都可以访问。 如果需要多个Role, 可以多次指定 Authorize 属性, 或者自己实现filter筛选器。

[Authorize(Roles="admin, system"]
public class BackofficeController : Controller
{
...
}

此外,Authorize属性还可以通过ActiveAuthenticationSchemes 属性接受一个或多个身份验证方案。

[Authorize(Roles="admin, system", ActiveAuthenticationSchemes="Cookie"]
public class BackofficeController : Controller
{
...
}

ActiveAuthenticationSchemes 属性是一个用都好分隔的字符串, 它列出了 Authorization 层在当前上下文context 信任的 Authentication中间件组件。 如前所述,传递给ActiveAuthenticationSchemes属性的字符串值必须与在应用程序启动时注册的Authentication中间件(身份验证中间件)相匹配。

这里要提到一点, 在Asp.Net 2.0 Authentication 中间件被替换为一个具有多个handlerservice。 这就造成了, 一个 AuthenticationSchema 是一个选择 handler的标签。 更多相关 Cookies, Claims and Authentication in ASP.NET Core"

Authorization Filter (授权筛选器)

Authorize 属性提供的信息被 系统提供的 Authorization Filter使用。 因为它是负责检测用户使用可以执行某项操作, 这个filter 会在 Asp.Net Core 中其他任何的筛选器之前执行。 没有被授权的用户, 可以停止取消该请求。

可以自定义 授权筛选器, 但最好的方式还是使用默认的筛选器依赖Authorization层。

Roles, Permissions And Overrules

Roles并不能满足所有当代的应用程序。 比如admin 下需要细分更多的权限,这会出现权限继承的问题。

Hierarchy of Roles

角色本质上是平层概念。解决上述问题虽然可以通过创建不同的Role来实现User, Admin, CustomerAdmin and ContentsAdmin, 但是当类似的问题出现的时候,就需要不断地增长role。 这时就需要另外的授权方案--基于Policy

Policy 是什么

Asp.Net Core中, 基于PolicyAuthorization 框架是设计来解耦 AuthorizationApplication Logic. 简单地说,Policy是一个被设计成需求集合的实体,这些需求本身就是当前用户必须满足的条件。

最简单的Policy是对用户进行身份验证,而常见的需求是用户与给定的角色关联。另一个常见的需求是用户拥有一个特定的Claim,或者一个具有特定值的特定Claim。用最一般的术语来说,需求是关于用户身份的断言,该断言试图访问一个为真的方法。使用以下代码创建Policy对象:

var policy = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes("Cookie, Bearer")
.RequireAuthenticatedUser()
.RequireRole("Admin")
.RequireClaim("editor", "contents") .RequireClaim("level", "senior")
.Build();

builder对象使用各种扩展方法收集需求,然后构建Policy实例。可以看到,需求作用于authentication status and schemesrole以及通过authentication cookiebearer token读取的声明的任何组合。

如果定义需求的预定义扩展方法都不适合,可以通过自己的断言来定义新的需求。方法如下:

var policy = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes("Cookie, Bearer")
.RequireAuthenticatedUser()
.RequireRole("Admin")
.RequireAssertion(ctx =>
{
return ctx.User.HasClaim("editor", "contents") ||
ctx.User.HasClaim("level", "senior");
})
.Build();

请注意,如果您多次使用RequireRole,那么用户必须拥有所有的 Role. 如果您想表达一个OR条件,那么可以使用断言。

Registering Policies

定义Policy是不够的,还必须在授权中间件中注册它们。为此,可以在Startup 类的ConfigureServices方法中将授权中间件作为服务添加,如下所示:

services.AddAuthorization(options=>
{
options.AddPolicy("ContentsEditor", policy =>
{
policy.AddAuthenticationSchemes("Cookie, Bearer");
policy.RequireAuthenticatedUser();
policy.RequireRole("Admin");
policy.RequireClaim("editor", "contents");
});
}

添加到中间件的每个Policy都有一个名称,该名称用于在控制器类的Authorize属性内引用该Policy

[Authorize(Policy = "ContentsEditor")]
public IActionResult Save(Article article)
{
// ...
}

Checking Policies Programmatically

public class AdminController : Controller
{
private IAuthorizationService _authorization;
public AdminController(IAuthorizationService authorizationService)
{
_authorization = authorizationService;
}

public async Task<IActionResult> Save(Article article)
{
   var allowed = await _authorization.AuthorizeAsync( User, "ContentsEditor"));
if (!allowed)
return new ForbiddenResult();
// Proceed with the method implementation 
...
}
}

Check of the policies from within a Razor view

@{ 
@inject IAuthorizationService Authorization
var authorized = await Authorization.AuthorizeAsync(
User, "ContentsEditor"))}
@if (!authorized)
{
<div class="alert alert-error">
You’re not authorized to access this page.
</div>
}

Custom Requirements

已有的Requirement 基本上包括ClaimAuthentication,并提供基于断言进行定制的通用机制, 但同时也可以自定义 Requirement。 一个Policy Requirement 有两部分组成: 一个只保存数据 Requirement类,以及一个针对用户验证数据的Authorization Handler。 自定义的 Requirement拓展了处理特定 Policy 的能力。 举例, 通过添加 Requirement 拓展 Content Editor Policy, 让用户至少要有三年的工作经验。

public class ExperienceRequirement : IAuthorizationRequirement
{
public int Years { get; private set; }

public ExperienceRequirement(int minimumYears)
{
Years = minimumYears;
}
}
public class ExperienceHandler : 
AuthorizationHandler<ExperienceRequirement>
{
protected override Task HandleRequirementAsync( 
AuthorizationHandlerContext context, 
ExperienceRequirement requirement)
{
// Save User object to access claims
var user = context.User; if (!user.HasClaim(c => c.Type == "EditorSince")) return Task.CompletedTask;

var since = user.FindFirst("EditorSince").Value.ToInt();
if (since >= requirement.Years)
context.Succeed(requirement);

return Task.CompletedTask;
}
}

样例Authorization Handler读取与用户相关联的Claim,并检查自定义的EditorSince Claim。如果找不到,处理程序将返回失败。

自定义Claim应该是以某种方式链接到用户的一条信息--例如,用户表中的一列--并保存到Authentication Cookie中。 一旦拥有了对用户的引用,总是可以从声明中找到用户名,并对任何数据库或外部服务运行查询,以获得多少年的经验并使用处理程序中的信息。

Authorization Handler调用方法Succeed,传递当前Requirement以通知Requirement已成功验证。如果Requirement没有通过,Handler不需要做任何事情,只需要返回。但是,如果Handler想要确定某个需求的失败,而不考虑同一Requirement上的其他Handler可能成功的事实,那么它将调用Authorization Context对象上的Fail方法

下面是向策略添加定制需求的方法

services.AddAuthorization(options =>
{
options.AddPolicy("AtLeast3Years",
policy => policy
.Requirements
.Add(new ExperienceRequirement(3)));
});

此外,您必须在IAuthorization Handler的范围内向DI系统注册新的Handler:

services.AddSingleton<IAuthorizationHandler, ExperienceHandler>();

如前所述,一个Requirement可以有多个Handler。当多个Handler对相同的Requirement在DI系统中为Authorization 层注册时,至少有一个成功即可。

Accessing the Current HTTP Context

Authorization Handler的实现中,您可能需要检查请求属性或路由数据,如下所示:

if (context.Resource is AuthorizationFilterContext mvc)
{
var url = mvc.HttpContext.Request.GetDisplayUrl(); ...
}

Asp.Net Core中, Authorization Handler Context 对象有一个类型为filter context的 Resource 属性。filter context根据所涉及的框架而不同。例如,MVC和SignalR发送它们自己的特定对象。是否进行类型转换取决于需要访问的内容。例如,用户信息总是存在的,所以您不需要为此进行强制转换,但是如果您想要特定于mvc的细节,比如路由信息,那么您就必须强制转换。

posted @ 2020-09-06 09:07  大头大头灬下雨不愁  阅读(432)  评论(0编辑  收藏  举报