问题引入:

  通过【ASP.NET Core[源码分析篇] - 认证】这篇文章中,我们知道当请求通过认证模块时,会给当前的HttpContext赋予当前用户身份标识,我们在需要授权的控制器中打上[Authorize]授权标签,就可以在ControllerBase的User属性获取到基于声明的权限标识(ClaimsPrincipal)。

  遗憾的是这只是针对Controller层面,很多场景下我们是需要在Service层乃至数据层获直接使用用户信息,这种情况我们就使用不了User了。  

  解决方案:

  在Asp.net 4.x时代,我们通常的做法是通过HttpContext.Current获取当前请求的上下文进而获取到当前的User属性,所以问题的切入点在于我们如何获取当前的HttpContext上下文。

  在我们的Aspnet Core应用中,系统是通过注入HttpContext的访问器对象IHttpContextAccessor来获取当前的HttpContext。

  实现

  首先我们需要在Startup的ConfigureServices方法中注册IHttpContextAccessor的实例

public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); ....
}

  有了这个注册,我们封装一个方法从IHttpContextAccessor 的HttpContext中获取对应的ClaimsPrincipal,如下(认证通过后,User是具有当前用户的身份标志的ClaimsPrincipal):

public class PrincipalAccessor : IPrincipalAccessor
{
  //没有通过认证的,User会为空
public ClaimsPrincipal Principal => _httpContextAccessor.HttpContext?.User; private readonly IHttpContextAccessor _httpContextAccessor; public PrincipalAccessor(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
} public interface IPrincipalAccessor
{
ClaimsPrincipal Principal { get; }
}

  在Startup的ConfigureServices方法中,我们一样把这个类也加入注册中

public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IPrincipalAccessor, PrincipalAccessor>();
....
}

  有了以上这些基础架构提供的东西,剩下的是我们该需要如何从ClaimsPrincipal获取到对应的Claims了,在这里我们定义一个ClaimsAccessor类负责从PrincipalAccessor把用户的身份标志信息提取出来,比如用户的角色,Id等业务数据,这些是需要在获取Token时系统所提供过的信息。

  public class ClaimsAccessor : IClaimsAccessor
{
protected IPrincipalAccessor PrincipalAccessor { get; } public ClaimsAccessor(IPrincipalAccessor principalAccessor)
{
PrincipalAccessor = principalAccessor;
} /// <summary>
/// 登录用户ID
/// </summary>
public int? ApiUserId
{
get
{
var userId = PrincipalAccessor.Principal?.Claims.FirstOrDefault(c => c.Type == SystemClaimTypes.UserId)?.Value;
if (userId != null)
{
int id = ;
int.TryParse(userId, out id);
return id;
} return null;
}
} /// <summary>
/// 用户角色Id
/// </summary>
public string RoleIds
{
get
{
var roleIds = PrincipalAccessor.Principal?.Claims.FirstOrDefault(c => c.Type == SystemClaimTypes.RoleIds)?.Value;
if (string.IsNullOrWhiteSpace(roleIds))
{
return string.Empty;
} return roleIds;
}
}
  } public interface IClaimsAccessor
{
/// <summary>
/// 登录用户ID
/// </summary>
int? ApiUserId { get; } /// <summary>
/// 用户角色Id
/// </summary>
string RoleIds{ get; }
}

  同样我们在Startup的ConfigureServices方法中把IClaimsAccessor注册进来

public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IPrincipalAccessor, PrincipalAccessor>();
services.AddSingleton<IClaimsAccessor, ClaimsAccessor>();
....
}

  这样我们所涉及到的需获取身份标志的基础类都已经定义好,来看看我们该如何使用这些基础类。

  使用

  在有了以上的这些类,我们需要的是在业务中通过依赖注入方式来解析出我们所需的对象,来好的让我们看看该如何具体使用  

    public class TestService : ITestService
{
private readonly IClaimsAccessor _claims;
private readonly IRepository<Product> _productRepository; public TestService(IClaimsAccessor claims, IRepository<Product> productRepository)
{
_claims = claims;
_productRepository = productRepository;
} public Result AddProduct(ProductDto dto)
{
_productRepository.Insert(new Product
{
Name = dto.Name,
...
CreatorUserId = _claims.ApiUserId
});
}
}

  当我们的IClaimsAccessor解析出来时,我们就获取到所有的Claims信息,可以基于这些信息提取访问用户的身份标志,这样我们就不仅仅局限于在Controller层面才能获取用户的身份标志了,至此我们的系统级别的标识已经完成,记得在项目的启动项中利用ASP.NET Core的容器把服务注册进来,在需通的地方解析出来即可使用。

  改进

  这时能满足我们的业务吗?能!但是对于我们稍微要求高点的程序员,我们就可以发现,如果每个服务都按照上面的写法的话,明显在实际应用中需要写很多重复的代码,每次都需要手动进行构造注入会比较繁琐,好吧,看看我们该如何进行优雅且可复用的简化和改进

  首先我们先定义一个ServiceProviderInstance类

  public class ServiceProviderInstance
{
public static IServiceProvider Instance { get; set; }
} 

  这个类的作用是保存IServiceProvider的一个实例,为什么需要这样呢?这里的一个设计思想是,我们如何能顺利解析出我们所需的IClaimsAccessor对象进而得到我们所需的信息?在ASP.NET Core的容器中,系统提供了IServiceCollection来注册服务和提供了IServiceProvider这个让我们解析各种注册过的服务(具体可参考ASP.NET Core - 依赖注入文章所讲解的依赖注入),这时我们的目标就是需要获取到当前应用的IServiceProvider实例,所以这个ServiceProviderInstance类的作用时为了获取IServiceProvider所设计出来的静态类。

  如何获取到应用的IServiceProvider实例?

  在应用初始化过程中,WebHostBuilder会利用ServiceCollection来创建新的ServiceProvider来供系统使用,所以我们在Startup类的Configure方法中,通过ApplicationBuilder的ApplicationServices属性就能获取到系统的ServiceProvider实例,在此我们利用ServiceProviderInstance的Instance属性保存当前的IServiceProvider以供系统后面使用  

  public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
...
ServiceProviderInstance.Instance = app.ApplicationServices;
}

  在获取到了系统的IServiceProvider实例后,剩下的就是利用这个实例把我们前面注册的基础服务IClaimsAccessor解析出来了,我们可以利用面向对象的特点,创建基类进行继承

    public abstract class ServiceBase
{
/// <summary>
/// 身份信息
/// </summary>
protected IClaimsAccessor Claims { get; set; } /// <summary>
/// cotr
/// </summary>
protected ServiceBase ()
{
Claims = ServiceProviderInstance.Instance.GetRequiredService<IClaimsAccessor>();
}
}

  好的让我们看看改进后在一个实际环境中该如何使用

   public class TestService : ServiceBase,ITestService
{
private readonly IRepository<Product> _productRepository; public TestService(IRepository<Product> productRepository)
{
_productRepository = productRepository;
} public Result AddProduct(ProductDto dto)
{
_productRepository.Insert(new Product
{
Name = dto.Name,
...
CreatorUserId = Claims.ApiUserId
});
}
}

  让所有子类都继承了ServiceBase,这样在所有的业务层都可以直接获取到用户的身份信息而不用写太多的重复代码。

  总结

  1. 利用ASP.NET Core提供的IHttpContextAccessor来获取HttpContext的User属性

  2. 封装一系列的基础类和利用依赖注入来解析出所有的Claims

  3. 为了避免过多的侵入式代码,优雅且可复用的创建ServiceBase给所有的业务类使用

  让我知道如果你有更好的想法或建议!

ASP.NET Core - 基于IHttpContextAccessor实现系统级别身份标识的更多相关文章

  1. ASP.NET Core 基于JWT的认证(一)

    ASP.NET Core 基于JWT的认证(一) Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计 ...

  2. ASP.NET Core 基于JWT的认证(二)

    ASP.NET Core 基于JWT的认证(二) 上一节我们对 Jwt 的一些基础知识进行了一个简单的介绍,这一节我们将详细的讲解,本次我们将详细的介绍一下 Jwt在 .Net Core 上的实际运用 ...

  3. Asp.Net Core基于JWT认证的数据接口网关Demo

    近日,应一位朋友的邀请写了个Asp.Net Core基于JWT认证的数据接口网关Demo.朋友自己开了个公司,接到的一个升级项目,客户要求用Aps.Net Core做数据网关服务且基于JWT认证实现对 ...

  4. ASP.NET Core基于K8S的微服务电商案例实践--学习笔记

    摘要 一个完整的电商项目微服务的实践过程,从选型.业务设计.架构设计到开发过程管理.以及上线运维的完整过程总结与剖析. 讲师介绍 产品需求介绍 纯线上商城 线上线下一体化 跨行业 跨商业模式 从0开始 ...

  5. 如何在ASP.NET Core中实现一个基础的身份认证

    注:本文提到的代码示例下载地址> How to achieve a basic authorization in ASP.NET Core 如何在ASP.NET Core中实现一个基础的身份认证 ...

  6. [转]如何在ASP.NET Core中实现一个基础的身份认证

    本文转自:http://www.cnblogs.com/onecodeonescript/p/6015512.html 注:本文提到的代码示例下载地址> How to achieve a bas ...

  7. Asp.net Core基于MVC框架实现PostgreSQL操作

    简单介绍 Asp.net Core最大的价值在于跨平台.跨平台.跨平台.重要的事情说三遍.但是目前毕竟是在开发初期,虽然推出了1.0.0 正式版,但是其实好多功能还没有完善.比方说编译时的一些文件编码 ...

  8. Asp.Net Core基于Cookie实现同域单点登录(SSO)

    在同一个域名下有很多子系统 如:a.giant.com  b.giant.com   c.giant.com等 但是这些系统都是giant.com这个子域. 这样的情况就可以在不引用其它框架的情况下, ...

  9. Asp.Net Core Docker镜像更新系统从wheezy改为stretch

    之前写过一个在Asp.Net Core里调用System.Drawing.Common绘图的DEMO,部署到Docker里运行,需要更新Asp.Net Core镜像的操作系统. https://www ...

随机推荐

  1. DS-5新加交叉编译工具

    Adding New Compiler Toolchains to DS-5 In this tutorial, you will learn how to add new compiler tool ...

  2. python基础知识第二篇(字符串)

    基本数据类型 数字                  整形 int                             ---int                            将字符串 ...

  3. 如何使用charles抓包H5页面内容

    安装charles 这里推荐直接去官网下载 https://www.charlesproxy.com/latest-release/download.do 根据自己的电脑选择合适的安装包,我这里选择m ...

  4. python通过人脸识别全面分析好友,一起看透你的“朋友圈”

    微信:一个提供即时通讯服务的应用程序,更是一种生活方式,超过数十亿的使用者,越来越多的人选择使用它来沟通交流. 不知从何时起,我们的生活离不开微信,每天睁开眼的第一件事就是打开微信,关注着朋友圈里好友 ...

  5. docker快速部署本地项目到服务器(tomcat8+mysql8)

    目标是:将本地运行的spring项目,部署到服务器上 为什么使用docker? 环境隔离 服务器上,各种环境交杂,使用docker,能清楚的把各个项目进行隔离,不单维护的人员方便,也会省去很多维护这些 ...

  6. sshfs 相关材料索引

    这两天简单看了 sshfs 缓存相关的内容,下面对一些好的链接进行索引,防止以后忘了: OpenSSH: Difference between internal-sftp and sftp-serve ...

  7. 分布式Redis的分布式锁 Redlock

    链接 Distributed locks with Redis 引言 之前自己在用redis来实现分布式锁的时候都是基于单个Redis实例,也就是说Redis本身是有单点故障的,Redis的官方文档介 ...

  8. Spring Boot 异常处理静止trace

    概述 在spring boot 2.2 中 默认状态为status 999 private void addStatus(Map<String, Object> errorAttribut ...

  9. 一步一步搭建 Oracle Data Guard

    前言 为什么要写前言,因为我要吐槽一下.作为一个Java后端,搭建Oracle Data Guard真的是一件,嗯,既不专业也不擅长的事情,然而,为什么还是要我来弄? 因为DBA出差了,我们这边急着要 ...

  10. springboot~mybatis枚举映射

    在mybatis和mybatis plus里,如果你的实体字段是一个枚举类型,而在数据表里是整型,这时在存储时需要进行处理,默认情况下,会把枚举的元素名称拼接到SQL语句里,而由于数据表是int类型, ...