.NET Core + Ocelot + IdentityServer4 + Consul 基礎架構實現

先決條件

  • 關於 Ocelot
    • 針對使用 .NET 開發微服務架構或者面向服務架構提供一個統一訪問系統的組件。 參考
    • 本文將使用 Ocelot 構建統一入口的 Gateway。
  • 關於 IdentityServer4
    • IdentityServer4 是一個 OpenID Connect 和 OAuth 2.0 框架用於 ASP.NET Core 。IdentityServer4 在你的應用程序中集成了基於令牌認證、單點登錄、API訪問控制所需的所有協議和擴展點。參考
    • 本文將使用 IdentityServer4 搭建獨立認證服務器。
  • 關於 Consul
    • Consul 是一個服務網格解決方案,通過服務發現、配置、功能分割提供一個全功能的控制層。這些功能可以單獨使用,也可以同時使用以形成一個完整的網格服務。參考
    • 本文將使用 Consul 註冊多個服務。
  • 關於 .Net Core
    • 將使用 WebApi 構建多個服務

構建 IdentityServer 服務

  1. 添加 ASP.Net Core Web 項目
  2. 添加空項目
  3. 在程序包管理控制檯中輸入:Install-Package IdentityServer4.AspNetIdentity
  4. 添加 Config.cs 文件,並添加內容如下:

     using System.Collections.Generic;
     using IdentityServer4.Models;
     using IdentityServer4.Test;
    
     namespace IdentityServer
     {
         public sealed class Config
         {
             public static IEnumerable<ApiResource> GetApiResources()
             {
                 return new List<ApiResource>
                 {
                     new ApiResource("ServiceA", "ServiceA API"),
                     new ApiResource("ServiceB", "ServiceB API")
                 };
             }
    
             public static IEnumerable<Client> GetClients()
             {
                 return new List<Client>
                 {
                     new Client
                     {
                         ClientId = "ServiceAClient",
                         AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
                         ClientSecrets =
                         {
                             new Secret("ServiceAClient".Sha256())
                         },
                         AllowedScopes = new List<string> {"ServiceA"},
                         AccessTokenLifetime = 60 * 60 * 1
                     },
                     new Client
                     {
                         ClientId = "ServiceBClient",
                         AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
                         ClientSecrets =
                         {
                             new Secret("ServiceBClient".Sha256())
                         },
                         AllowedScopes = new List<string> {"ServiceB"},
                         AccessTokenLifetime = 60 * 60 * 1
                     }
                 };
             }
    
             public static List<TestUser> GetUsers()
             {
                 return new List<TestUser>
                 {
                     new TestUser
                     {
                         Username = "test",
                         Password = "123456",
                         SubjectId = "1"
                     }
                 };
             }
    
             public static IEnumerable<IdentityResource> GetIdentityResources()
             {
                 return new List<IdentityResource>();
             }
         }
     }

    注意:這裏添加了兩個 Client ,分別爲 ServiceA、ServiceB ,因此接下來將構建這兩個服務。

  5. 刪掉StartUp.cs文件,在Program.cs中添加內容如下:

     using Microsoft.AspNetCore;
     using Microsoft.AspNetCore.Builder;
     using Microsoft.AspNetCore.Hosting;
     using Microsoft.AspNetCore.Mvc;
     using Microsoft.Extensions.DependencyInjection;
    
     namespace IdentityServer
     {
         public class Program
         {
             public static void Main(string[] args)
             {
                 CreateWebHostBuilder(args).Build().Run();
             }
    
             public static IWebHostBuilder CreateWebHostBuilder(string[] args)
             {
                 return WebHost.CreateDefaultBuilder(args).ConfigureServices(services =>
                 {
                     services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    
                     services.AddIdentityServer()
                         .AddDeveloperSigningCredential()
                         .AddInMemoryIdentityResources(Config.GetIdentityResources())
                         .AddInMemoryApiResources(Config.GetApiResources())
                         .AddInMemoryClients(Config.GetClients())
                         .AddTestUsers(Config.GetUsers());
                 }).Configure(app =>
                 {
                     app.UseIdentityServer();
                 });
             }
         }
     }

    注意:AddDeveloperSigningCredential() 方法用於添加開發時使用的 Key material ,生產環境中不要使用該方法。在 .NET Core 2.2 中新建的 Web 項目文件 csproj 中包含了如下內容:
    csharp <PropertyGroup> <TargetFramework>netcoreapp2.2</TargetFramework> <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel> </PropertyGroup>
    這裏更改
    csharp <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
    爲或直接刪除該行,這麼做的原因是當值爲 InProcess 時,讀寫 tempkey.rsa 將產生權限問題。關於 AspNetCoreHostingModel 可參考 ASP.NET Core Module
    csharp <AspNetCoreHostingModel>OutOfProcess</AspNetCoreHostingModel>

  6. F5 啓動該服務,顯示如下:

    在瀏覽器中輸入 http://localhost:38033/.well-known/openid-configuration ,得到以下內容

至此,一個包含兩個服務認證的認證服務搭建完畢。

構建 ServiceA、ServiceB

  1. 添加 ASP.Net Core Web 項目,這裏以 ServiceA 爲例進行構建

  2. 添加 ASP.Net Core API

  3. 在程序包管理控制檯中運行

    Install-Package IdentityModel
  4. 在 StartUp.cs 中添加內容如下:

     using Microsoft.AspNetCore.Builder;
     using Microsoft.AspNetCore.Hosting;
     using Microsoft.AspNetCore.Mvc;
     using Microsoft.Extensions.Configuration;
     using Microsoft.Extensions.DependencyInjection;
    
     namespace ServiceA
     {
         public class Startup
         {
             public Startup(IConfiguration configuration)
             {
                 Configuration = configuration;
             }
    
             public IConfiguration Configuration { get; }
    
             // This method gets called by the runtime. Use this method to add services to the container.
             public void ConfigureServices(IServiceCollection services)
             {
                 services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    
                 services.AddAuthentication("Bearer")
                     .AddJwtBearer("Bearer", options =>
                     {
                         options.Authority = "http://127.0.0.1:8021";
                         options.RequireHttpsMetadata = false;
                         options.Audience = "ServiceA";
                     });
             }
    
             // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
             public void Configure(IApplicationBuilder app, IHostingEnvironment env)
             {
                 if (env.IsDevelopment())
                 {
                     app.UseDeveloperExceptionPage();
                 }
                 app.UseAuthentication();
                 app.UseMvc();
             }
         }
     }
    
  5. 添加 SessionController 用於用戶登錄,內容如下:

     using System.ComponentModel.DataAnnotations;
     using System.Net.Http;
     using System.Threading.Tasks;
     using IdentityModel.Client;
     using Microsoft.AspNetCore.Mvc;
    
     namespace ServiceA.Controllers
     {
         [Route("api/[controller]")]
         [ApiController]
         public class SessionController : ControllerBase
         {
             public async Task<string> Login(UserRequestModel userRequestModel)
             {
                 // discover endpoints from metadata
                 var client = new HttpClient();
                 DiscoveryResponse disco = await client.GetDiscoveryDocumentAsync("http://127.0.0.1:8021");
                 if (disco.IsError)
                 {
                     return "認證服務器未啓動";
                 }
                 TokenResponse tokenResponse = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
                 {
                     Address = disco.TokenEndpoint,
                     ClientId = "ServiceAClient",
                     ClientSecret = "ServiceAClient",
                     UserName = userRequestModel.Name,
                     Password = userRequestModel.Password
                 });
    
                 return tokenResponse.IsError ? tokenResponse.Error : tokenResponse.AccessToken;
             }
         }
    
         public class UserRequestModel
         {
             [Required(ErrorMessage = "用戶名稱不可以爲空")]
             public string Name { get; set; }
    
             [Required(ErrorMessage = "用戶密碼不可以爲空")]
             public string Password { get; set; }
         }
     }
  6. 添加 HealthController 用於 Consul 進行服務健康檢查,內容如下:

     using Microsoft.AspNetCore.Mvc;
    
     namespace ServiceA.Controllers
     {
         [Route("api/[controller]"), ApiController]
         public class HealthController : ControllerBase
         {
             /// <summary>
             /// 健康檢查
             /// </summary>
             /// <returns></returns>
             [HttpGet]
             public IActionResult Get()
             {
                 return Ok();
             }
         }
     }
  7. 更改 ValuesController.cs 內容如下:

     using System.Collections.Generic;
     using Microsoft.AspNetCore.Authorization;
     using Microsoft.AspNetCore.Mvc;
    
     namespace ServiceA.Controllers
     {
         [Authorize] //添加 Authorize Attribute 以使該控制器啓用認證
         [Route("api/[controller]")]
         [ApiController]
         public class ValuesController : ControllerBase
         {
             // GET api/values
             [HttpGet]
             public ActionResult<IEnumerable<string>> Get()
             {
                 return new[] { "value1", "value2" };
             }
         }
     }

注意,以上基本完成了 ServiceA 的服務構建,但在實際應用中應做一些修改,例如:IdentityServer 地址應在 appsettings.json 中進行配置,不應把地址分散於項目中各處;認證服務啓用最好在全局啓用,以防止漏寫等等。ServiceB 的內容與 ServiceA 大致相似,因此文章中將不再展示 ServiceB 的構建過程。

Gateway 構建

  1. 添加ASP.Net Web

  2. 添加空項目

  3. 打開程序包管理器控制檯輸入命令:
    csharp install-package Ocelot //添加 Ocelot
    csharp install-package Ocelot.Provider.Consul // 添加 Consul 服務發現
  4. 添加 ocelot.json 文件,內容如下

     {
     "ReRoutes": [
         {
         "DownstreamPathTemplate": "/api/{everything}",
         "DownstreamScheme": "http",
         "UpstreamPathTemplate": "/ServiceA/{everything}",
         "UpstreamHttpMethod": [ "GET", "POST", "DELETE", "PUT" ],
         "ServiceName": "ServiceA", //consul 服務中 ServiceA 的名稱
         "LoadBalancerOptions": {
             "Type": "LeastConnection"
         }
         },
         {
         "DownstreamPathTemplate": "/api/{everything}",
         "DownstreamScheme": "http",
         "UpstreamPathTemplate": "/ServiceB/{everything}",
         "UpstreamHttpMethod": [ "GET", "POST", "DELETE", "PUT" ],
         "ServiceName": "ServiceB", //consul 服務中 ServiceB 的名稱
         "LoadBalancerOptions": {
             "Type": "LeastConnection"
         }
         }
     ],
     "GlobalConfiguration": {
         "ServiceDiscoveryProvider": {    // Consul 服務發現配置
         "Host": "localhost",    // Consul 地址
         "Port": 8500,
         "Type": "Consul"
         }
     }
     }
  5. 刪除 StartUp.cs 文件,在 Program.cs 文件中添加如下內容

     using System.IO;
     using Microsoft.AspNetCore.Hosting;
     using Microsoft.Extensions.Configuration;
     using Ocelot.DependencyInjection;
     using Ocelot.Middleware;
     using Ocelot.Provider.Consul;
    
     namespace ApiGateway
     {
         public class Program
         {
             public static void Main(string[] args)
             {
                 new WebHostBuilder()
                     .UseKestrel()
                     .UseContentRoot(Directory.GetCurrentDirectory())
                     .ConfigureAppConfiguration((hostingContext, config) =>
                     {
                         config
                             .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
                             .AddJsonFile("appsettings.json", true, true)
                             .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
                             .AddJsonFile("ocelot.json")
                             .AddEnvironmentVariables();
                     })
                     .ConfigureServices(services =>
                     {
                         services.AddOcelot().AddConsul();
                     })
                     .ConfigureLogging((hostingContext, logging) =>
                     {
                         //add your logging
                     })
                     .UseIISIntegration()
                     .Configure(app =>
                     {
                         app.UseOcelot().Wait();
                     })
                     .Build()
                     .Run();
             }
         }
     }

注意:打開 Gateway.csproj 文件,更改

<PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
</PropertyGroup>

<PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <AspNetCoreHostingModel>OutOfProcess</AspNetCoreHostingModel>
</PropertyGroup>

至此,一個基礎網關基本構建完成。

構建 Consul 服務

  1. 使用 Chocoletey 安裝 Consul,

    choco install consul
  2. 新建一個文件夾以保存 Consul 服務配置

  3. 在 consul.d 文件夾中添加配置文件,內容如下:

        {
            "services": [{
                    "ID": "ServiceA",
                    "Name": "ServiceA",
                    "Tags": [
                        "ServiceAWebApi", "Api"
                    ],
                    "Address": "127.0.0.1",
                    "Port": 8010,
                    "Check": {
                        "HTTP": "http://127.0.0.1:8010/Api/health",
                        "Interval": "10s"
                    }
                }, {
                    "id": "ServiceB",
                    "name": "ServiceB",
                    "tags": [
                        "ServiceBWebApi","Api"
                    ],
                    "Address": "127.0.0.1",
                    "Port": 8011,
                    "Check": [{
                            "HTTP": "http://127.0.0.1:8011/Api/health",
                            "Interval": "10s"
                        }
                    ]
                }
            ]
        }
    
  4. 啓動 consul 服務

    consul agent -dev -config-dir=./consul.d

    啓動後在瀏覽器中輸入 http://localhost:8500/ui/ 以查看Consul服務

Postman 驗證

  1. F5 啓動 Gateway 項目,啓動 Postman 發送請求到 ServiceA 獲取 Token。

  2. 使用 Token 請求 ServiceA Values 接口

  3. 當嘗試使用 ServiceA 獲取到的 Token 去獲取 ServiceB 的數據時,請求也如意料之中返回 401

總結

至此,一個由 .NET Core、IdentityServer4、Ocelot、Consul實現的基礎架構搭建完畢。源碼地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章