eShopOnContainers 看微服務③:Identity Service


引言

通常,服務所公開的資源和 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函數:

 1 public static void Main(string[] args)
 2         {
 3             BuildWebHost(args)
 4                 .MigrateDbContext<PersistedGrantDbContext>((_, __) => { })
 5                 .MigrateDbContext<ApplicationDbContext>((context, services) =>
 6                 {
 7                     var env = services.GetService<IHostingEnvironment>();
 8                     var logger = services.GetService<ILogger<ApplicationDbContextSeed>>();
 9                     var settings = services.GetService<IOptions<AppSettings>>();
10 
11                     new ApplicationDbContextSeed()
12                         .SeedAsync(context, env, logger, settings)//初始化默認登錄用戶種子數據
13                         .Wait();
14                 })
15                 .MigrateDbContext<ConfigurationDbContext>((context, services) =>
16                 {
17                     var configuration = services.GetService<IConfiguration>();
18 
19                     new ConfigurationDbContextSeed()
20                         .SeedAsync(context, configuration)//初始化identity server種子數據
21                         .Wait();
22                 }).Run();
23         }
View Code
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

  1     public class Startup
  2     {
  3         public Startup(IConfiguration configuration)
  4         {
  5             Configuration = configuration;
  6         }
  7 
  8         public IConfiguration Configuration { get; }
  9 
 10         /// <summary>
 11         /// 來配置我們應用程序中的各種服務,
 12         /// 它通過參數獲取一個IServiceCollection 實例 。
 13         /// </summary>
 14         /// <param name="services"></param>
 15         /// <returns>IServiceProvider</returns>
 16         public IServiceProvider ConfigureServices(IServiceCollection services)
 17         {
 18             RegisterAppInsights(services);
 19 
 20             // Add framework services.
 21             //注冊EF使用的DbContext
 22             services.AddDbContext<ApplicationDbContext>(options =>
 23             //使用mysql
 24              options.UseSqlServer(Configuration["ConnectionString"],//數據庫連接字符串
 25                                      sqlServerOptionsAction: sqlOptions =>
 26                                      {
 27                                          sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
 28                                          //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency 
 29                                          sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
 30                                      }));
 31             //使用Microsoft asp.net identity系統
 32             services.AddIdentity<ApplicationUser, IdentityRole>()
 33                 .AddEntityFrameworkStores<ApplicationDbContext>()//使用EF
 34                 .AddDefaultTokenProviders();
 35 
 36             services.Configure<AppSettings>(Configuration);
 37 
 38             services.AddMvc();//使用MVC
 39 
 40             //對集群對配置
 41             if (Configuration.GetValue<string>("IsClusterEnv") == bool.TrueString)
 42             {
 43                 services.AddDataProtection(opts =>
 44                 {
 45                     //在集群環境中,如果不被具體的硬件機器環境所限制,就要排除運行機器的一些差異,
 46                     //就需要抽象出來一些特定的標識,來標識應用程序本身並且使用該標識來區分不同的應用程序。
 47                     //這個時候,我們可以指定ApplicationDiscriminator。
 48                     opts.ApplicationDiscriminator = "eshop.identity";
 49                     //集群環境下同一應用程序他們需要設定為相同的值(ApplicationName or ApplicationDiscriminator)。
 50                 })
 51                 .PersistKeysToRedis(ConnectionMultiplexer.Connect(Configuration["DPConnectionString"]), "DataProtection-Keys");
 52             }
 53 
 54             //注冊健康檢查
 55             services.AddHealthChecks(checks =>
 56             {
 57                 var minutes = 1;
 58                 if (int.TryParse(Configuration["HealthCheck:Timeout"], out var minutesParsed))
 59                 {
 60                     minutes = minutesParsed;
 61                 }
 62                 //數據庫健康檢查
 63                 checks.AddSqlCheck("Identity_Db", Configuration["ConnectionString"], TimeSpan.FromMinutes(minutes));
 64             });
 65 
 66             //注冊登陸注冊的應用服務(ApplicationService)
 67             services.AddTransient<ILoginService<ApplicationUser>, EFLoginService>();
 68             services.AddTransient<IRedirectService, RedirectService>();
 69 
 70             var connectionString = Configuration["ConnectionString"];
 71             var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
 72 
 73             // 注冊 IdentityServer
 74             services.AddIdentityServer(x =>
 75             {
 76                 x.IssuerUri = "null";
 77                 x.Authentication.CookieLifetime = TimeSpan.FromHours(2);//cookie有效期兩小時
 78             })
 79             .AddSigningCredential(Certificate.Get())//設置加密證書
 80             //配置IdentityServer。IUserClaimsPrincipalFactory、IResourceOwnerPasswordValidator和IProfileService的網絡標識實現。
 81             .AddAspNetIdentity<ApplicationUser>()
 82             .AddConfigurationStore(options => //使用IdentityServer配置IClientStore、IResourceStore和ICorsPolicyService的EF實現。
 83             {
 84                 options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString,
 85                                     sqlServerOptionsAction: sqlOptions =>
 86                                     {
 87                                         sqlOptions.MigrationsAssembly(migrationsAssembly);
 88                                         //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency 
 89                                         sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
 90                                     });
 91             })
 92             //注冊IPersistedGrantStore的實現,用於存儲AuthorizationCode和RefreshToken等等,默認實現是存儲在內存中,
 93             //如果服務重啟那么這些數據就會被清空了,因此實現IPersistedGrantStore將這些數據寫入到數據庫中
 94             .AddOperationalStore(options =>
 95                 {
 96                     options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString,
 97                                     sqlServerOptionsAction: sqlOptions =>
 98                                     {
 99                                         sqlOptions.MigrationsAssembly(migrationsAssembly);
100                                         //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency 
101                                         sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
102                                     });
103                 })
104             //注冊IProfileService,該接口允許IdentityServer連接到用戶。
105             .Services.AddTransient<IProfileService, ProfileService>();
106 
107             //使用autofac
108             var container = new ContainerBuilder();
109             container.Populate(services);
110 
111             return new AutofacServiceProvider(container.Build());
112         }
113 
114         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
115         public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
116         {
117             //配置日志
118             loggerFactory.AddConsole(Configuration.GetSection("Logging"));
119             loggerFactory.AddDebug();
120             loggerFactory.AddAzureWebAppDiagnostics();
121             loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace);
122 
123             if (env.IsDevelopment())
124             {
125                 app.UseDeveloperExceptionPage();
126                 app.UseDatabaseErrorPage();
127             }
128             else
129             {
130                 app.UseExceptionHandler("/Home/Error");
131             }
132 
133             var pathBase = Configuration["PATH_BASE"];
134             if (!string.IsNullOrEmpty(pathBase))
135             {
136                 loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'");
137                 app.UsePathBase(pathBase);
138             }
139 
140 
141 #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
142             app.Map("/liveness", lapp => lapp.Run(async ctx => ctx.Response.StatusCode = 200));
143 #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
144 
145             //使用StaticFiles,等於啟動了靜態文件服務器功能。wwwroot 就是靠這個中間件讀取的。
146             //也可以不使用wwwroot,並且制定自己對目錄。傳入參數就可以了。
147             app.UseStaticFiles();
148 
149 
150             // Make work identity server redirections in Edge and lastest versions of browers. WARN: Not valid in a production environment.
151             app.Use(async (context, next) =>
152             {
153                 context.Response.Headers.Add("Content-Security-Policy", "script-src 'unsafe-inline'");
154                 await next();
155             });
156 
157             //處理代理服務器和負載均衡對解決方案,
158             //詳情 https://docs.microsoft.com/zh-cn/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-2.1
159             app.UseForwardedHeaders();
160             //使用IdentityServer4
161             app.UseIdentityServer();
162 
163             //配置MVC
164             app.UseMvc(routes =>
165             {
166                 routes.MapRoute(
167                     name: "default",
168                     template: "{controller=Home}/{action=Index}/{id?}");
169             });
170         }
171 
172         /// <summary>
173         /// 應用監控
174         /// </summary>
175         /// <param name="services"></param>
176         private void RegisterAppInsights(IServiceCollection services)
177         {
178             services.AddApplicationInsightsTelemetry(Configuration);
179             var orchestratorType = Configuration.GetValue<string>("OrchestratorType");
180 
181             if (orchestratorType?.ToUpper() == "K8S")
182             {
183                 // Enable K8s telemetry initializer
184                 services.EnableKubernetes();
185             }
186             if (orchestratorType?.ToUpper() == "SF")
187             {
188                 // Enable SF telemetry initializer
189                 services.AddSingleton<ITelemetryInitializer>((serviceProvider) =>
190                     new FabricTelemetryInitializer());
191             }
192         }
193     }
View Code

 

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM