做者:markjiang7m2
原文地址:http://www.javashuo.com/article/p-xfmimnsb-ev.html
源碼地址:https://gitee.com/Sevenm2/OcelotDemohtml
本文是我關於Ocelot系列文章的第四篇,認證與受權。在前面的系列文章中,咱們的下游服務接口都是公開的,沒有通過任何的認證,只要知道接口的調用方法,任何人均可以隨意調用,所以,很容易就形成信息泄露或者服務被攻擊。git
正如,我要找Willing幹活以前,我得先到HR部門那裏登記而且拿到屬於我本身的工卡,而後我帶着個人工卡去找Willing,亮出我是公司員工的身份,而且有權利要求他幫我完成一個任務。shell
在這裏集成一套 .net core的服務認證框架IdentityServer4,以及如何在Ocelot中接入IdentityServer4的認證與受權。json
跟上一篇Ocelot(三)- 服務發現文章中的Consul相似,這一個是關於Ocelot的系列文章,我暫時也不打算詳細展開說明IdentityServer4,在本文中也是使用IdentityServer4最簡單的Client認證模式。c#
關於更多的Ocelot功能介紹,能夠查看個人系列文章api
本文中涉及案例的完整代碼均可以從個人代碼倉庫進行下載。數組
IdentityServer4有多種認證模式,包括用戶密碼、客戶端等等,我這裏只須要實現IdentityServer4的驗證過程便可,所以,我選擇了使用最簡單的客戶端模式。服務器
首先咱們來看,當沒有Ocelot網關時系統是如何使用IdentityServer4進行認證的。網絡
客戶端須要先想IdentityServer請求認證,得到一個Token,而後再帶着這個Token向下遊服務發出請求。併發
我嘗試根據流程圖搭建出這樣的認證服務。
建立IdentityServer服務端
新建一個空的Asp.Net Core Web API項目,由於這個項目只作IdentityServer服務端,所以,我將Controller也直接刪除掉。
使用NuGet添加IdentityServer4,能夠直接使用NuGet包管理器搜索IdentityServer4
進行安裝,或者經過VS中內置的PowerShell執行下面的命令行
Install-Package IdentityServer4
在appsettings.json
中添加IdentityServer4的配置
{ "Logging": { "LogLevel": { "Default": "Warning" } }, "SSOConfig": { "ApiResources": [ { "Name": "identityAPIService", "DisplayName": "identityAPIServiceName" } ], "Clients": [ { "ClientId": "mark", "ClientSecrets": [ "markjiang7m2" ], "AllowedGrantTypes": "ClientCredentials", "AllowedScopes": [ "identityAPIService" ] } ] }, "AllowedHosts": "*" }
ApiResources
爲數組類型,表示IdentityServer管理的全部的下游服務列表
Clients
爲數組類型,表示IdentityServer管理的全部的上游客戶端列表
ApiResources
列表中登記的新建一個類用於讀取IdentityServer4的配置
using IdentityServer4.Models; using Microsoft.Extensions.Configuration; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace IdentityServer { public class SSOConfig { public static IEnumerable<ApiResource> GetApiResources(IConfigurationSection section) { List<ApiResource> resource = new List<ApiResource>(); if (section != null) { List<ApiConfig> configs = new List<ApiConfig>(); section.Bind("ApiResources", configs); foreach (var config in configs) { resource.Add(new ApiResource(config.Name, config.DisplayName)); } } return resource.ToArray(); } /// <summary> /// 定義受信任的客戶端 Client /// </summary> /// <returns></returns> public static IEnumerable<Client> GetClients(IConfigurationSection section) { List<Client> clients = new List<Client>(); if (section != null) { List<ClientConfig> configs = new List<ClientConfig>(); section.Bind("Clients", configs); foreach (var config in configs) { Client client = new Client(); client.ClientId = config.ClientId; List<Secret> clientSecrets = new List<Secret>(); foreach (var secret in config.ClientSecrets) { clientSecrets.Add(new Secret(secret.Sha256())); } client.ClientSecrets = clientSecrets.ToArray(); GrantTypes grantTypes = new GrantTypes(); var allowedGrantTypes = grantTypes.GetType().GetProperty(config.AllowedGrantTypes); client.AllowedGrantTypes = allowedGrantTypes == null ? GrantTypes.ClientCredentials : (ICollection<string>)allowedGrantTypes.GetValue(grantTypes, null); client.AllowedScopes = config.AllowedScopes.ToArray(); clients.Add(client); } } return clients.ToArray(); } } public class ApiConfig { public string Name { get; set; } public string DisplayName { get; set; } } public class ClientConfig { public string ClientId { get; set; } public List<string> ClientSecrets { get; set; } public string AllowedGrantTypes { get; set; } public List<string> AllowedScopes { get; set; } } }
在Startup.cs
中注入IdentityServer服務
public void ConfigureServices(IServiceCollection services) { var section = Configuration.GetSection("SSOConfig"); services.AddIdentityServer() .AddDeveloperSigningCredential() .AddInMemoryApiResources(SSOConfig.GetApiResources(section)) .AddInMemoryClients(SSOConfig.GetClients(section)); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); }
使用IdentityServer中間件
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseIdentityServer(); app.UseMvc(); }
配置完成,接下來用Debug模式看看IdentityServer是否可用,嘗試向IdentityServer進行認證。由於須要使用post方式,並且在認證請求的body中加入認證信息,因此我這裏藉助Postman工具完成。
請求路徑:<host>
+/connect/token
若是認證正確,會獲得以下結果:
若是認證失敗,則會返回以下:
這樣,最簡單的IdentityServer服務就配置完成了。固然,我剛剛爲了快速驗證IdentityServer服務是否搭建成功,因此使用的是Debug模式,接下來要使用的話,仍是要經過IIS部署使用的,我這裏就把IdentityServer服務部署到8005
端口。
下游服務加入認證
在OcelotDownAPI
項目中,使用NuGet添加AccessTokenValidation包,能夠直接使用NuGet包管理器搜索IdentityServer4.AccessTokenValidation
進行安裝,或者經過VS中內置的PowerShell執行下面的命令行
Install-Package IdentityServer4.AccessTokenValidation
在appsettings.json
中加入IdentityServer服務信息
"IdentityServerConfig": { "ServerIP": "localhost", "ServerPort": 8005, "IdentityScheme": "Bearer", "ResourceName": "identityAPIService" }
這裏的identityAPIService
就是在IdentityServer服務端配置ApiResources
列表中登記的其中一個下游服務。
在Startup.cs
中讀取IdentityServer服務信息,加入IdentityServer驗證
public void ConfigureServices(IServiceCollection services) { IdentityServerConfig identityServerConfig = new IdentityServerConfig(); Configuration.Bind("IdentityServerConfig", identityServerConfig); services.AddAuthentication(identityServerConfig.IdentityScheme) .AddIdentityServerAuthentication(options => { options.RequireHttpsMetadata = false; options.Authority = $"http://{identityServerConfig.IP}:{identityServerConfig.Port}"; options.ApiName = identityServerConfig.ResourceName; } ); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseAuthentication(); app.UseMvc(); }
根據前面的配置,咱們添加一個須要受權的下游服務API
注意添加屬性[Authorize]
由於我這裏只是爲了演示IdentityServer的認證流程,因此我只是在其中一個API接口中添加該屬性,若是還有其餘接口須要整個認證,就須要在其餘接口中添加該屬性,若是是這個Controller全部的接口都須要IdentityServer認證,那就直接在類名前添加該屬性。
using Microsoft.AspNetCore.Authorization;
// GET api/ocelot/identityWilling [HttpGet("identityWilling")] [Authorize] public async Task<IActionResult> IdentityWilling(int id) { var result = await Task.Run(() => { ResponseResult response = new ResponseResult() { Comment = $"我是Willing,既然你是我公司員工,那我就幫你幹活吧, host: {HttpContext.Request.Host.Value}, path: {HttpContext.Request.Path}" }; return response; }); return Ok(result); }
從新打包OcelotDownAPI
項目,併發布到8001
端口。
首先,像以前那樣直接請求API,獲得以下結果:
獲得了401
的狀態碼,即未經受權。
所以,我必須先向IdentityServer請求認證並受權
而後將獲得的Token
以Bearer
的方式加入到向下遊服務的請求當中,這樣咱們就能夠獲得了正確的結果
可能有些朋友在這裏會有點疑惑,在Postman中咱們在Authorization
中加入這個Token,可是在咱們實際調用中該怎麼加入Token?
其實熟悉Postman的朋友可能就知道怎麼一回事,Postman爲了咱們在使用過程當中更加方便填入Token信息而單獨列出了Authorization
,實際上,最終仍是會轉換加入到請求頭當中
這個請求頭的Key就是Authorization
,對應的值是Bearer
+ (空格)
+ Token
。
以上就是沒有Ocelot網關時,IdentityServer的認證流程。
在上面的例子中,我是直接將下游服務暴露給客戶端調用,當接入Ocelot網關時,咱們要達到內外互隔的特性,因而就把IdentityServer服務也託管到Ocelot網關中,這樣咱們就能統一認證和服務請求時的入口。
因而,咱們能夠造成下面這個流程圖:
根據流程圖,我在Ocelot ReRoutes
中添加兩組路由
{ "DownstreamPathTemplate": "/connect/token", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 8005 } ], "UpstreamPathTemplate": "/token", "UpstreamHttpMethod": [ "Post" ], "Priority": 2 }, { "DownstreamPathTemplate": "/api/ocelot/identityWilling", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 8001 } ], "UpstreamPathTemplate": "/ocelot/identityWilling", "UpstreamHttpMethod": [ "Get" ], "Priority": 2 }
第一組是將IdentityServer服務進行託管,這樣客戶端就能夠直接經過Ocelot網關訪問/token
就能夠進行認證,第二組是將下游服務進行託管
而後,也是按照以前例子的步驟,先經過http://localhost:4727/token
認證,而後將獲得的Token
以Bearer
的方式加入到向下遊服務的請求當中
結果也是跟我預想的是一致的,能夠按照這樣的流程進行身份認證。
可是!!!可是!!!可是!!!
當外面隨便來一我的,跟前臺說他要找我作一件事情,而後前臺直接告訴他個人具體位置,就讓他進公司找我了,而後當我接待他的時候,我才發現這我的根本就是來搞事的,拒絕他的請求。若是一天來這麼幾十號人,我還要不要正常幹活了?
這明顯就不符合實際應用場景,外面的人(客戶端)在前臺(Ocelot)的時候,就須要進行身份認證(IdentityServer),只有經過認證的人才能進公司(路由),我纔會接觸到這我的(響應),這才叫專人作專事。
因而,認證流程改成下圖:
準備下遊服務
爲了保證個人案例與上面這個認證流程是一致的,我就把前面在下游服務中的認證配置去掉。並且在實際生產環境中,客戶端與下游服務的網絡是隔斷的,客戶端只能經過網關的轉發才能向下遊服務發出請求。
OcelotDownAPI項目
public void ConfigureServices(IServiceCollection services) { //IdentityServerConfig identityServerConfig = new IdentityServerConfig(); //Configuration.Bind("IdentityServerConfig", identityServerConfig); //services.AddAuthentication(identityServerConfig.IdentityScheme) // .AddIdentityServerAuthentication(options => // { // options.RequireHttpsMetadata = false; // options.Authority = $"http://{identityServerConfig.IP}:{identityServerConfig.Port}"; // options.ApiName = identityServerConfig.ResourceName; // } // ); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } //app.UseAuthentication(); app.UseMvc(); }
同時也把API接口中的[Authorize]
屬性去除。
而後將OcelotDownAPI
項目從新打包,部署在8001
、8002
端口,做爲兩個獨立的下游服務。
配置IdentityServer
回到IdentityServer
項目的appsettings.json
,在ApiResources
中另外添加兩個服務
{ "Name": "identityAPIService8001", "DisplayName": "identityAPIService8001Name" }, { "Name": "identityAPIService8002", "DisplayName": "identityAPIService8002Name" }
在Clients
中添加兩個Client
{ "ClientId": "markfull", "ClientSecrets": [ "markjiang7m2" ], "AllowedGrantTypes": "ClientCredentials", "AllowedScopes": [ "identityAPIService8001", "identityAPIService8002" ] }, { "ClientId": "marklimit", "ClientSecrets": [ "123456" ], "AllowedGrantTypes": "ClientCredentials", "AllowedScopes": [ "identityAPIService8001" ] }
這裏我爲了能讓你們看出容許訪問範圍的效果,特地分配了兩個不一樣的AllowedScopes
。
使用markfull
登陸的客戶端能夠同時請求identityAPIService8001
和identityAPIService8002
兩個下游服務,而使用marklimit
登陸的客戶端只容許請求identityAPIService8001
服務。
Ocelot集成IdentityServer認證
跟前面的例子同樣,要支持IdentityServer認證,OcelotDemo項目就須要安裝IdentityServer4.AccessTokenValidation
包。
OcelotDemo
項目的appsettings.json
添加IdentityServer信息
"IdentityServerConfig": { "IP": "localhost", "Port": 8005, "IdentityScheme": "Bearer", "Resources": [ { "Key": "APIService8001", "Name": "identityAPIService8001" }, { "Key": "APIService8002", "Name": "identityAPIService8002" } ] }
固然這個配置項的結構是任意的,我這裏的Resources
數組配置的就是Ocelot網關支持哪些服務的認證,Name
就是服務的名稱,同時會惟一對應一個Key
。
爲了能更加方便讀取IdentityServerConfig
的信息,我定義了一個跟它同結構的類
public class IdentityServerConfig { public string IP { get; set; } public string Port { get; set; } public string IdentityScheme { get; set; } public List<APIResource> Resources { get; set; } } public class APIResource { public string Key { get; set; } public string Name { get; set; } }
而後來到Startup.cs
的ConfigureServices
方法,就能很快地將IdentityServer
信息進行註冊
var identityBuilder = services.AddAuthentication(); IdentityServerConfig identityServerConfig = new IdentityServerConfig(); Configuration.Bind("IdentityServerConfig", identityServerConfig); if (identityServerConfig != null && identityServerConfig.Resources != null) { foreach (var resource in identityServerConfig.Resources) { identityBuilder.AddIdentityServerAuthentication(resource.Key, options => { options.Authority = $"http://{identityServerConfig.IP}:{identityServerConfig.Port}"; options.RequireHttpsMetadata = false; options.ApiName = resource.Name; options.SupportedTokens = SupportedTokens.Both; }); } }
Configure
方法中添加
app.UseAuthentication();
最後,就是配置Ocelot.json
文件。
在ReRoutes
中添加兩組路由
{ "DownstreamPathTemplate": "/api/ocelot/identityWilling", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 8001 } ], "UpstreamPathTemplate": "/ocelot/8001/identityWilling", "UpstreamHttpMethod": [ "Get" ], "Priority": 2, "AuthenticationOptions": { "AuthenticationProviderKey": "APIService8001", "AllowedScopes": [] } }, { "DownstreamPathTemplate": "/api/ocelot/identityWilling", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 8002 } ], "UpstreamPathTemplate": "/ocelot/8002/identityWilling", "UpstreamHttpMethod": [ "Get" ], "Priority": 2, "AuthenticationOptions": { "AuthenticationProviderKey": "APIService8002", "AllowedScopes": [] } }
跟其餘普通路由相比,這兩組路由都多了一個AuthenticationOptions
屬性,它裏面的AuthenticationProviderKey
就是咱們在前面ConfigureServices
方法中登記過的Key
。
咱們來捋順一下這個路由跟認證受權過程。以markfull的ID和這裏的第一組路由爲例。
markfull
的clientID向IdentityServer(http://localhost:4727/token
)進行認證,獲得了一個的Tokenmarkfull
的身份,請求Url地址http://localhost:4727/ocelot/8001/identityWilling
APIService8001
,從而獲得了對應的IdentityServer服務信息:IdentityServer服務地址爲http://localhost:8005
,下游服務名稱爲identityAPIService8001
http://localhost:8005
)進行配對,即查看markfull
身份的訪問範圍是否包含了identityAPIService8001
服務markfull
是容許訪問的,將請求轉發到下游服務中,根據路由配置,下游服務地址爲http://localhost:8001/api/ocelot/identityWilling
下面我將Ocelot運行起來,並經過Postman進行驗證。
markfull身份認證
使用markfull
ClientId向IdentityServer進行認證
向8001請求
將獲得的Token加入到請求中,請求Url地址http://localhost:4727/ocelot/8001/identityWilling
,獲得下游服務返回的響應結果
向8002請求
將獲得的Token加入到請求中,請求Url地址http://localhost:4727/ocelot/8002/identityWilling
,獲得下游服務返回的響應結果
而後,更換marklimit
身份再驗證一遍
marklimit身份認證
使用marklimit
ClientId向IdentityServer進行認證
向8001請求
將獲得的Token加入到請求中,請求Url地址http://localhost:4727/ocelot/8001/identityWilling
,獲得下游服務返回的響應結果
向8002請求
將獲得的Token加入到請求中,請求Url地址http://localhost:4727/ocelot/8002/identityWilling
,此時,咱們獲得了401
的狀態碼,即未受權。
在這篇文章中就跟你們介紹了基於IdentityServer4爲認證服務器的Ocelot認證與受權,主要是經過一些案例的實踐,讓你們理解Ocelot對客戶端身份的驗證過程,使用了IdentityServer中最簡單的客戶端認證模式,由於這種模式下IdentityServer的認證沒有複雜的層級關係。但一般在咱們實際開發時,更多的多是經過用戶密碼等方式進行身份認證的,以後我會盡快給你們分享關於IdentityServer如何使用其它模式進行認證。今天就先跟你們介紹到這裏,但願你們能持續關注咱們。