Ocelot (four) - Authentication and Authorization

Ocelot (four) - Authentication and Authorization

Author: markjiang7m2
original address: https://www.cnblogs.com/markjiang7m2/p/10932805.html
Source Address: https://gitee.com/Sevenm2/OcelotDemo

This article is my fourth in a series of articles about Ocelot authentication and authorization . In the previous series, our downstream service interfaces are open, without any certification, as long as the know call the methods of the interface, anyone can freely call, therefore, it is easy to result in information disclosure or service being attacked.

As before Willing I'm looking for work, I have first-HR department where the registration card and get my own work, and then I took my ID card, look for Willing, I showed the identity of employees, and We have the right to ask him to help me complete a task.

Here a set of integrated services certification .net Framework IdentityServer4 core, and how to access authentication and authorization IdentityServer4 in the Ocelot.

Keep an Ocelot (c) - Service Discovery similar articles of Consul, which is a series of articles about Ocelot, and I do not intend to expand the detailed description IdentityServer4, but also the easiest to use IdentityServer4 Client authentication mode in this article.

More about Ocelot Features, you can view my series of articles

This article in cases involving the complete code can be downloaded from my code repository.

IdentityServer4 use

IdentityServer4 There are several authentication modes, including user passwords, client, and so, here I only need to implement the verification process can IdentityServer4, so I chose to use the simplest client mode.

First we look at, when there is no Ocelot gateway system is how to use IdentityServer4 for authentication.

Ocelot_025_identityserver

The client needs to think IdentityServer request certification, access to a Token, and then the Token with a request to the downstream services.

I try to build a flow chart of such certification services.

Creating IdentityServer server
to create a new empty Asp.Net Core Web API project, because the project only IdentityServer server, so I'll Controller directly deleted.

Use NuGet added IdentityServer4, may be used as search NuGet package manager IdentityServer4to install, or by performing the following command line VS built PowerShell

Install-Package IdentityServer4

Ocelot_026_identityService

In appsettings.jsonadding the configuration IdentityServer4

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "SSOConfig": {
    "ApiResources": [
      {
        "Name": "identityAPIService",
        "DisplayName": "identityAPIServiceName"
      }
    ],
    "Clients": [
      {
        "ClientId": "mark",
        "ClientSecrets": [ "markjiang7m2" ],
        "AllowedGrantTypes": "ClientCredentials",
        "AllowedScopes": [ "identityAPIService" ]
      }
    ]
  },
  "AllowedHosts": "*"
}

ApiResourcesAn array type, representing the service list of all downstream IdentityServer management

  • Name: Name downstream services
  • DisplayName: Alias ​​downstream services

ClientsAn array type, indicating that all of the upstream client list management IdentityServer

  • ClientId: Client ID
  • ClientSecrets: key corresponding to the client
  • AllowedGrantTypes: The client supports the authentication mode, currently supports the following:
    • 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 authentication
using marklimitClientId authenticate to IdentityServer

Ocelot_040_identityserver_marklimit

8001 a request to
the resultant is added to the Token request, the request address Url http://localhost:4727/ocelot/8001/identityWillingobtain the response result returned downstream services

Ocelot_041_identityserver_marklimit8001

8002 a request to
the resultant is added to the Token request, Url address request http://localhost:4727/ocelot/8002/identityWilling, this time, we get the 401status code, i.e. unauthorized.

Ocelot_042_identityserver_marklimit8002

to sum up

In this article we will introduce just Ocelot based authentication and authorization IdentityServer4 for the authentication server, mainly through the practice of some cases, so that we understand Ocelot verification process for the client's identity, using IdentityServer the easiest client authentication mode, because no complicated hierarchy IdentityServer certification in this mode. But usually when we actually developed, more may be authenticates by user passwords, etc., then I will share with you on how to use other modes IdentityServer for authentication as soon as possible. Today to talk to you about here, I hope you can continue to concern us.

dotNet框架学苑

Guess you like

Origin www.cnblogs.com/markjiang7m2/p/10932805.html