ASP.NET Core - 基于IHttpContextAccessor实现系统级别身份标识
问题引入:
我们知道当请求通过认证模块时,会给当前的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 = 0; 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和HttpContextAccessor的妙用
- sql server 关于表中只增标识问题 C# 实现自动化打开和关闭可执行文件(或 关闭停止与系统交互的可执行文件) ajaxfileupload插件上传图片功能,用MVC和aspx做后台各写了一个案例 将小写阿拉伯数字转换成大写的汉字, C# WinForm 中英文实现, 国际化实现的简单方法 ASP.NET Core 2 学习笔记(六)ASP.NET Core 2 学习笔记(三)
- 如何使用 C# .NET 在 ASP.NET 应用程序中实现基于窗体的身份验证
- 基于UML和ASP.NET实现三层B/S结构系统开发
- 基于layui+asp.net mvc实现个人博客系统
- 如何使用 C# .NET 在 ASP.NET 应用程序中实现基于窗体的身份验证
- Asp.Net Core 项目实战之权限管理系统(4) 依赖注入、仓储、服务的多项目分层实现
- ASP.NET Core 运行原理解剖[4]:进入HttpContext的世界
- 项目开发中的一些注意事项以及技巧总结 基于Repository模式设计项目架构—你可以参考的项目架构设计 Asp.Net Core中使用RSA加密 EF Core中的多对多映射如何实现? asp.net core下的如何给网站做安全设置 获取服务端https证书 Js异常捕获
- 基于UML和ASP.NET实现三层B/S结构系统开发
- 使用ASP.NET Web Api构建基于REST风格的服务实战系列教程【五】——在Web Api中实现Http方法(Put,Post,Delete)
- 模拟HttpContext 实现ASP.NET MVC 的单元测试
- 基于UML和ASP.NET实现三层B/S结构系统开发
- asp.net core 对HttpContext 的扩展
- ASP.Net Mvc实现自定义User Identity用户身份识别系统(2)
- ASP.NET Core开发之HttpContext
- 基于UML和ASP.NET实现三层B/S结构系统开发(网摘)
- 实现基于 ASP.NET Forms 身份验证的跨子域单点登录
- 用 ASP.NET MVC 实现基于 XMLHttpRequest long polling(长轮询) 的 Comet
- 基于UML和ASP.NET实现三层B/S结构系统开发