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
集合, 每一个 identity
用 IIdentiy
对象标识,特别是 Claims Identity
对象, 这意味着任何登录的用户都带有一个 Claim
列表, 这实际上是用户状态的声明。 Username
和 Role
是连个在 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
)。 不止于此, 这个属性也支持其他的属性参数,比如 Roles
。 Roles
属性是说拥有所示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
中间件被替换为一个具有多个handler
的service
。 这就造成了, 一个 Authentication
的 Schema
是一个选择 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 下需要细分更多的权限,这会出现权限继承的问题。
角色本质上是平层概念。解决上述问题虽然可以通过创建不同的Role来实现User, Admin, CustomerAdmin and ContentsAdmin
, 但是当类似的问题出现的时候,就需要不断地增长role。 这时就需要另外的授权方案--基于Policy
。
Policy
是什么
Asp.Net Core中, 基于Policy
的Authorization
框架是设计来解耦 Authorization
和 Application 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 schemes
、role
以及通过authentication cookie
或bearer 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
基本上包括Claim
、Authentication
,并提供基于断言进行定制的通用机制, 但同时也可以自定义 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的细节,比如路由信息,那么您就必须强制转换。