ASP.NET Core - 基于IHttpContextAccessor实现系统级别身份标识
问题引入:
通过【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实现系统级别身份标识的更多相关文章
- ASP.NET Core 基于JWT的认证(一)
ASP.NET Core 基于JWT的认证(一) Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计 ...
- ASP.NET Core 基于JWT的认证(二)
ASP.NET Core 基于JWT的认证(二) 上一节我们对 Jwt 的一些基础知识进行了一个简单的介绍,这一节我们将详细的讲解,本次我们将详细的介绍一下 Jwt在 .Net Core 上的实际运用 ...
- Asp.Net Core基于JWT认证的数据接口网关Demo
近日,应一位朋友的邀请写了个Asp.Net Core基于JWT认证的数据接口网关Demo.朋友自己开了个公司,接到的一个升级项目,客户要求用Aps.Net Core做数据网关服务且基于JWT认证实现对 ...
- ASP.NET Core基于K8S的微服务电商案例实践--学习笔记
摘要 一个完整的电商项目微服务的实践过程,从选型.业务设计.架构设计到开发过程管理.以及上线运维的完整过程总结与剖析. 讲师介绍 产品需求介绍 纯线上商城 线上线下一体化 跨行业 跨商业模式 从0开始 ...
- 如何在ASP.NET Core中实现一个基础的身份认证
注:本文提到的代码示例下载地址> How to achieve a basic authorization in ASP.NET Core 如何在ASP.NET Core中实现一个基础的身份认证 ...
- [转]如何在ASP.NET Core中实现一个基础的身份认证
本文转自:http://www.cnblogs.com/onecodeonescript/p/6015512.html 注:本文提到的代码示例下载地址> How to achieve a bas ...
- Asp.net Core基于MVC框架实现PostgreSQL操作
简单介绍 Asp.net Core最大的价值在于跨平台.跨平台.跨平台.重要的事情说三遍.但是目前毕竟是在开发初期,虽然推出了1.0.0 正式版,但是其实好多功能还没有完善.比方说编译时的一些文件编码 ...
- Asp.Net Core基于Cookie实现同域单点登录(SSO)
在同一个域名下有很多子系统 如:a.giant.com b.giant.com c.giant.com等 但是这些系统都是giant.com这个子域. 这样的情况就可以在不引用其它框架的情况下, ...
- Asp.Net Core Docker镜像更新系统从wheezy改为stretch
之前写过一个在Asp.Net Core里调用System.Drawing.Common绘图的DEMO,部署到Docker里运行,需要更新Asp.Net Core镜像的操作系统. https://www ...
随机推荐
- sql语句字符串包含
select instr('1222','122') from dual//前者包含后者>0 oracle mysql 数据库可中 select charindex('1','12') from ...
- zookeeper扫盲
一.zookeeper概述 a.zookeeper是一个开源的分布式的项目,为分布式业务提供协调服务的apache顶级项目 那什么是分布式的呢,通俗的说就是多个机器可以同时去处理一件事情 b.zook ...
- c++-变量,this指针,全局函数,成员函数,自定义数组类
区分变量属于哪个对象 c++对象管理模型初探 C++类对象中的成员变量和成员函数是分开存储的,C中内存四区仍然有效 C++编译器对普通成员函数的内部处理(隐藏this指针) this指针解决函数形参和 ...
- CSS 盒子模型、RestCSS、浮动、定位
盒子模型 边框:border 左边框:border-left 右边框:border-right 上边框:border-top 下边框:border-bottom 复合样式:border 边框颜色:bo ...
- 使用Navicat Keygen激活(破解)Navicat Premium 12
1.到Navicat官网下载使用版本进行安装,具体操作不再详述.Navcat官网下载链接:http://www.navicat.com.cn/download/navicat-premium : 2. ...
- MySQL集群高可用
目录 MySQL高可用 Galera Cluster Galera Cluster Galera Cluster特点 Galera Cluster 缺点 Galera Cluster工作过程 Gale ...
- C++之new关键字
我们都知道new是用来在程序运行过程中为变量临时分配内存的C++关键字,那它跟C语言中的malloc有什么区别呢,相比之下又为什么推荐使用new呢 c++ throwing() void* opera ...
- Redis入门(四)-Java操作Redis
<Redis入门>系列文章的第四篇,这一节看一下如何用Java版本的redis客户端工具--Jedis来操作redis. Jedis封装了丰富的api来对redis的五种数据类型 stri ...
- Cross-Site Scripting:Reflected 跨站点脚本:获取
- 使用系统定时器SysTick实现精确延时微秒和毫秒函数
SysTick定时器简介 SysTick定时器是存在于系统内核的一个滴答定时器,只要是ARM Cortex-M0/M3/M4/M7内核的MCU都包含这个定时器,它是一个24位的递减定时器,当计数到 0 ...