eShopOnContainers 知多少[3]:Identity microservice
首先感谢晓晨Master和EdisonChou的审稿!也感谢正在阅读的您!
引言
通常,服务所公开的资源和 API 必须仅限受信任的特定用户和客户端访问。那进行 API 级别信任决策的第一步就是身份认证——确定用户身份是否可靠。
在微服务场景中,身份认证通常统一处理。一般有两种实现形式:
基于API 网关中心化认证:要求客户端必须都通过网关访问微服务。(这就要求提供一种安全机制来认证请求是来自于网关。)
基于安全令牌服务(STS)认证:所有的客户端先从STS获取令牌,然后请求时携带令牌完成认证。
而本节所讲的Identity microservice就是使用第二种身份认证方式。
服务简介
Identity microservice 主要用于统一的身份认证和授权,为其他服务提供支撑。
提到认证,大家最熟悉不过的当属Cookie认证了,它也是目前使用最多的认证方式。但Cookie认证也有其局限性:不支持跨域、移动端不友好等。而从当前的架构来看,需要支持移动端、Web端、微服务间的交叉认证授权,所以传统的基于Cookie的本地认证方案就行不通了。我们就需要使用远程认证的方式来提供统一的认证授权机制。
而远程认证方式当属:OAuth2.0和OpenID Connect了。借助OAuth2.0和OpenID Connect即可实现类似下图的认证体系:
而如何实现呢,借助:
- ASP.NET Core Identity
- IdentityServer4
基于Cookie的认证和基于Token的认证的差别如下所示:
架构模式
该微服务作为支撑服务,并没有选择复杂的架构模式,使用了MVC单层架构,使用EF Core ORM框架用于数据持久化,SQL Server数据库。使用Autofac IOC框架替换了默认依赖注入框架。
项目结构如下所示:
核心技术选型:
- MVC单层架构
- EF Core
- ASP.NET Core Identity
- IdentityServer4
- SQL Server 数据库
- Autofac
PS:对ASP.NET Core Identity、IdentityServer4以及OAuth2.0不了解的,请先行阅读文末参考资料补课!!!
下面就着重讲解ASP.NET Core Identity和IdentityServer4在本服务中的使用。
ASP.NET Core Identity && IdentityServer4简介
ASP.NET Core Identity用于构建ASP.NET Core Web应用程序的成员资格系统,包括成员资格,登录和用户数据(包括登录信息、角色和声明)。
ASP.NET Core Identity封装了User、Role、Claim等身份信息,便于我们快速完成登录功能的实现,并且支持第三方登录(Google、Facebook、QQ、Weixin等,支持开箱即用[第三方身份提供商列表]),以及双重验证,同时内置支持Bearer 认证(令牌认证)。
虽然ASP.NET Core Identity已经完成了绝大多数的功能,且支持第三方登录(第三方为其用户颁发令牌),但若要为本地用户颁发令牌,则需要自己实现令牌的颁发和验证逻辑。换句话说,我们需要自行实现OpenId Connect协议。
OpenID Connect 1.0 是基于OAuth 2.0协议之上的简单身份层,它允许客户端根据授权服务器的认证结果最终确认终端用户的身份,以及获取基本的用户信息。
而IdentityServer4就是为ASP.NET Core量身定制的实现了OpenId Connect和OAuth2.0协议的认证授权中间件。IdentityServer4在ASP.NET Core Identity的基础上,提供令牌的颁发验证等。
认证流程简介
在ASP.NET Core中使用的是基于申明(Claim)的认证,而什么是申明(Cliam)呢?
Claim 是关于一个人或组织的某个主题的陈述,比如:一个人的名称,角色,个人喜好,种族,特权,社团,能力等等。它本质上就是一个键值对,是一种非常通用的保存用户信息的方式,可以很容易的将认证和授权分离开来,前者用来表示用户是/不是什么,后者用来表示用户能/不能做什么。在认证阶段我们通过用户信息获取到用户的Claims,而授权便是对这些的Claims的验证,如:是否拥有Admin的角色,姓名是否叫XXX等等。
认证主要与以下几个核心对象打交道:
- Claim(身份信息)
- ClaimsIdentity(身份证)
- ClaimsPrincipal (身份证持有者)
- AuthorizationToken (授权令牌)
- IAuthenticationScheme(认证方案)
- IAuthenticationHandler(与认证方案对应的认证处理器)
- IAuthenticationService (向外提供统一的认证服务接口)
那其认证流程是怎样的呢?
用户打开登录界面,输入用户名密码先行登录,服务端先行校验用户名密码是否有效,有效则返回用户实例(User),这时进入认证准备阶段,根据用户实例携带的身份信息(Claim),创建身份证(ClaimsIdentity),然后将身份证交给身份证持有者(ClaimsPrincipal)持有。接下来进入真正的认证阶段,根据配置的认证方案(IAuthenticationScheme),使用相对应的认证处理器(IAuthenticationHandler)进行认证 。认证成功后发放授权令牌(AuthorizationToken)。该授权令牌包含后续授权阶段需要的全部信息。
授权流程简介
授权就是对于用户身份信息(Claims)的验证,,授权又分以下几种种:
- 基于Role的授权
- 基于Scheme的授权
- 基于Policy的授权
授权主要与以下几个核心对象打交道:
- IAuthorizationRequirement(授权条件)
- IAuthorizationService(授权服务)
- AuthorizationPolicy(授权策略)
- IAuthorizationHandler (授权处理器)
- AuthorizationResult(授权结果)
那授权流程是怎样的呢?
当收到授权请求后,由授权服务(IAuthorizationService)根据资源上指定的授权策略(AuthorizationPolicy)中包含的授权条件(IAuthorizationRequirement),找到相对应的授权处理器(IAuthorizationHandler )来判断授权令牌中包含的身份信息是否满足授权条件,并返回授权结果。
中间件集成
简单了解了下认证和授权流程后,我们来了解Identity microservice是如何集成相关中间件的。
1. 首先是映射自定义扩展的User和Role
// 映射自定义的User,Role
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()//配置使用EF持久化存储
.AddDefaultTokenProviders();//配置默认的TokenProvider用于变更密码和修改email时生成Token
2. 配置IdentityServer服务
// Adds IdentityServer
services.AddIdentityServer(x =>
{
x.IssuerUri = "null";
x.Authentication.CookieLifetime = TimeSpan.FromHours(2);
})
.AddSigningCredential(Certificate.Get())
.AddAspNetIdentity<ApplicationUser>()
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString,
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.MigrationsAssembly(migrationsAssembly);
//Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
});
})
.AddOperationalStore(options =>
{
options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString,
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.MigrationsAssembly(migrationsAssembly);
//Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
});
})
.Services.AddTransient<IProfileService, ProfileService>();
IdentityServer默认直接在内存中存储配置数据(客户端和资源)和操作数据(令牌,代码和和用户的授权信息consents)。这显然在生产环境是不合适的,如果服务所在主机宕机,那么内存中的数据就会丢失,所以有必要持久化到数据库。
其中AddConfigurationStore
和AddOperationalStore
扩展方法就是用来来指定配置数据和操作数据基于EF进行持久化。
3. 添加IdentityServer中间件
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// .....
// Adds IdentityServer
app.UseIdentityServer();
}
4. 预置种子数据
从已知的体系结构来说,我们需要预置Client和Resource:
- Client
public static IEnumerable<Client> GetClients(Dictionary<string,string> clientsUrl)
{
return new List<Client>
{
// SPA OpenId Client Client(Implicit)
new Client
// Xamarin Client(Hybrid)
new Client
// MVC Client(Hybrid)
new Client
// MVC TEST Client(Hybrid)
new Client
// Locations Swagger UI(Implicit)
new Client
// Marketing Swagger UI(Implicit)
new Client
// Basket Swagger UI(Implicit)
new Client
// Ordering Swagger UI(Implicit)
new Client
// Mobile Shopping Aggregattor Swagger UI(Implicit)
new Client
// Web Shopping Aggregattor Swagger UI(Implicit)
new Client
};
}
- IdentityResources
public static IEnumerable<IdentityResource> GetResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile()
};
}
- ApiResources
public static IEnumerable<ApiResource> GetApis()
{
return new List<ApiResource>
{
new ApiResource("orders", "Orders Service"),
new ApiResource("basket", "Basket Service"),
new ApiResource("marketing", "Marketing Service"),
new ApiResource("locations", "Locations Service"),
new ApiResource("mobileshoppingagg", "Mobile Shopping Aggregator"),
new ApiResource("webshoppingagg", "Web Shopping Aggregator"),
new ApiResource("orders.signalrhub", "Ordering Signalr Hub")
};
}
5. 迁移数据库上下文
下面就把提前在代码预置的种子数据迁移到数据库中,我们如何做呢?IdentityServer为配置数据和操作数据分别定义了DBContext
用于持久化,配置数据对应ConfigurationDbContext
,操作数据对应PersistedGrantDbContext
。代码如下所示:
public static void Main(string[] args)
{
BuildWebHost(args)
.MigrateDbContext<PersistedGrantDbContext>((_, __) => { })//迁移操作数据库
.MigrateDbContext<ApplicationDbContext>((context, services) =>
{
var env = services.GetService<IHostingEnvironment>();
var logger = services.GetService<ILogger<ApplicationDbContextSeed>>();
var settings = services.GetService<IOptions<AppSettings>>();
new ApplicationDbContextSeed()
.SeedAsync(context, env, logger, settings)
.Wait();
})//迁移用户数据库
.MigrateDbContext<ConfigurationDbContext>((context,services)=>
{
var configuration = services.GetService<IConfiguration>();
new ConfigurationDbContextSeed()
.SeedAsync(context, configuration)
.Wait();
})//迁移配置数据库
.Run();
}
至此,本服务的核心代码已解析完毕。
最终的生成的数据库如下图所示:
最后
本文从业务和技术上对本服务进行剖析,介绍了其技术选型,并紧接着简要介绍了ASP.NET Core Identity和IdentityServer4,最后分析源码,一步步揭开其神秘的面纱。至于客户端和其他微服务服务如何使用Identity microservice进行认证和授权,我将在后续文章再行讲解。
如果对ASP.NET Core Idenity和IdentityServer4不太了解,建议大家博客园阅读雨夜朦胧、晓晨Master和Savorboard
的博客进行系统学习后,再重读本文,相信你对Identity microservice的实现机制豁然开朗。
参考资料
雨夜朦胧 -- ASP.NET Core 认证与授权:初识认证/授权
Savorboard -- ASP.NET Core 之 Identity 入门(一)
晓晨Master -- IdentityServer(14)- 通过EntityFramework Core持久化配置和操作数据
IdentityServer4 知多少
OAuth2.0 知多少
.NET Core微服务之基于Ocelot+IdentityServer实现统一验证与授权
eShopOnContainers 知多少[3]:Identity microservice的更多相关文章
- eShopOnContainers 知多少[7]:Basket microservice
引言 Basket microservice(购物车微服务)主要用于处理购物车的业务逻辑,包括: 购物车商品的CRUD 订阅商品价格更新事件,进行购物车商品同步处理 购物车结算事件发布 订阅订单成功创 ...
- eShopOnContainers 知多少[1]:总体概览
引言 在微服务大行其道的今天,Java阵营的Spring Boot.Spring Cloud.Dubbo微服务框架可谓是风水水起,也不得不感慨Java的生态圈的火爆.反观国内.NET阵营,微服务却不愠 ...
- eShopOnContainers 知多少[2]:Run起来
环境准备 Win10(开启Hyper-V) .NET Core SDK Docker for Windows VS2017 or VS Code Git SQL Server Management S ...
- eShopOnContainers 知多少[8]:Ordering microservice
1. 引言 Ordering microservice(订单微服务)就是处理订单的了,它与前面讲到的几个微服务相比要复杂的多.主要涉及以下业务逻辑: 订单的创建.取消.支付.发货 库存的扣减 2. 架 ...
- eShopOnContainers 知多少[4]:Catalog microservice
引言 Catalog microservice(目录微服务)维护着所有产品信息,包括库存.价格.所以该微服务的核心业务为: 产品信息的维护 库存的更新 价格的维护 架构模式 如上图所示,本微服务采用简 ...
- eShopOnContainers 知多少[10]:部署到 K8S | AKS
1. 引言 断断续续,感觉这个系列又要半途而废了.趁着假期,赶紧再更一篇,介绍下如何将eShopOnContainers部署到K8S上,进而实现大家常说的微服务上云. 2. 先了解下 Helm 读过我 ...
- eShopOnContainers 知多少[9]:Ocelot gateways
引言 客户端与微服务的通信问题永远是一个绕不开的问题,对于小型微服务应用,客户端与微服务可以使用直连的方式进行通信,但对于对于大型的微服务应用我们将不得不面对以下问题: 如何降低客户端到后台的请求数量 ...
- eShopOnContainers 知多少[5]:EventBus With RabbitMQ
1. 引言 事件总线这个概念对你来说可能很陌生,但提到观察者(发布-订阅)模式,你也许就很熟悉.事件总线是对发布-订阅模式的一种实现.它是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需 ...
- eShopOnContainers 知多少[6]:持久化事件日志
1. 引言 事件总线解决了微服务间如何基于集成事件进行异步通信的问题.然而只有事件总线正常运行,微服务之间基于事件的通信才得以运转. 而现实情况是,总有这样或那样的问题,导致事件总线不稳定或不可用,比 ...
随机推荐
- JS 实现无缝滚动动画原理(初学者入)
这段时间在教培训班的学生使用原生javascript实现无缝滚动的动画案例,做了这个原理演示的动画,分享给自学JS的朋友!博主希望对你们有帮助! 在讲解之前先看一下demo: demo:https:/ ...
- SSM-SpringMVC-07:SpringMVC中处理器映射器
------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- BeanNameUrlHandlerMapping和SimpleUrlHandlerMapping BeanN ...
- .Net Core微服务系列--理论篇
微服务的由来 微服务最早由Martin Fowler与James Lewis于2014年共同提出来的,但是微服务也不是一个全新的概念,它是由一系列在实践中获得成功并流行起来的概念中总结出来的一种模式, ...
- Linux时间子系统之二:Alarm Timer
一.前言 严格来讲Alarm Timer也算POSIX Timer一部分,包含两种类型CLOCK_REALTIME_ALARM和CLOCK_BOOTTIME_ALARM.分别是在CLOCK_REALT ...
- 使用Maven+Nexus+Jenkins+Svn+Tomcat+Sonar搭建持续集成环境
前言 但凡一个略有规模的项目都需要一个持续集成环境的支撑,为什么需要持续集成环境,我们来看一个例子.假如一个项目,由A.B两位程序员来协作开发,A负责前端模块,B负责后端模块,前端依赖后端.A和B都习 ...
- Ambiguous mapping found
If you have a single default method (without explicit path mapping), then all requests without a mor ...
- 你不知道的JavaScript--Item28 垃圾回收机制与内存管理
1.垃圾回收机制-GC Javascript具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存. 原理:垃圾收集器会定期(周期性 ...
- Centos7 编译安装Nginx 教程
相信经过上篇博文的学习,聪明的你已经学会了如何在Centos7 上通过yum 方式安装Nginx ,但是有时候有些场景或者特俗情况下,我们往往需要通过编译源码方式安装,以便于更灵活地定制我们的Ngin ...
- 【codeforces 718 C&D】C. Sasha and Array&D. Andrew and Chemistry
C. Sasha and Array 题目大意&题目链接: http://codeforces.com/problemset/problem/718/C 长度为n的正整数数列,有m次操作,$o ...
- bzoj 2428 均分数据 模拟退火
模拟退火 按照自己的思路打了,结果WA,发现退火最关键的就是初温,降温,和修改次数, 这个题还在外层带了一个循环,骚气 #include<cstdio> #include<iostr ...