这篇文章我们将一起来学习 Asp.Net Core 中的(注:这样描述不准确,稍后你会明白)授权过程

前情提要

在之前的文章里,我们有提到认证和授权是两个分开的过程,而且认证过程不属于Identity。同样授权过程也不属于Identity,授权过程放在Identity系列中将的原因和认证过程一样——和成员系统放在一起容易理解。

动手做

在弄清的是授权过程在哪里发生的之前,我们先来动手写一写授权的代码,如果了解策略授权,那么你可以快速浏览过这部分

打开之前创建的项目,添加一个名为Demo的控制器,控制器代码如下:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; namespace IdentityDemo.Controllers
{
[Produces("application/json")]
[Route("api/demo")]
public class DemoController : Controller
{
[Authorize]
[HttpGet]
public object Get()
{
return new
{
User.Identity.Name,
User.Identity.IsAuthenticated
略...

用之前注册的账户登录系统,

访问/api/demo,你将得到如下结果:

{
"name": "jbl-2011@163.com",
"isAuthenticated": true
}

然后退出登录,再次访问/api/demo,那么将会跳转到登陆页面,在这个过程中Authorize特性起到了至关重要的作用,接下来去掉Authorize特性,重复上两个操作,未登录的结果将是:

{
"name": null,
"isAuthenticated": false
}

通过这两个小例子,我们很容易就能推断出Authorize特性拦截了没有登陆的用户,等等,是Authorize特性拦截了请求吗?

授权过程的发生地

很显然,不是Authorize特性拦截了请求,特性只是标记了这个方法需要被授权才能访问,而真正拦截了请求的是——“Mvc 中间件”。Action是由Mvc执行的,Mvc执行时会确认Action上的Authorize特性,来确定是否要进行授权操作(成功授权可以访问,失败了会被阻止(比如跳转到登陆)),以及如何授权(动物园例子中,第二个门卫根据切实的情况决定),也就是自定义授权(角色等等)。

另外,如果我们只是简单的为 Action方法打上[Authorize]标记,那么它的默认行为就是验证IsAuthenticated是否是true,也就是在认证环节(Authentication 中间件)是否通过了认证

现在,我们知道了两个点

  • 认证过程 Authentication 发生在 Authentication 中间件中
  • 授权过程 Authorization 发生在 Mvc中间件中

基于策略的灵活授权

在企业应用中最为常见的就是基于角色的授权,实现角色授权的方式有两种,一种是直接写在Authorize特性上:

[Authorize(Roles = "admin,super-admin,")]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Test()

不过这种方式,不推荐,因为这样的话我们就将“角色”和“Uri”的绑定“硬编码在代码里了”,在很多场景这显然不合适,所以接下来我们要介绍的基于策略的授权就允许我们自定义授权逻辑,这样就灵活多了

基于策略Policy的授权

我们假设我们的授权规则是要求和上方代码片段实现相同效果,即用户具有角色“admin”或者角色“super-admin”,我们来逐步实现这个目标:

第一步在 DI 中注册一个用于我们需要的 policy

services.AddAuthorization(options =>
{
options.AddPolicy("role-policy", policy =>
{
policy.AddRequirements(new RoleRequirement("admin","super-admin"));
});
});

我们为该策略指定了一个名字role-policy,并且指定了这个策略的需求条件,需求条件主要是为了设置策略的初始值,我们可以在策略注册时更改需求条件从而灵活控制授权。

接下来我们来编写 RoleRequirement

public class RoleRequirement : IAuthorizationRequirement
{
public IEnumerable<string> Roles { get; }
public RoleRequirement(params string[] roles)
{
Roles = roles ?? throw new ArgumentNullException(nameof(roles));
略...

那我们的 RoleRequirement 主要实现的功能就是确定要包含的角色,因为要包含的角色是在构造函数中确定的,那么我们就将角色授权的逻辑(稍后介绍的Handler)和具体授权的数据分开了。

然后我们来实现RoleRequirement对应的处理程序:

public class RoleHandler : AuthorizationHandler<RoleRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement)
{ foreach (var item in requirement.Roles)
{
if (context.User.IsInRole(item))
{
context.Succeed(requirement);
return Task.CompletedTask;
}
}
context.Fail();
return Task.CompletedTask;
略...

这个处理器的工作十分简单就是验证当前用户是否在任意一个由RoleRequirement指定的角色中。在这里context.Succeed(requirement);指示授权成功,而授权失败一般不需要调用 context.Fail();因为对于这个需求还可能有其它处理器进行处理,而此例中调用 context.Fail();可以确保授权失败,因为RoleRequirement的处理器只有一个,所以这样做是没有问题的。

要注意的是刚刚提到的,我们已经将角色授权的逻辑(稍后介绍的Handler)和具体授权的数据分开了。

因为RoleHandler并不清楚要求用户有哪些角色,RoleHandler只知道如何去验证用户含有哪些角色,而具体要求用户含有哪些角色,是由 RoleRequirement 来决定的,这符合关注点分离和单一职责这两个编程概念。

再然后,我们要将刚刚写好的RoleHandler注册进Di

services.AddSingleton<IAuthorizationHandler, RoleHandler>();

最后一步,更换原来的Attribute:

// [Authorize(Roles = "admin,super-admin,")]
[Authorize(Policy ="role-policy")]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Test()

现在,一个最基本的基于策略的授权就完成了。

本文中的示例较为简单,也并没有使用全部的授权特性,更详细的使用方法参考资料很多,本文也就不多做介绍。

另外你可以参考ASP.NET Core中基于策略的授权来学习更过关于策略授权的内容

授权时指定AuthenticationScheme

指定AuthenticationScheme的代码类似这样:

// [Authorize(Roles = "admin,super-admin,")]
[Authorize(AuthenticationSchemes ="jwt"/*注意,这里的名字取决于你添加AuthenticationHandler时的名字*/, Policy ="role-policy")] [HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Test()

在上一篇博客ASP.NET Core Identity 实战(3)认证过程中提到,在Authentication中间件中可以放置多个Handler,而有一个是默认激活的,那么剩下的是被动调用的,现在我们的情况就是由我们在Authorize特性中去挑选一个Handler来执行,例如我们在Authentication中间件上放置两个Handler——CookieAuthenticationHandler和JwtAuthenticationHandler,并经CookieAuthenticationHandler指定为默认,那么我们想经由Jwt认证时怎么办?

这里有一个重要问题就是:当HttpContext流过Authentication中间件后才到Mvc中间件,而Mvc在确认Action指定的AuthenticationHandler时,Authentication过程已经结束了

那这是怎么做到的呢?

还记的HttpContext中有一个扩展方法叫AuthenticateAsync,作为HttpContext的扩展方法也就意味着,我们可以在任何时候调用它进行认证操作。

namespace Microsoft.AspNetCore.Authentication
{
public static class AuthenticationHttpContextExtensions
{
public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context);
public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context, string scheme);
略...

看它的第二个重载,它是指定了 AuthenticationScheme的名字的,所以在Mvc中间件探查到Attribute指定了AuthenticationScheme时,就会重新挑选指定的AuthenticationHandler再次对请求进行认证

授权的发生地——AuthorizationFilter

在旧的Asp.Net时代,我们知道MvcFilter这个东西,现在它仍然在,如果你不了解它,我建议你稍作了解,建议参考官方文档

正如这一节的标题,授权发生在Microsoft.AspNetCore.Mvc.Authorization.AuthorizationFilter中,授权的逻辑类似这样:

先进行认证

如果指定了scheme,那么重新认证,如果没有,则使用之前 Authentication中间件的授权结果:

    public virtual async Task<AuthenticateResult> Microsoft.AspNetCore.Authorization.Policy.PolicyEvaluator.AuthenticateAsync(AuthorizationPolicy policy, HttpContext context)
{
if (policy.AuthenticationSchemes != null && policy.AuthenticationSchemes.Count > 0)
{
ClaimsPrincipal newPrincipal = null;
foreach (var scheme in policy.AuthenticationSchemes)
{
var result = await context.AuthenticateAsync(scheme);
if (result != null && result.Succeeded)
{
newPrincipal = SecurityHelper.MergeUserPrincipal(newPrincipal, result.Principal);
}
} if (newPrincipal != null)
{
context.User = newPrincipal;
return AuthenticateResult.Success(new AuthenticationTicket(newPrincipal, string.Join(";", policy.AuthenticationSchemes)));
}
else
{
context.User = new ClaimsPrincipal(new ClaimsIdentity());
return AuthenticateResult.NoResult();
}
} return (context.User?.Identity?.IsAuthenticated ?? false)
? AuthenticateResult.Success(new AuthenticationTicket(context.User, "context.User"))
: AuthenticateResult.NoResult();
}

这里面值得再次深入探讨的是 context.AuthenticateAsync(scheme),这是在 HttpAbstractions项目中的扩展方法,它的实现是:

    public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context, string scheme) =>
context.RequestServices.GetRequiredService<IAuthenticationService>().AuthenticateAsync(context, scheme);

IAuthenticationService我们在 Authentication中间件中也见过,Authentication中间件也是使用了IAuthenticationService,之前的文章有提到过,这也再次证明了单一原则职责,身份认证中间件负责在管道中认证,而认证本身并非是和身份认证中间件捆绑的,上一篇博客ASP.NET Core Identity 实战(3)认证过程的最后有认证的源代码

再进行授权

授权总共分三步

  1. 拿到IAuthorizeHandler的实例(前面我们写了一个)(可能一个或者多个
  2. 执行授权(每个Handler都会进行授权)
  3. 没了

这部分代码还是很简单的:

    public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements)
{
// 第一步
var authContext = _contextFactory.CreateContext(requirements, user, resource);
var handlers = await _handlers.GetHandlersAsync(authContext);
// 第二部
foreach (var handler in handlers)
{
await handler.HandleAsync(authContext);
if (!_options.InvokeHandlersAfterFailure && authContext.HasFailed)
{
break;
}
} // 没了(这主要是对结果进行处理)
var result = _evaluator.Evaluate(authContext);
if (result.Succeeded)
{
_logger.UserAuthorizationSucceeded(GetUserNameForLogging(user));
}
else
{
_logger.UserAuthorizationFailed(GetUserNameForLogging(user));
}
return result;
}

这里面和我们在项目中写的代码有关就是IAuthorizeHandler的实例,在本文中,我们写了一个RoleHandler

到此,授权过程就结束了,另外一些就是边边角角的知识点,比如授权之后如何操作,这些不难,就不再文中赘述了

ASP.NET Core Identity 实战(4)授权过程的更多相关文章

  1. ASP.NET Core Identity 实战(2)——注册、登录、Claim

    上一篇文章(ASP.NET Core Identity Hands On(1)--Identity 初次体验)中,我们初识了Identity,并且详细分析了AspNetUsers用户存储表,这篇我们将 ...

  2. ASP.NET Core Identity 实战(3)认证过程

    如果你没接触过旧版Asp.Net Mvc中的 Authorize 或者 Cookie登陆,那么你一定会疑惑 认证这个名词,这太正式了,这到底代表这什么? 获取资源之前得先过两道关卡Authentica ...

  3. ASP.NET Core Identity 迁移数据 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core Identity 迁移数据 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core Identity 迁移数据 上一章节中我们配置了 ...

  4. ASP.NET Core Identity Hands On(2)——注册、登录、Claim

    上一篇文章(ASP.NET Core Identity Hands On(1)--Identity 初次体验)中,我们初识了Identity,并且详细分析了AspNetUsers用户存储表,这篇我们将 ...

  5. ASP.NET Core Identity 配置 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core Identity 配置 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core Identity 配置 上一章节我们简单介绍了下 Id ...

  6. Asp.Net Core Identity 隐私数据保护

    前言 Asp.Net Core Identity 是 Asp.Net Core 的重要组成部分,他为 Asp.Net Core 甚至其他 .Net Core 应用程序提供了一个简单易用且易于扩展的基础 ...

  7. Asp.Net Core 项目实战之权限管理系统(4) 依赖注入、仓储、服务的多项目分层实现

    0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...

  8. Asp.Net Core 项目实战之权限管理系统(2) 功能及实体设计

    0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...

  9. Asp.Net Core 项目实战之权限管理系统(3) 通过EntityFramework Core使用PostgreSQL

    0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...

随机推荐

  1. Python中threading的join和setDaemon的区别及用法

    Python多线程编程时经常会用到join()和setDaemon()方法,基本用法如下: join([time]): 等待至线程中止.这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或 ...

  2. SpringMvc的Url映射和传参案例

    Springmvc的基本使用,包括url映射.参数映射.页面跳转.ajax和文件上传 以前学习的时候写的代码案例,今天整理笔记的时候找到了,很久没有来园子了,发上来当个在线笔记用吧,免的时间长了又忘了 ...

  3. javascript面向对象精要第四章构造函数和原型对象整理精要

  4. qsort代码(pascal/c/c++)与思想及扩展(随机化,TopK)

    1.快速排序思想:从一堆数A中找到一个数x,然后把这堆数x分成两堆B,C,B堆的数小于(或小于等于)该数,放在左边,C堆的数大于(或大于等于)该数,放在右边,有可能把该数x单独分开,放在中间.然后对小 ...

  5. Java逐行写入字符串到文件

    下边是写东西到一个文件中的Java代码.运行后每一次,一个新的文件被创建,并且之前一个也将会被新的文件替代.这和给文件追加内容是不同的. 1. public static void writeFile ...

  6. struct字节对齐原则

    原则1:windows下,k字节基本类型以k字节倍数偏移量对齐,自定义结构体则以结构体中最高p字节基本类型的p字节倍数偏移量对齐,Linux下则以2或4字节对齐; 原则2:整体对齐原则,例如数组结构体 ...

  7. Prometheus Redis_exporter

    Redis 下载redis_exporter wget https://github.com/oliver006/redis_exporter/releases/download/v0.15.0/re ...

  8. 金融量化分析【day113】:羊驼策略

    零.动量策略VS反转策略 1.实现代码 # 导入函数库 import jqdata import pandas as pd import numpy as np import datetime imp ...

  9. Linux记录-普通用户下执行sudo xxx 找不到命令解决方案

    chmod 777 /etc/sudoers vim /etc/sudoers 1.可以使用 secure_path 指令修改 sudoers 中默认的 PATH为你想要的路径.这个指令指定当用户执行 ...

  10. Linux记录-Linux 企业运维人员最常用 150 个命令

    命令 功能说明 线上查询及帮助命令 (2 个) man 查看命令帮助,命令的词典,更复杂的还有 info,但不常用. help 查看 Linux 内置命令的帮助,比如 cd 命令. 文件和目录操作命令 ...