天天看點

Ocelot(四)- 認證與授權

本文是我關于Ocelot系列文章的第四篇,認證與授權。在前面的系列文章中,我們的下遊服務接口都是公開的,沒有經過任何的認證,隻要知道接口的調用方法,任何人都可以随意調用,是以,很容易就造成資訊洩露或者服務被攻擊。

Ocelot(四)- 認證與授權

作者:markjiang7m2

原文位址:https://www.cnblogs.com/markjiang7m2/p/10932805.html

源碼位址:https://gitee.com/Sevenm2/OcelotDemo

正如,我要找Willing幹活之前,我得先到HR部門那裡登記并且拿到屬于我自己的工卡,然後我帶着我的工卡去找Willing,亮出我是公司員工的身份,并且有權利要求他幫我完成一個任務。

在這裡內建一套 .net core的服務認證架構IdentityServer4,以及如何在Ocelot中接入IdentityServer4的認證與授權。

跟上一篇Ocelot(三)- 服務發現文章中的Consul類似,這一個是關于Ocelot的系列文章,我暫時也不打算詳細展開說明IdentityServer4,在本文中也是使用IdentityServer4最簡單的Client認證模式。

關于更多的Ocelot功能介紹,可以檢視我的系列文章

  • Ocelot - .Net Core開源網關
  • Ocelot(二)- 請求聚合與負載均衡
  • Ocelot(三)- 服務發現

本文中涉及案例的完整代碼都可以從我的代碼倉庫進行下載下傳。

  • 倉庫位址:https://gitee.com/Sevenm2/OcelotDemo

IdentityServer4使用

IdentityServer4有多種認證模式,包括使用者密碼、用戶端等等,我這裡隻需要實作IdentityServer4的驗證過程即可,是以,我選擇了使用最簡單的用戶端模式。

首先我們來看,當沒有Ocelot網關時系統是如何使用IdentityServer4進行認證的。

Ocelot(四)- 認證與授權

用戶端需要先想IdentityServer請求認證,獲得一個Token,然後再帶着這個Token向下遊服務送出請求。

我嘗試根據流程圖搭建出這樣的認證服務。

建立IdentityServer服務端

建立一個空的Asp.Net Core Web API項目,因為這個項目隻做IdentityServer服務端,是以,我将Controller也直接删除掉。

使用NuGet添加IdentityServer4,可以直接使用NuGet包管理器搜尋

IdentityServer4

進行安裝,或者通過VS中内置的PowerShell執行下面的指令行

Install-Package IdentityServer4
           
Ocelot(四)- 認證與授權

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(四)- 認證與授權

這樣,最簡單的IdentityServer服務就配置完成了。當然,我剛剛為了快速驗證IdentityServer服務是否搭建成功,是以使用的是Debug模式,接下來要使用的話,還是要通過IIS部署使用的,我這裡就把IdentityServer服務部署到

8005

端口。

下遊服務加入認證

OcelotDownAPI

項目中,使用NuGet添加AccessTokenValidation包,可以直接使用NuGet包管理器搜尋

IdentityServer4.AccessTokenValidation

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(四)- 認證與授權

得到了

401

的狀态碼,即未經授權。

是以,我必須先向IdentityServer請求認證并授權

Ocelot(四)- 認證與授權

然後将得到的

Token

Bearer

的方式加入到向下遊服務的請求當中,這樣我們就可以得到了正确的結果

Ocelot(四)- 認證與授權

可能有些朋友在這裡會有點疑惑,在Postman中我們在

Authorization

中加入這個Token,但是在我們實際調用中該怎麼加入Token?

其實熟悉Postman的朋友可能就知道怎麼一回事,Postman為了我們在使用過程中更加友善填入Token資訊而單獨列出了

Authorization

,實際上,最終還是會轉換加入到請求頭當中

這個請求頭的Key就是

Authorization

,對應的值是

Bearer

(空格)

Token

Ocelot(四)- 認證與授權

以上就是沒有Ocelot網關時,IdentityServer的認證流程。

案例五 Ocelot內建IdentityServer服務

在上面的例子中,我是直接将下遊服務暴露給用戶端調用,當接入Ocelot網關時,我們要達到内外互隔的特性,于是就把IdentityServer服務也托管到Ocelot網關中,這樣我們就能統一認證和服務請求時的入口。

于是,我們可以形成下面這個流程圖:

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(四)- 認證與授權
Ocelot(四)- 認證與授權

結果也是跟我預想的是一緻的,可以按照這樣的流程進行身份認證。

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

當外面随便來一個人,跟前台說他要找我做一件事情,然後前台直接告訴他我的具體位置,就讓他進公司找我了,然後當我接待他的時候,我才發現這個人根本就是來搞事的,拒絕他的請求。如果一天來這麼幾十号人,我還要不要正常幹活了?

這明顯就不符合實際應用場景,外面的人(用戶端)在前台(Ocelot)的時候,就需要進行身份認證(IdentityServer),隻有通過認證的人才能進公司(路由),我才會接觸到這個人(響應),這才叫專人做專事。

于是,認證流程改為下圖:

Ocelot(四)- 認證與授權

準備下遊服務

為了保證我的案例與上面這個認證流程是一緻的,我就把前面在下遊服務中的認證配置去掉。而且在實際生産環境中,用戶端與下遊服務的網絡是隔斷的,用戶端隻能通過網關的轉發才能向下遊服務送出請求。

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和這裡的第一組路由為例。

  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身份認證

markfull

ClientId向IdentityServer進行認證

Ocelot(四)- 認證與授權

向8001請求

将得到的Token加入到請求中,請求Url位址

http://localhost:4727/ocelot/8001/identityWilling

,得到下遊服務傳回的響應結果

Ocelot(四)- 認證與授權

向8002請求

http://localhost:4727/ocelot/8002/identityWilling

然後,更換

marklimit

身份再驗證一遍

marklimit身份認證

marklimit

Ocelot(四)- 認證與授權

http://localhost:4727/ocelot/8001/identityWilling

Ocelot(四)- 認證與授權

http://localhost:4727/ocelot/8002/identityWilling

,此時,我們得到了

401

的狀态碼,即未授權。

Ocelot(四)- 認證與授權

總結

在這篇文章中就跟大家介紹了基于IdentityServer4為認證伺服器的Ocelot認證與授權,主要是通過一些案例的實踐,讓大家了解Ocelot對用戶端身份的驗證過程,使用了IdentityServer中最簡單的用戶端認證模式,因為這種模式下IdentityServer的認證沒有複雜的層級關系。但通常在我們實際開發時,更多的可能是通過使用者密碼等方式進行身份認證的,之後我會盡快給大家分享關于IdentityServer如何使用其它模式進行認證。今天就先跟大家介紹到這裡,希望大家能持續關注我們。

作者: markjiang7m2 出處:

https://www.cnblogs.com/markjiang7m2/

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。

Ocelot(四)- 認證與授權