做者: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如何使用其它模式进行认证。今天就先跟你们介绍到这里,但愿你们能持续关注咱们。