eShopOnContainers 看微服务③:Identity Service
引言
通常,服务所公开的资源和 API 必须仅限受信任的特定用户和客户端访问。那进行 API 级别信任决策的第一步就是身份认证——确定用户身份是否可靠。
在微服务场景中,身份认证通常统一处理。一般有两种实现形式:
基于API 网关中心化认证:要求客户端必须都通过网关访问微服务。(这就要求提供一种安全机制来认证请求是来自于网关。)
基于安全令牌服务(STS)认证:所有的客户端先从STS获取令牌,然后请求时携带令牌完成认证。
Identity Service就是使用第二种身份认证方式。
服务简介
Identity microservice 主要用于统一的身份认证和授权,为其他服务提供支撑。
提到认证,大家最熟悉不过的当属Cookie认证了,它也是目前使用最多的认证方式。但Cookie认证也有其局限性:不支持跨域、移动端不友好等。而从当前的架构来看,需要支持移动端、Web端、微服务间的交叉认证授权,所以传统的基于Cookie的本地认证方案就行不通了。我们就需要使用远程认证的方式来提供统一的认证授权机制。
而远程认证方式当属:OAuth2.0和OpenID Connect了。借助OAuth2.0和OpenID Connect即可实现类似下图的认证体系:
而如何实现呢,借助:
- ASP.NET Core Identity
- IdentityServer4
基于Cookie的认证和基于Token的认证的差别如下所示:
架构模式
从目录结构可以看出它是一套MVC单层架构的网站。我们可以单独进行运行和调试,也可以把它放进自己的项目中。
主要依赖:
1、HealthCheck 健康检查
2、WebHost
3、Entity Framework
4、Autofac
6、其中IdentityServer4.AspNetIdentity又用到了ASP.NET Core Identity
启动流程
Program.cs
Main函数:
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)//初始化identity server种子数据
.Wait();
}).Run();
}
BuildWebHost函数:
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseKestrel()//使用Kestrel作为的web服务器
.UseHealthChecks("/hc")//健康检查
.UseContentRoot(Directory.GetCurrentDirectory())//将当前项目的根目录作为ContentRoot目录
.UseIISIntegration()//使用IIS
.UseStartup<Startup>()//使用startup类
.ConfigureAppConfiguration((builderContext, config) =>
{
var builtConfig = config.Build(); var configurationBuilder = new ConfigurationBuilder(); if (Convert.ToBoolean(builtConfig["UseVault"]))
{
configurationBuilder.AddAzureKeyVault(
$"https://{builtConfig["Vault:Name"]}.vault.azure.net/",
builtConfig["Vault:ClientId"],
builtConfig["Vault:ClientSecret"]);
} configurationBuilder.AddEnvironmentVariables(); config.AddConfiguration(configurationBuilder.Build());
})
.ConfigureLogging((hostingContext, builder) =>
{
builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
builder.AddConsole();
builder.AddDebug();
})
.UseApplicationInsights()
.Build();
其中有一个UseHealthChecks,这是一个对项目健康的检查。
健康检查,其实这个名称已经很明确了,它是检查你的应用程序是否健康运行的一种方式。随着当前各类项目越来越多的应用程序正在转向微
服务式架构,健康检查就变得尤为关键。虽然微服务体系结构具有许多好处,但其中一个缺点就是为了确保所有这些服务都正常运行的操作开销
更高。你不在是监视一个庞大的整体项目的健康状况,而是需要监控许多不同服务的状态,甚至这些服务通常只负责一件事情。健康检查(Heatlh
Checks)通常与一些服务发现工具结合使用,如Consul ,来监控您的微服务器,来观测您的服务是否健康运行。 健康检查有很多种不同的方法,但最常见的方法是将HTTP端点暴露给专门用于健康检查的应用程序。一般来说,如果一切情况都很好,你的服
务将返回200的状态码,然而任何非200的代码则意味着出现问题。例如,如果发生错误,你可能会返回500以及一些出错的JSON信息。
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
} public IConfiguration Configuration { get; } /// <summary>
/// 来配置我们应用程序中的各种服务,
/// 它通过参数获取一个IServiceCollection 实例 。
/// </summary>
/// <param name="services"></param>
/// <returns>IServiceProvider</returns>
public IServiceProvider ConfigureServices(IServiceCollection services)
{
RegisterAppInsights(services); // Add framework services.
//注册EF使用的DbContext
services.AddDbContext<ApplicationDbContext>(options =>
//使用mysql
options.UseSqlServer(Configuration["ConnectionString"],//数据库连接字符串
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
//Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
sqlOptions.EnableRetryOnFailure(maxRetryCount: , maxRetryDelay: TimeSpan.FromSeconds(), errorNumbersToAdd: null);
}));
//使用Microsoft asp.net identity系统
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()//使用EF
.AddDefaultTokenProviders(); services.Configure<AppSettings>(Configuration); services.AddMvc();//使用MVC //对集群对配置
if (Configuration.GetValue<string>("IsClusterEnv") == bool.TrueString)
{
services.AddDataProtection(opts =>
{
//在集群环境中,如果不被具体的硬件机器环境所限制,就要排除运行机器的一些差异,
//就需要抽象出来一些特定的标识,来标识应用程序本身并且使用该标识来区分不同的应用程序。
//这个时候,我们可以指定ApplicationDiscriminator。
opts.ApplicationDiscriminator = "eshop.identity";
//集群环境下同一应用程序他们需要设定为相同的值(ApplicationName or ApplicationDiscriminator)。
})
.PersistKeysToRedis(ConnectionMultiplexer.Connect(Configuration["DPConnectionString"]), "DataProtection-Keys");
} //注册健康检查
services.AddHealthChecks(checks =>
{
var minutes = ;
if (int.TryParse(Configuration["HealthCheck:Timeout"], out var minutesParsed))
{
minutes = minutesParsed;
}
//数据库健康检查
checks.AddSqlCheck("Identity_Db", Configuration["ConnectionString"], TimeSpan.FromMinutes(minutes));
}); //注册登陆注册的应用服务(ApplicationService)
services.AddTransient<ILoginService<ApplicationUser>, EFLoginService>();
services.AddTransient<IRedirectService, RedirectService>(); var connectionString = Configuration["ConnectionString"];
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name; // 注册 IdentityServer
services.AddIdentityServer(x =>
{
x.IssuerUri = "null";
x.Authentication.CookieLifetime = TimeSpan.FromHours();//cookie有效期两小时
})
.AddSigningCredential(Certificate.Get())//设置加密证书
//配置IdentityServer。IUserClaimsPrincipalFactory、IResourceOwnerPasswordValidator和IProfileService的网络标识实现。
.AddAspNetIdentity<ApplicationUser>()
.AddConfigurationStore(options => //使用IdentityServer配置IClientStore、IResourceStore和ICorsPolicyService的EF实现。
{
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: , maxRetryDelay: TimeSpan.FromSeconds(), errorNumbersToAdd: null);
});
})
//注册IPersistedGrantStore的实现,用于存储AuthorizationCode和RefreshToken等等,默认实现是存储在内存中,
//如果服务重启那么这些数据就会被清空了,因此实现IPersistedGrantStore将这些数据写入到数据库中
.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: , maxRetryDelay: TimeSpan.FromSeconds(), errorNumbersToAdd: null);
});
})
//注册IProfileService,该接口允许IdentityServer连接到用户。
.Services.AddTransient<IProfileService, ProfileService>(); //使用autofac
var container = new ContainerBuilder();
container.Populate(services); return new AutofacServiceProvider(container.Build());
} // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
//配置日志
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
loggerFactory.AddAzureWebAppDiagnostics();
loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
} var pathBase = Configuration["PATH_BASE"];
if (!string.IsNullOrEmpty(pathBase))
{
loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'");
app.UsePathBase(pathBase);
} #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
app.Map("/liveness", lapp => lapp.Run(async ctx => ctx.Response.StatusCode = ));
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously //使用StaticFiles,等于启动了静态文件服务器功能。wwwroot 就是靠这个中间件读取的。
//也可以不使用wwwroot,并且制定自己对目录。传入参数就可以了。
app.UseStaticFiles(); // Make work identity server redirections in Edge and lastest versions of browers. WARN: Not valid in a production environment.
app.Use(async (context, next) =>
{
context.Response.Headers.Add("Content-Security-Policy", "script-src 'unsafe-inline'");
await next();
}); //处理代理服务器和负载均衡对解决方案,
//详情 https://docs.microsoft.com/zh-cn/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-2.1
app.UseForwardedHeaders();
//使用IdentityServer4
app.UseIdentityServer(); //配置MVC
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
} /// <summary>
/// 应用监控
/// </summary>
/// <param name="services"></param>
private void RegisterAppInsights(IServiceCollection services)
{
services.AddApplicationInsightsTelemetry(Configuration);
var orchestratorType = Configuration.GetValue<string>("OrchestratorType"); if (orchestratorType?.ToUpper() == "K8S")
{
// Enable K8s telemetry initializer
services.EnableKubernetes();
}
if (orchestratorType?.ToUpper() == "SF")
{
// Enable SF telemetry initializer
services.AddSingleton<ITelemetryInitializer>((serviceProvider) =>
new FabricTelemetryInitializer());
}
}
}
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 (向外提供统一的认证服务接口)
那其认证流程是怎样的呢?
1、用户打开登录界面,输入用户名密码先行登录,服务端先行校验用户名密码是否有效,有效则返回用户实例(User)。
2、这时进入认证准备阶段,根据用户实例携带的身份信息(Claim),创建身份证(ClaimsIdentity),然后将身份证交给身份证持有者(ClaimsPrincipal)持有。
3、接下来进入真正的认证阶段,根据配置的认证方案(IAuthenticationScheme),使用相对应的认证处理器(IAuthenticationHandler)进行认证 。认证成功后发放授权令牌(AuthorizationToken)。该授权令牌包含后续授权阶段需要的全部信息。
授权流程
授权就是对于用户身份信息(Claims)的验证,,授权又分以下几种种:
- 基于Role的授权
- 基于Scheme的授权
- 基于Policy的授权
授权主要与以下几个核心对象打交道:
- IAuthorizationRequirement(授权条件)
- IAuthorizationService(授权服务)
- AuthorizationPolicy(授权策略)
- IAuthorizationHandler (授权处理器)
- AuthorizationResult(授权结果)
那授权流程是怎样的呢?
当收到授权请求后,由授权服务(IAuthorizationService)根据资源上指定的授权策略(AuthorizationPolicy)中包含的授权条件(IAuthorizationRequirement),找到相对应的授权处理器(IAuthorizationHandler )来判断授权令牌中包含的身份信息是否满足授权条件,并返回授权结果。
中间件集成
回过头来我们再来刷一遍startup代码中是怎么集成进Identity service的。
1. 首先是映射自定义扩展的User和Role
// 映射自定义的User,Role
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()//配置使用EF持久化存储
.AddDefaultTokenProviders();//配置默认的TokenProvider用于变更密码和修改email时生成Token
2. 配置IdentityServer服务
services.AddIdentityServer(x =>
{
...
})
.AddSigningCredential(Certificate.Get())
.AddAspNetIdentity<ApplicationUser>()
.AddConfigurationStore(options =>
{
...
})
.AddOperationalStore(options =>
{
...
})
.Services.AddTransient<IProfileService, ProfileService>();
使用AddConfigurationStore
和AddOperationalStore
扩展方法就是用来来指定配置数据和操作数据基于EF进行持久化。
3. 添加IdentityServer中间件
app.UseIdentityServer();
4. 预置种子数据
需要预置Client和Resource写在Config.cs文件中,他们又是中main函数中被MigrateDbContext使用的。
- GetClients
public static IEnumerable<Client> GetClients(Dictionary<string,string> clientsUrl)
{
return new List<Client>
{
//通过不同对ClientId设置不同客户端参数
new Client
... ... new Client
};
}
- IdentityResources
身份资源是用户ID、姓名或电子邮件地址等数据
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
。详细看main函数。
这篇文章使用了园子里『___知多少』文章对不少内容,表示感谢,原文链接eShopOnContainers 知多少[3]:Identity microservice。
eShopOnContainers 看微服务③:Identity Service的更多相关文章
- eShopOnContainers 看微服务④:Catalog Service
服务简介 Catalog service(目录服务)维护着所有产品信息,包括库存.价格.所以该微服务的核心业务为: 产品信息的维护 库存的更新 价格的维护 架构模式 先看代码结构(下图). 主要依赖: ...
- [转]eShopOnContainers 看微服务 ①:总体概览
本文转自:https://www.cnblogs.com/tianyamoon/p/10081177.html 一.简介 eShopOnContainers是一个简化版的基于.NET Core和Doc ...
- eShopOnContainers 看微服务⑤:消息通信
1.消息通信 传统的单体应用,组件间的调用都是使用代码级的方法函数.比如用户登录自动签到,增加积分.我们可以在登录函数调用积分模块的某个函数,为了解耦我们使用以来注入并放弃new Class()这种方 ...
- eShopOnContainers 看微服务 ①:总体概览
一.简介 eShopOnContainers是一个简化版的基于.NET Core和Docker等技术开发的面向微服务架构的参考应用. 该参考应用是一个简化版的在线商城/电子商务微服务参考示例应用. 其 ...
- eShopOnContainers 看微服务 ②:配置 启动
一.什么是docker Docker 是一个开源项目,通过把应用程序打包为可移植的.自给自足的容器(可以运行在云端或本地)的方式,实现应用程序的自动化部署. 使用 Docker 的时候,需要创建一个应 ...
- 浅谈服务治理、微服务与Service Mesh(三) Service Mesh与Serverless
作为本系列文章的第三篇(前两篇<浅谈服务治理.微服务与Service Mesh(一)Dubbo的前世今生>,<浅谈服务治理.微服务与Service Mesh(二) Spring Cl ...
- 微服务之Service Fabric 系列 (一):概览、环境安装
参考 微软官方文档 service fabric 百家号 大话微服务架构之微服务框架微软ServiceFabric正式开源 一.概述 1.概念 Azure Service Fabric 是一款分 ...
- 【Azure 微服务】Service Fabric中微服务在升级时,遇见Warning - System.Collections.Generic.KeyNotFoundException 服务无法正常运行
问题描述 使用.Net Framework 4.5.2为架构的Service Fabric微服务应用,在升级后发布到Azure Fabric中,服务无法运行.通过Service Fabric Expl ...
- 【Azure 微服务】Service Fabric, 使用ARM Template方式来更新SF集群的证书(Renew SF Certificate)
问题描述 因证书过期导致Service Fabric集群挂掉(升级无法完成,节点不可用)一文中,描述了因为证书过期而导致了SF集群不可用,并且通过命令dd-AzServiceFabricCluster ...
随机推荐
- Mac os 下brew的安装与使用—— Homebrew
1.简介 brew 全称Homebrew 是Mac OSX上的软件包管理工具,相当于linux下的apt-get. 2.安装 2.1安装ruby工具 2.1.1 ruby简介 2.1.2 检查rub ...
- [Java Web学习]junit.framework.AssertionFailedError: No tests found in {Class}
No tests found in com.XXXXX.XXX.inboundPrepService.bizLogic.prepDeterminationEngine.workers.Determin ...
- poj 3347
#include <cstring> #include <iostream> #include <cstdlib> #include <iomanip> ...
- python实现linux下文件遍历
import os def getAllFile(*names): if len(names) == 0: return "" else: allList = [] for nam ...
- 使用DHCP动态管理主机地址
- Java中的锁分类与使用
1. Java锁的种类 在笔者面试过程时,经常会被问到各种各样的锁,如乐观锁.读写锁等等,非常繁多,在此做一个总结.介绍的内容如下: 乐观锁/悲观锁 独享锁/共享锁 互斥锁/读写锁 可重入锁 公平锁/ ...
- Jmeter 传 PUT 请求方式
最近用 Jmeter 发送 PUT 请求,踩了个坑,现记录如下: 难点在在于 body 内有一大串 json 形式的内容 1.PUT 请求的 body 内,直接将 json串传 form-data 形 ...
- 第6章 Hyperledger Fabric模型
This section outlines the key design features woven into Hyperledger Fabric that fulfill its promise ...
- Session小结
session小结1.session是什么Session是服务器为每个访问这个服务器的客户端用户创建的一个容器.这个容器中存储的数据能够在多个request之间实现共享.而且,这个容器只属于当前这个用 ...
- ionic3使用@angular/http 访问nodejs(koa2框架)服务不能返回数据
cordova的http插件不能使用在browser上,所以当需要在browser上浏览时,需要使用@angular/http 里的方法来访问nodejs服务. 如果出现服务端能够接收请求并相应,而客 ...