• Ocelot(四)- 认证与授权


    Ocelot(四)- 认证与授权

    作者:markjiang7m2
    原文地址:https://www.cnblogs.com/markjiang7m2/p/10932805.html
    源码地址:https://gitee.com/Sevenm2/OcelotDemo

    本文是我关于Ocelot系列文章的第四篇,认证与授权。在前面的系列文章中,我们的下游服务接口都是公开的,没有经过任何的认证,只要知道接口的调用方法,任何人都可以随意调用,因此,很容易就造成信息泄露或者服务被攻击。

    正如,我要找Willing干活之前,我得先到HR部门那里登记并且拿到属于我自己的工卡,然后我带着我的工卡去找Willing,亮出我是公司员工的身份,并且有权利要求他帮我完成一个任务。

    在这里集成一套 .net core的服务认证框架IdentityServer4,以及如何在Ocelot中接入IdentityServer4的认证与授权。

    跟上一篇Ocelot(三)- 服务发现文章中的Consul类似,这一个是关于Ocelot的系列文章,我暂时也不打算详细展开说明IdentityServer4,在本文中也是使用IdentityServer4最简单的Client认证模式。

    关于更多的Ocelot功能介绍,可以查看我的系列文章

    本文中涉及案例的完整代码都可以从我的代码仓库进行下载。

    IdentityServer4使用

    IdentityServer4有多种认证模式,包括用户密码、客户端等等,我这里只需要实现IdentityServer4的验证过程即可,因此,我选择了使用最简单的客户端模式。

    首先我们来看,当没有Ocelot网关时系统是如何使用IdentityServer4进行认证的。

    Ocelot_025_identityserver

    客户端需要先想IdentityServer请求认证,获得一个Token,然后再带着这个Token向下游服务发出请求。

    我尝试根据流程图搭建出这样的认证服务。

    创建IdentityServer服务端
    新建一个空的Asp.Net Core Web API项目,因为这个项目只做IdentityServer服务端,因此,我将Controller也直接删除掉。

    使用NuGet添加IdentityServer4,可以直接使用NuGet包管理器搜索IdentityServer4进行安装,或者通过VS中内置的PowerShell执行下面的命令行

    Install-Package IdentityServer4
    

    Ocelot_026_identityService

    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管理的所有的下游服务列表

    • Name: 下游服务名称
    • DisplayName: 下游服务别名

    Clients为数组类型,表示IdentityServer管理的所有的上游客户端列表

    • ClientId: 客户端ID
    • ClientSecrets: 客户端对应的密钥
    • AllowedGrantTypes: 该客户端支持的认证模式,目前支持如下:
      • Implicit
      • ImplicitAndClientCredentials
      • Code
      • CodeAndClientCredentials
      • Hybrid
      • HybridAndClientCredentials
      • ClientCredentials
      • ResourceOwnerPassword
      • ResourceOwnerPasswordAndClientCredentials
      • DeviceFlow
    • AllowedScopes: 该客户端支持访问的下游服务列表,必须是在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

    如果认证正确,会得到如下结果:

    Ocelot_027_identityserver_postman

    如果认证失败,则会返回如下:

    Ocelot_028_identityserver_postmanerror

    这样,最简单的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,得到如下结果:

    Ocelot_029_identityserver_401

    得到了401的状态码,即未经授权。

    因此,我必须先向IdentityServer请求认证并授权

    Ocelot_030_identityserver_token

    然后将得到的TokenBearer的方式加入到向下游服务的请求当中,这样我们就可以得到了正确的结果

    Ocelot_031_identityserver_8001

    可能有些朋友在这里会有点疑惑,在Postman中我们在Authorization中加入这个Token,但是在我们实际调用中该怎么加入Token?

    其实熟悉Postman的朋友可能就知道怎么一回事,Postman为了我们在使用过程中更加方便填入Token信息而单独列出了Authorization,实际上,最终还是会转换加入到请求头当中
    这个请求头的Key就是Authorization,对应的值是Bearer + (空格) + Token

    Ocelot_032_identityserver_header

    以上就是没有Ocelot网关时,IdentityServer的认证流程。

    案例五 Ocelot集成IdentityServer服务

    在上面的例子中,我是直接将下游服务暴露给客户端调用,当接入Ocelot网关时,我们要达到内外互隔的特性,于是就把IdentityServer服务也托管到Ocelot网关中,这样我们就能统一认证和服务请求时的入口。
    于是,我们可以形成下面这个流程图:

    Ocelot_033_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认证,然后将得到的TokenBearer的方式加入到向下游服务的请求当中

    Ocelot_034_identityserver_ocelottoken

    Ocelot_035_identityserver_ocelotresult

    结果也是跟我预想的是一致的,可以按照这样的流程进行身份认证。

    但是!!!但是!!!但是!!!

    当外面随便来一个人,跟前台说他要找我做一件事情,然后前台直接告诉他我的具体位置,就让他进公司找我了,然后当我接待他的时候,我才发现这个人根本就是来搞事的,拒绝他的请求。如果一天来这么几十号人,我还要不要正常干活了?

    这明显就不符合实际应用场景,外面的人(客户端)在前台(Ocelot)的时候,就需要进行身份认证(IdentityServer),只有通过认证的人才能进公司(路由),我才会接触到这个人(响应),这才叫专人做专事。

    于是,认证流程改为下图:

    Ocelot_036_identityserver_ocelot2

    准备下游服务

    为了保证我的案例与上面这个认证流程是一致的,我就把前面在下游服务中的认证配置去掉。而且在实际生产环境中,客户端与下游服务的网络是隔断的,客户端只能通过网关的转发才能向下游服务发出请求。

    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项目重新打包,部署在80018002端口,作为两个独立的下游服务。

    配置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登录的客户端可以同时请求identityAPIService8001identityAPIService8002两个下游服务,而使用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.csConfigureServices方法,就能很快地将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和这里的第一组路由为例。

    1. 客户端拿着markfull的clientID向IdentityServer(http://localhost:4727/token)进行认证,得到了一个的Token
    2. 客户端带着这个Token,因此有了markfull的身份,请求Url地址http://localhost:4727/ocelot/8001/identityWilling
    3. Ocelot网关接收到请求,根据路由表找到了认证支持关键字为APIService8001,从而得到了对应的IdentityServer服务信息:IdentityServer服务地址为http://localhost:8005,下游服务名称为identityAPIService8001
    4. Ocelot带着Token向IdentityServer服务(http://localhost:8005)进行配对,即查看markfull身份的访问范围是否包含了identityAPIService8001服务
    5. Ocelot认证过markfull是允许访问的,将请求转发到下游服务中,根据路由配置,下游服务地址为http://localhost:8001/api/ocelot/identityWilling

    下面我将Ocelot运行起来,并通过Postman进行验证。

    markfull身份认证
    使用markfullClientId向IdentityServer进行认证

    Ocelot_037_identityserver_markfull

    向8001请求
    将得到的Token加入到请求中,请求Url地址http://localhost:4727/ocelot/8001/identityWilling,得到下游服务返回的响应结果

    Ocelot_038_identityserver_markfull8001

    向8002请求
    将得到的Token加入到请求中,请求Url地址http://localhost:4727/ocelot/8002/identityWilling,得到下游服务返回的响应结果

    Ocelot_039_identityserver_markfull8002

    然后,更换marklimit身份再验证一遍

    marklimit身份认证
    使用marklimitClientId向IdentityServer进行认证

    Ocelot_040_identityserver_marklimit

    向8001请求
    将得到的Token加入到请求中,请求Url地址http://localhost:4727/ocelot/8001/identityWilling,得到下游服务返回的响应结果

    Ocelot_041_identityserver_marklimit8001

    向8002请求
    将得到的Token加入到请求中,请求Url地址http://localhost:4727/ocelot/8002/identityWilling,此时,我们得到了401的状态码,即未授权。

    Ocelot_042_identityserver_marklimit8002

    总结

    在这篇文章中就跟大家介绍了基于IdentityServer4为认证服务器的Ocelot认证与授权,主要是通过一些案例的实践,让大家理解Ocelot对客户端身份的验证过程,使用了IdentityServer中最简单的客户端认证模式,因为这种模式下IdentityServer的认证没有复杂的层级关系。但通常在我们实际开发时,更多的可能是通过用户密码等方式进行身份认证的,之后我会尽快给大家分享关于IdentityServer如何使用其它模式进行认证。今天就先跟大家介绍到这里,希望大家能持续关注我们。

  • 相关阅读:
    在Ubuntu上安装Hadoop(集群模式)
    Node.js v0.10.8 发布
    设置 Sublime Text 的 Python 开发环境
    jQuery 1.10.0 和 2.0.1 发布
    openSUSE 13.1 Milestone 2 发布
    mochad 0.1.6 发布,TCP 网关守护进程
    JPPF 3.3.2 发布,Java 并行处理框架
    PyCharm 又一强大Python IDE
    AntiXSS 支持Html同时防止XSS攻击
    (原创)攻击方式学习系列(总)
  • 原文地址:https://www.cnblogs.com/markjiang7m2/p/10932805.html
Copyright © 2020-2023  润新知