在上篇文章里面,我们在 ApplicationService 当中看到了权限检测代码,通过注入 IAuthorizationService 就可以实现权限检测。不过跳转到源码才发现,这个接口是 ASP.NET Core 原生提供的 “基于策略” 的权限验证接口,这就说明 ABP vNext 基于原生的授权验证框架进行了自定义扩展。
让我们来看一下 Volo.Abp.Ddd.Application 项目的依赖结构(权限相关)。
本篇文章下面的内容基本就会围绕上述框架模块展开,本篇文章通篇较长,因为还涉及到 .NET Core Identity 与 IdentityServer4 这两部分。关于这两部分的内容,我会在本篇文章大概讲述 ABP vNext 的实现,关于更加详细的内容,请查阅官方文档或其他博主的博客。
二、源码分析ABP vNext 关于权限验证和权限定义的部分,都存放在 Volo.Abp.Authorization 和 Volo.Abp.Security 模块内部。源码分析我都比较喜欢倒推,即通过实际的使用场景,反向推导 基础实现,所以后面文章编写的顺序也将会以这种方式进行。
2.1 Security 基础组件库这里我们先来到 Volo.Abp.Security,因为这个模块代码和类型都是最少的。这个项目都没有模块定义,说明里面的东西都是定义的一些基础组件。
2.1.1 Claims 与 Identity 的快捷访问先从第一个扩展方法开始,这个扩展方法里面比较简单,它主要是提供对 ClaimsPrincipal 和 IIdentity 的快捷访问方法。比如我要从 ClaimsPrincipal / IIdentity 获取租户 Id、用户 Id 等。
public static class AbpClaimsIdentityExtensions { public static Guid? FindUserId([NotNull] this ClaimsPrincipal principal) { Check.NotNull(principal, nameof(principal)); // 根据 AbpClaimTypes.UserId 查找对应的值。 var userIdOrNull = principal.Claims?.FirstOrDefault(c => c.Type == AbpClaimTypes.UserId); if (userIdOrNull == null || userIdOrNull.Value.IsNullOrWhiteSpace()) { return null; } // 返回 Guid 对象。 return Guid.Parse(userIdOrNull.Value); } 2.1.2 未授权异常的定义这个异常我们在老版本 ABP 里面也见到过,它就是 AbpAuthorizationException 。只要有任何未授权的操作,都会导致该异常被抛出。后面我们在讲解 ASP.NET Core MVC 的时候就会知道,在默认的错误码处理中,针对于程序抛出的 AbpAuthorizationException ,都会视为 403 或者 401 错误。
public class DefaultHttpExceptionStatusCodeFinder : IHttpExceptionStatusCodeFinder, ITransientDependency { // ... 其他代码 public virtual HttpStatusCode GetStatusCode(HttpContext httpContext, Exception exception) { // ... 其他代码 // 根据 HTTP 协议对于状态码的定义,401 表示的是没有登录的用于尝试访问受保护的资源。而 403 则表示用户已经登录,但他没有目标资源的访问权限。 if (exception is AbpAuthorizationException) { return httpContext.User.Identity.IsAuthenticated ? HttpStatusCode.Forbidden : HttpStatusCode.Unauthorized; } // ... 其他代码 } // ... 其他代码 }就 AbpAuthorizationException 异常来说,它本身并不复杂,只是一个简单的异常而已。只是因为它的特殊含义,在 ABP vNext 处理异常时都会进行特殊处理。
只是在这里我说明一下,ABP vNext 将它所有的异常都设置为可序列化的,这里的可序列化不仅仅是将 Serialzable 标签打在类上就行了。ABP vNext 还创建了基于 StreamingContext 的构造函数,方便我们后续对序列化操作进行定制化处理。
关于运行时序列化的相关文章,可以参考 《CLR Via C#》第 24 章,我也编写了相应的 读书笔记 。
2.1.3 当前用户与客户端开发人员经常会在各种地方需要获取当前的用户信息,ABP vNext 将当前用户封装到 ICurrentUser 与其实现 CurrentUser 当中,使用时只需要注入 ICurrentUser 接口即可。
我们首先康康 ICurrentUser 接口的定义:
public interface ICurrentUser { bool IsAuthenticated { get; } [CanBeNull] Guid? Id { get; } [CanBeNull] string UserName { get; } [CanBeNull] string PhoneNumber { get; } bool PhoneNumberVerified { get; } [CanBeNull] string Email { get; } bool EmailVerified { get; } Guid? TenantId { get; } [NotNull] string[] Roles { get; } [CanBeNull] Claim FindClaim(string claimType); [NotNull] Claim[] FindClaims(string claimType); [NotNull] Claim[] GetAllClaims(); bool IsInRole(string roleName); }那么这些值是从哪儿来的呢?从带有 Claim 返回值的方法来看,肯定就是从 HttpContext.User 或者 Thread.CurrentPrincipal 里面拿到的。