使用asp.net core 开发应用系统过程中,基本上都会涉及到用户身份的认证,及授权访问控制,因此了解认证和授权流程也相当重要,下面通过分析asp.net core 框架中的认证和授权的源码来分析认证、授权的原理及认证和授权的关系。

认证是什么?

认证是应用系统识别当前访问者的身份的一个过程,当应用系统接收到浏览器的请求后,通常会根据请求中携带的一些用户的的关键信息来识别当前登录用户的身份,通过解析这些信息,对用户进行合法性校验并进行解密,如果校验通过,则表示认证通过,应用系统会将认证通过后的用户信息存储到Http请求上下文中,以便后续业务使用及授权流程中使用。

asp.net core中通常将认证信息加密后存储到cookie中,每次访问需要认证的页面时将这些cookie信息发送到应用系统,以便应用系统识别访问者的身份,也就是经典的Cookie认证。

需要注意的是:认证仅仅只是识别当前访问用户的身份,并不负责具体的访问权限控制逻辑,如不具备某个资源的访问权限返回403,未登录返回401等,这些均由授权流程来控制。

asp.net core 中负责认证流程的中间件是AuthenticationMiddleware 类,以下是asp.net core 3.1 的源代码,可以看到,先遍历所有实现了IAuthenticationRequestHandler接口的认证方案,并调用IAuthenticationRequestHandler接口的HandleRequestAsync方法,如果认证通过,则不再继续往下执行,并且此时HttpContext.User已经包含认证后的用户信息,如果所有实现 IAuthenticationRequestHandler 接口的认证方案,都未能对当前访问用户进行身份认证,则使用默认的认证方案进行认证(也就是:GetDefaultAuthenticateSchemeAsync返回的认证方案),可以看到认证流程即使没能识别当前访问者的用户身份,也会继续执行下一个流程,(尾部:await _next(context);

public class AuthenticationMiddleware
{
private readonly RequestDelegate _next; public IAuthenticationSchemeProvider Schemes
{
get;
set;
} public AuthenticationMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemes)
{
if (next == null)
{
throw new ArgumentNullException("next");
}
if (schemes == null)
{
throw new ArgumentNullException("schemes");
}
_next = next;
Schemes = schemes;
} public async Task Invoke(HttpContext context)
{
context.Features.Set((IAuthenticationFeature)new AuthenticationFeature
{
OriginalPath = context.Request.Path,
OriginalPathBase = context.Request.PathBase
});
IAuthenticationHandlerProvider handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
foreach (AuthenticationScheme item in await Schemes.GetRequestHandlerSchemesAsync())
{
IAuthenticationRequestHandler authenticationRequestHandler = (await handlers.GetHandlerAsync(context, item.Name)) as IAuthenticationRequestHandler;
bool flag = authenticationRequestHandler != null;
if (flag)
{
flag = await authenticationRequestHandler.HandleRequestAsync();
}
if (flag)
{
return;
}
} AuthenticationScheme authenticationScheme = await Schemes.GetDefaultAuthenticateSchemeAsync();
if (authenticationScheme != null)
{
//内部调用IAuthenticationService进行认证。
AuthenticateResult authenticateResult = await context.AuthenticateAsync(authenticationScheme.Name);
if (authenticateResult?.Principal != null)
{
context.User = authenticateResult.Principal;
}
}
await _next(context);
}
}

授权是什么?

授权是确定当前访问用户是否具备访问某个系统资源权限的过程,对于需要授权才能访问的系统资源,通常通过[Authorize]特性来标识,通过该特性,可以指定该资源需要哪个用户角色才能访问、必须符合哪个授权策略才能访问,以及访问该资源时采用的用户认证方案是什么,当用户访问系统的某个API或者页面时,授权流程会检查当前用户是否具备该API或者页面的访问权限,如果授权检查失败,那么会判断当前用户是否已经认证通过,如果认证通过,但无访问该资源的权限,那么返回403(禁止访问),如果未认证,那么直接返回401(未认证),表示需要用户登录认证后在进行访问,需要注意的是:检查是否具备访问权限之前会先进行用户身份的认证,至于用什么认证方案就看AuthorizeAttribute有没有指定特定的认证方案,如果没有,则直接采用认证流程的认证成功的身份信息。

asp.net core 中,授权流程的执行是通过AuthorizationMiddleware类来完成的,以下是asp.net core 3.1中的源码。

// Microsoft.AspNetCore.Authorization.AuthorizationMiddleware
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Policy;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection; public class AuthorizationMiddleware
{
private const string AuthorizationMiddlewareInvokedWithEndpointKey = "__AuthorizationMiddlewareWithEndpointInvoked"; private static readonly object AuthorizationMiddlewareWithEndpointInvokedValue = new object(); private readonly RequestDelegate _next; private readonly IAuthorizationPolicyProvider _policyProvider; public AuthorizationMiddleware(RequestDelegate next, IAuthorizationPolicyProvider policyProvider)
{
_next = next ?? throw new ArgumentNullException("next");
_policyProvider = policyProvider ?? throw new ArgumentNullException("policyProvider");
} public async Task Invoke(HttpContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
Endpoint endpoint = context.GetEndpoint();
if (endpoint != null)
{
context.Items["__AuthorizationMiddlewareWithEndpointInvoked"] = AuthorizationMiddlewareWithEndpointInvokedValue;
}
//获取访问当前资源所需要的所有角色权限,及授权策略,以及访问该资源时需要使用的认证方案列表,并统一合并到一个AuthorizationPolicy对象中。
IReadOnlyList<IAuthorizeData> authorizeData = endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>() ?? Array.Empty<IAuthorizeData>();
AuthorizationPolicy policy = await AuthorizationPolicy.CombineAsync(_policyProvider, authorizeData);
if (policy == null)
{
await _next(context);
return;
}
IPolicyEvaluator policyEvaluator = context.RequestServices.GetRequiredService<IPolicyEvaluator>();
//通过IPolicyEvaluator.AuthenticateAsync()方法,对当前访问者进行认证,至于使用哪种方案认证,根据该资源要求使用的认证方案来,如果没有指定,
//则使用默认认证方案进行认证。
AuthenticateResult authenticationResult = await policyEvaluator.AuthenticateAsync(policy, context);
//如果包含实现了IAllowAnonymous接口的特性,则不进行授权检查。
if (endpoint?.Metadata.GetMetadata<IAllowAnonymous>() != null)
{
await _next(context);
return;
}
//这里调用AuthorizeAsync进行授权检查,注意,这里将上一步认证结果authenticationResult也传到了授权检查方法内部。
PolicyAuthorizationResult policyAuthorizationResult = await policyEvaluator.AuthorizeAsync(policy, authenticationResult, context, endpoint);
//检查授权结果,如果是未登录,则返回401未认证,让用户进行登录,如果该资源指定了特定的认证方案,则调用特定认证方案的Challenge方法,
//否则调用默认认证方案的Challenge方法,通常Challenge做的事情就是重定向用户的浏览器到登录页面或者对于ajax异步请求返回401.
if (policyAuthorizationResult.Challenged)
{
if (policy.AuthenticationSchemes.Any())
{
foreach (string authenticationScheme in policy.AuthenticationSchemes)
{
await context.ChallengeAsync(authenticationScheme);
}
}
else
{
await context.ChallengeAsync();
}
}
//如果当前访问者用户身份认证通过,但是不被允许访问该资源的权限,那么默认返回401(禁止访问)给浏览器端,通常对于未授权的访问请求,应用常常的做法是将用户的浏览器重定向到禁止访问的提示页面,或者对于ajax异步请求来说,通常返回403状态码,和上面未认证情况一样,如果该资源指定了特定的认证方案,那么会调用特定认证方案的Forbid方法,否则调用默认认证方案的Forbid方法。
else if (policyAuthorizationResult.Forbidden)
{
if (policy.AuthenticationSchemes.Any())
{
foreach (string authenticationScheme2 in policy.AuthenticationSchemes)
{
await context.ForbidAsync(authenticationScheme2);
}
}
else
{
await context.ForbidAsync();
}
}
else
{
await _next(context);
}
}
}

IPolicyEvaluator接口实现类 PolicyEvaluator类代码如下,该类主要是负责授权流程中的认证和授权。

// Microsoft.AspNetCore.Authorization.Policy.PolicyEvaluator
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Policy;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Internal; public class PolicyEvaluator : IPolicyEvaluator
{
private readonly IAuthorizationService _authorization; public PolicyEvaluator(IAuthorizationService authorization)
{
_authorization = authorization;
} public virtual async Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context)
{
//这里去判断当前资源是否有要求特定的认证方案进行认证,如果有指定特定的认证方案,则分别对每个认证方案进行认证,并把认证后的用户信息进行合并
//最终存储到HttpContext.User属性中,并返回认证成功,如果没有指定认证方案,则使用认证流程中已经认证的用户信息作为认证结果返回,
//从这里可以看出,认证流程还是很有必要的,在资源没有指定认证方案的前提下,认证流程为授权流程提供当前访问者的身份信息,以便执行是否具备相应资源的访问权限检查,否则就直接进入Challenge流程将要求用户先进行身份认证了
if (policy.AuthenticationSchemes != null && policy.AuthenticationSchemes.Count > 0)
{
ClaimsPrincipal newPrincipal = null;
foreach (string authenticationScheme in policy.AuthenticationSchemes)
{
AuthenticateResult authenticateResult = await context.AuthenticateAsync(authenticationScheme);
if (authenticateResult != null && authenticateResult.Succeeded)
{
newPrincipal = SecurityHelper.MergeUserPrincipal(newPrincipal, authenticateResult.Principal);
}
}
if (newPrincipal != null)
{
context.User = newPrincipal;
return AuthenticateResult.Success(new AuthenticationTicket(newPrincipal, string.Join(";", policy.AuthenticationSchemes)));
}
context.User = new ClaimsPrincipal(new ClaimsIdentity());
return AuthenticateResult.NoResult();
}
return (context.User?.Identity?.IsAuthenticated).GetValueOrDefault() ? AuthenticateResult.Success(new AuthenticationTicket(context.User, "context.User")) : AuthenticateResult.NoResult();
}
//resource为EndPoint对象。
public virtual async Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, object resource)
{
if (policy == null)
{
throw new ArgumentNullException("policy");
}
//这里调用IAuthorizationService.AuthorizeAsync方法进行授权检查,默认实现类为:DefaultAuthorizationService。
if ((await _authorization.AuthorizeAsync(context.User, resource, policy)).Succeeded)
{
return PolicyAuthorizationResult.Success();
}
//下面这句表示如果授权检查失败的情况下是进入Forbid流程还是进入Challenge流程,可以看到如果认证成功,那么表示无权限访问进入Forbid流程。
//如果未认证,则进入Challenge流程,引导用户登录认证。
return authenticationResult.Succeeded ? PolicyAuthorizationResult.Forbid() : PolicyAuthorizationResult.Challenge();
}
}

认证和授权的关系?

授权检查之前都会先执行用户身份的认证,不过这里的认证流程只有在被访问的资源有指定特定的认证方案时才会执行,否则直接采用统一认证流程中的产生的认证信息。

可以理解为认证流程一方面是为了告诉应用系统当前访问者的身份,一方面是为了给授权检查时识别用户的身份信息,当资源没有指定采用何种认证方案时,授权流程将会采用统一认证流程里认证通过产生的用户信息,如果不启用认证流程,并且被访问的资源也没有指定特定的认证方案对访问者身份进行认证时,那么最终访问该资源时还是会被要求先登录认证,因此认证流程的另外一个用途就是为授权流程提供默认的用户认证信息。

总结起来说,

认证流程主要有如下几个作用:

  1. 识别系统访问者的身份信息,认证通过后提供给后续业务使用。
  2. 给授权流程提供访问者身份信息(资源没有指定特定认证方案时,采用默认认证方案认证通过的用户信息)。
  3. 实现授权失败后的处理逻辑,比如授权检查失败后返回的 401(未认证),403(禁止访问)等最终都是认证方案的 ChallegeAsync方法以及ForbidAsync方法来处理,这些方法是IAuthenticationHandler里面定义的,这些流程在授权失败为401/403的时候分别被授权流程调用。

授权流程主要如下几个作用:

  1. 授权流程主要是检查当前用户是否具备指定资源的访问权限,如果授权检查失败,如401(未认证),403(禁止访问),那么最终会分别调用认证方案的ChallegenAsync和ForbidAsync方法,也就是说,授权流程侧重于授权失败后的流程控制。
  2. 授权流程另外一个主要的任务是检查授权策略是否均能检验通过,如果一个资源通过AuthorizeAttribute的Policy属性指定了一个或者多个授权策略,那么必须所有授权策略都验证通过才算授权成功,如果未指定授权策略,那么就验证默认的授权策略是否能检验通过,默认的授权策略则是要求必须用户认证通过才允许访问资源。

授权流程本质上就是遍历所有注入到容器中的IAuthorizationHandler(微软默认在AddAuthorization的时候向容器注入了:PassThroughAuthorizationHandler,这个授权处理程序遍历AuthorizationHandlerContext.Requirements中所有实现了IAuthorizationHandler的Requirement类,并调用其HandleAsync方法来检查当前Requirement是否能校验通过),并对访问指定资源所要满足的所有策略中包含的Requirement进行验证,如果所有策略包含的Requirement都验证通过,那么表示授权成功,这里的Requirement是指实现了IAuthorizationRequirement的类,这个接口是一个空接口,用于标记Requirement使用。

聊聊 asp.net core 认证和授权的更多相关文章

  1. ASP.NET Core 认证与授权[2]:Cookie认证

    由于HTTP协议是无状态的,但对于认证来说,必然要通过一种机制来保存用户状态,而最常用,也最简单的就是Cookie了,它由浏览器自动保存并在发送请求时自动附加到请求头中.尽管在现代Web应用中,Coo ...

  2. ASP.NET Core 认证与授权[5]:初识授权

    经过前面几章的姗姗学步,我们了解了在 ASP.NET Core 中是如何认证的,终于来到了授权阶段.在认证阶段我们通过用户令牌获取到用户的Claims,而授权便是对这些的Claims的验证,如:是否拥 ...

  3. ASP.NET Core 认证与授权[6]:授权策略是怎么执行的?

    在上一章中,详细介绍了 ASP.NET Core 中的授权策略,在需要授权时,只需要在对应的Controler或者Action上面打上[Authorize]特性,并指定要执行的策略名称即可,但是,授权 ...

  4. ASP.NET Core 认证与授权[7]:动态授权

    ASP.NET Core 中基于策略的授权旨在分离授权与应用程序逻辑,它提供了灵活的策略定义模型,在一些权限固定的系统中,使用起来非常方便.但是,当要授权的资源无法预先确定,或需要将权限控制到每一个具 ...

  5. ASP.NET Core 认证与授权[1]:初识认证

    在ASP.NET 4.X 中,我们最常用的是Forms认证,它既可以用于局域网环境,也可用于互联网环境,有着非常广泛的使用.但是它很难进行扩展,更无法与第三方认证集成,因此,在 ASP.NET Cor ...

  6. ASP.NET Core 认证与授权[3]:OAuth & OpenID Connect认证

    在上一章中,我们了解到,Cookie认证是一种本地认证方式,通常认证与授权都在同一个服务中,也可以使用Cookie共享的方式分开部署,但局限性较大,而如今随着微服务的流行,更加偏向于将以前的单体应用拆 ...

  7. ASP.NET Core 认证与授权[4]:JwtBearer认证

    在现代Web应用程序中,通常会使用Web, WebApp, NativeApp等多种呈现方式,而后端也由以前的Razor渲染HTML,转变为Stateless的RESTFulAPI,因此,我们需要一种 ...

  8. Asp.net Core认证和授权:Cookie认证

    关于asp.net core 的文章,博客园已经有很多大牛写过了. 这里我只是记录下自己在学习中的点滴和一些不懂的地方 Cookie一般是用户网站授权,当用户访问需要授权(authorization) ...

  9. ASP.NET Core 认证与授权[1]:初识认证 (笔记)

    原文链接:  https://www.cnblogs.com/RainingNight/p/introduce-basic-authentication-in-asp-net-core.html 在A ...

随机推荐

  1. 【黑马pink老师的H5/CSS课程】(二)标签与语法

    视频链接:P8~P29 黑马程序员pink老师前端入门教程,零基础必看的h5(html5)+css3+移动 参考链接: HTML 元素 1.HTML语法规范 1.1 基本语法概述 HTML 标签是由尖 ...

  2. Java 集合常见知识点&面试题总结(上),2022 最新版!

    你好,我是 Guide.秋招即将到来(提前批已经开始),我对 JavaGuide 的内容进行了重构完善,公众号同步一下最新更新,希望能够帮助你. 你也可以在网站(javaguide.cn)上在线阅读, ...

  3. 使用dnSpy对无源码EXE或DLL进行反编译并且修改

    背景 总有一些特殊情况,我们没有源码,但是某个C#程序集dll或者可执行程序exe影响到我们代码的正常运行,我们希望得到源码,能改掉或者修改某些bug,但是苦于没有源码,这个时候可以用dnspy进行源 ...

  4. springboot creating bean with name 'sqlSessionFactory'

    pom.xml文件配置 <build> <plugins> <plugin> <groupId>org.springframework.boot< ...

  5. Webpack干货系列 | Webpack5 怎么处理字体图标、图片资源

    程序员优雅哥(youyacoder)简介:十年程序员,呆过央企外企私企,做过前端后端架构.分享vue.Java等前后端技术和架构. 本文摘要:主要讲解在不需要引入额外的loader的条件下运用Webp ...

  6. java后端分片上传接口

    文件上传工具--FileUtil package com.youmejava.chun.util; import lombok.Data; import org.apache.tomcat.util. ...

  7. springmvc源码笔记-RequestMappingHandlerMapping

    下图是springmvc的执行流程 图片来源:https://www.jianshu.com/p/8a20c547e245 DispatcherServlet根据url定位到Controller和方法 ...

  8. 基于二进制安装Cloudera Manager集群

    一.环境准备 参考链接:https://www.cnblogs.com/zhangzhide/p/11108472.html 二.安装jdk(三台主机都要做) 下载jdk安装包并解压:tar xvf ...

  9. SQL语句实战学习

    参考:https://zhuanlan.zhihu.com/p/38354000再次感谢作者的整理!! 1.数据已提前准备好了,已知有如下4张表:学生表:student 成绩表:score(学号,课程 ...

  10. python代码如何写的优雅?

    简介 在实际项目中,我们可能一开始为了完成功能而忽视了代码的整体质量,因此,使用一些高阶的函数或方法,能够更加使我们的代码更加优雅.废话不多说,现在马上开始. 使用enumerate方法替代range ...