引言

通常,服务所公开的资源和 API 必须仅限受信任的特定用户和客户端访问。那进行 API 级别信任决策的第一步就是身份认证——确定用户身份是否可靠。

在微服务场景中,身份认证通常统一处理。一般有两种实现形式:

  1. 基于API 网关中心化认证:要求客户端必须都通过网关访问微服务。(这就要求提供一种安全机制来认证请求是来自于网关。)

  2. 基于安全令牌服务(STS)认证:所有的客户端先从STS获取令牌,然后请求时携带令牌完成认证。

Identity Service就是使用第二种身份认证方式。

服务简介

Identity microservice 主要用于统一的身份认证和授权,为其他服务提供支撑。

提到认证,大家最熟悉不过的当属Cookie认证了,它也是目前使用最多的认证方式。但Cookie认证也有其局限性:不支持跨域、移动端不友好等。而从当前的架构来看,需要支持移动端、Web端、微服务间的交叉认证授权,所以传统的基于Cookie的本地认证方案就行不通了。我们就需要使用远程认证的方式来提供统一的认证授权机制。
而远程认证方式当属:OAuth2.0和OpenID Connect了。借助OAuth2.0和OpenID Connect即可实现类似下图的认证体系:

而如何实现呢,借助:

  1. ASP.NET Core Identity
  2. IdentityServer4

基于Cookie的认证和基于Token的认证的差别如下所示:

架构模式

从目录结构可以看出它是一套MVC单层架构的网站。我们可以单独进行运行和调试,也可以把它放进自己的项目中。

主要依赖:

1、HealthCheck 健康检查

2、WebHost

3、Entity Framework

4、Autofac

5、IdentityServer4

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的基础上,提供令牌的颁发验证等。

相关知识:

OAuth 2.0 简介

OpenID Connect 简介

Identity Server 4

认证流程

在ASP.NET Core中使用的是基于申明(Claim)的认证,而什么是申明(Cliam)呢?

Claim 是关于一个人或组织的某个主题的陈述,比如:一个人的名称,角色,个人喜好,种族,特权,社团,能力等等。它本质上就是一个键值对,是一种非常通用的保存用户信息的方式,可以很容易的将认证和授权分离开来,前者用来表示用户是/不是什么,后者用来表示用户能/不能做什么。在认证阶段我们通过用户信息获取到用户的Claims,而授权便是对这些的Claims的验证,如:是否拥有Admin的角色,姓名是否叫XXX等等。

认证主要与以下几个核心对象打交道:

  1. Claim(身份信息)
  2. ClaimsIdentity(身份证)
  3. ClaimsPrincipal (身份证持有者)
  4. AuthorizationToken (授权令牌)
  5. IAuthenticationScheme(认证方案)
  6. IAuthenticationHandler(与认证方案对应的认证处理器)
  7. IAuthenticationService (向外提供统一的认证服务接口)

那其认证流程是怎样的呢?

1、用户打开登录界面,输入用户名密码先行登录,服务端先行校验用户名密码是否有效,有效则返回用户实例(User)。

2、这时进入认证准备阶段,根据用户实例携带的身份信息(Claim),创建身份证(ClaimsIdentity),然后将身份证交给身份证持有者(ClaimsPrincipal)持有。

3、接下来进入真正的认证阶段,根据配置的认证方案(IAuthenticationScheme),使用相对应的认证处理器(IAuthenticationHandler)进行认证 。认证成功后发放授权令牌(AuthorizationToken)。该授权令牌包含后续授权阶段需要的全部信息。

授权流程

授权就是对于用户身份信息(Claims)的验证,,授权又分以下几种种:

  1. 基于Role的授权
  2. 基于Scheme的授权
  3. 基于Policy的授权

授权主要与以下几个核心对象打交道:

  1. IAuthorizationRequirement(授权条件)
  2. IAuthorizationService(授权服务)
  3. AuthorizationPolicy(授权策略)
  4. IAuthorizationHandler (授权处理器)
  5. 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>();

使用AddConfigurationStoreAddOperationalStore扩展方法就是用来来指定配置数据和操作数据基于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的更多相关文章

  1. eShopOnContainers 看微服务④:Catalog Service

    服务简介 Catalog service(目录服务)维护着所有产品信息,包括库存.价格.所以该微服务的核心业务为: 产品信息的维护 库存的更新 价格的维护 架构模式 先看代码结构(下图). 主要依赖: ...

  2. [转]eShopOnContainers 看微服务 ①:总体概览

    本文转自:https://www.cnblogs.com/tianyamoon/p/10081177.html 一.简介 eShopOnContainers是一个简化版的基于.NET Core和Doc ...

  3. eShopOnContainers 看微服务⑤:消息通信

    1.消息通信 传统的单体应用,组件间的调用都是使用代码级的方法函数.比如用户登录自动签到,增加积分.我们可以在登录函数调用积分模块的某个函数,为了解耦我们使用以来注入并放弃new Class()这种方 ...

  4. eShopOnContainers 看微服务 ①:总体概览

    一.简介 eShopOnContainers是一个简化版的基于.NET Core和Docker等技术开发的面向微服务架构的参考应用. 该参考应用是一个简化版的在线商城/电子商务微服务参考示例应用. 其 ...

  5. eShopOnContainers 看微服务 ②:配置 启动

    一.什么是docker Docker 是一个开源项目,通过把应用程序打包为可移植的.自给自足的容器(可以运行在云端或本地)的方式,实现应用程序的自动化部署. 使用 Docker 的时候,需要创建一个应 ...

  6. 浅谈服务治理、微服务与Service Mesh(三) Service Mesh与Serverless

    作为本系列文章的第三篇(前两篇<浅谈服务治理.微服务与Service Mesh(一)Dubbo的前世今生>,<浅谈服务治理.微服务与Service Mesh(二) Spring Cl ...

  7. 微服务之Service Fabric 系列 (一):概览、环境安装

    参考 微软官方文档  service fabric 百家号   大话微服务架构之微服务框架微软ServiceFabric正式开源 一.概述 1.概念 Azure Service Fabric 是一款分 ...

  8. 【Azure 微服务】Service Fabric中微服务在升级时,遇见Warning - System.Collections.Generic.KeyNotFoundException 服务无法正常运行

    问题描述 使用.Net Framework 4.5.2为架构的Service Fabric微服务应用,在升级后发布到Azure Fabric中,服务无法运行.通过Service Fabric Expl ...

  9. 【Azure 微服务】Service Fabric, 使用ARM Template方式来更新SF集群的证书(Renew SF Certificate)

    问题描述 因证书过期导致Service Fabric集群挂掉(升级无法完成,节点不可用)一文中,描述了因为证书过期而导致了SF集群不可用,并且通过命令dd-AzServiceFabricCluster ...

随机推荐

  1. 16.python-I/O模型

    一.事件驱动模型1.什么是事件驱动模型:本身是一种编程范式,这里程序的执行是由外部事件来决定的.它的特点是包含一个事件循环,当外部事件发生时使用回调机制来触发相应的处理.常见的编程范式(单线程)同步以 ...

  2. Java面试题2

    类加载机制 Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的加载机制. 类从被加载到虚拟机内存中开 ...

  3. HttpClient设置连接超时时间

    https://www.cnblogs.com/winner-0715/p/7087591.html 使用HttpClient,一般都需要设置连接超时时间和获取数据超时时间.这两个参数很重要,目的是为 ...

  4. Acrobat.CAcroPDDoc open 无法找到指定文件

    pdfDoc = (Acrobat.CAcroPDDoc)Microsoft.VisualBasic.Interaction.CreateObject("AcroExch.PDDoc&quo ...

  5. 记SCOI2019

    离精英体验营结束已两周的,要哭的要笑的现在也一定释怀了.是时候冷静分析一下这次的考试了.时间序虽然有流水账的嫌疑,但这毕竟是OI界的流行风气. day0 早上坐学校包的商务车去了电子科技大学.走在来过 ...

  6. C++ 基于凸包的Delaunay三角网生成算法

    Delaunay三角网,写了用半天,调试BUG用了2天……醉了. 基本思路比较简单,但效率并不是很快. 1. 先生成一个凸包: 2. 只考虑凸包上的点,将凸包环切,生成一个三角网,暂时不考虑Delau ...

  7. req和resp常用的方法

    req:  1. setAttribute()在Request域中存储数据 2. setCharacterEncoding()设置请求参数的编码方式,只对post请求有效 3. getMethod() ...

  8. 使用s3fs-fuse 挂载minio s3 对象存储

    minio 是一个aws s3 兼容的对象存储系统,我们可以通过s3fs 进行数据桶的挂载,这样可以做好多方便的事情 环境准备 使用docker-compose 运行 minio docker-com ...

  9. TR069网管测试华为ITMS平台(内部测试使用)

    声明:本篇华为ITMS平台仅用于学习和测试使用,如果需要商用,请购买正版软件! 原创作品,转载请注明出处,严禁非法转载或者用于商业目的! email:40879506@qq.com 一. 准备软件 V ...

  10. 常用git的命令

    常用git的命令 详解git fetch与git pull的区别 Git放弃本地所有修改,强制更新: git fetch --all git reset --hard origin/master 说明 ...