asp.net core系列 60 Ocelot 構建服務認證示例

来源:https://www.cnblogs.com/MrHSR/archive/2019/04/25/10766590.html
-Advertisement-
Play Games

一.概述 在Ocelot中,為了保護下游api資源,用戶訪問時需要進行認證鑒權,這需要在Ocelot 網關中添加認證服務。添加認證後,ReRoutes路由會進行身份驗證,並使用Ocelot的基於聲明的功能。在Startup.cs中註冊認證服務,為每個註冊提供一個方案 (authenticationP ...


一.概述

  在Ocelot中,為了保護下游api資源,用戶訪問時需要進行認證鑒權,這需要在Ocelot 網關中添加認證服務。添加認證後,ReRoutes路由會進行身份驗證,並使用Ocelot的基於聲明的功能。在Startup.cs中註冊認證服務,為每個註冊提供一個方案 (authenticationProviderKey身份驗證提供者密鑰)。

//下麵是在網關項目中,添加認證服務
public void ConfigureServices(IServiceCollection services)
{
    var authenticationProviderKey = "TestKey";

    services.AddAuthentication()
        .AddJwtBearer(authenticationProviderKey, x =>
        {
            //..
        });
}

  其中TestKey是此提供程式已註冊的方案,將映射到ReRoute的配置中

      "AuthenticationOptions": {
            "AuthenticationProviderKey": "TestKey",
            "AllowedScopes": []
        }

  當Ocelot運行時,會查看此configuration.json中的AuthenticationProviderKey節點,並檢查是否使用給定密鑰,該密鑰是否已註冊身份驗證提供程式。如果沒有,那麼Ocelot將無法啟動。如果有,則ReRoute將在執行時使用該提供程式。

 

  本次示例有四個項目:

    APIGateway網關項目  http://localhost:9000

    AuthServer項目生成jwt令牌服務  http://localhost:9009

    CustomerAPIServices 是web api項目  http://localhost:9001

    ClientApp項目 模擬客戶端HttpClient 

  當客戶想要訪問web api服務時,首先訪問API網關的身份驗證模塊。我們需要首先訪問AuthServer以獲取訪問令牌,以便我們可以使用access_token訪問受保護的api服務。開源Github地址,  架構如下圖所示:

  

二. AuthServer項目

  此服務主要用於,為用戶請求受保護的api,需要的jwt令牌。生成jwt關鍵代碼如下:

        /// <summary>
        ///用戶使用 用戶名密碼 來請求伺服器
        ///伺服器進行驗證用戶的信息
        ///伺服器通過驗證發送給用戶一個token
        ///客戶端存儲token,併在每次請求時附送上這個token值, headers: {'Authorization': 'Bearer ' + token}
        ///服務端驗證token值,並返回數據
        /// </summary>
        /// <param name="name"></param>
        /// <param name="pwd"></param>
        /// <returns></returns>
        [HttpGet]
        public IActionResult Get(string name, string pwd)
        {
            //驗證用戶,通過後發送一個token
            if (name == "catcher" && pwd == "123")
            {

                var now = DateTime.UtcNow;

                //添加用戶的信息,轉成一組聲明,還可以寫入更多用戶信息聲明
                var claims = new Claim[]
                {
                    
                    //聲明主題
                    new Claim(JwtRegisteredClaimNames.Sub, name),
                    //JWT ID 唯一標識符
                    new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
                    //發佈時間戳 issued timestamp
                    new Claim(JwtRegisteredClaimNames.Iat, now.ToUniversalTime().ToString(), ClaimValueTypes.Integer64)
                };

                //下麵使用 Microsoft.IdentityModel.Tokens幫助庫下的類來創建JwtToken

                //安全秘鑰
                var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_settings.Value.Secret));

                //生成jwt令牌(json web token)
                var jwt = new JwtSecurityToken(
                    //jwt發行方
                    issuer: _settings.Value.Iss,
                    //jwt訂閱者
                    audience: _settings.Value.Aud,
                    //jwt一組聲明
                    claims: claims,
                    notBefore: now,
                    //jwt令牌過期時間
                    expires: now.Add(TimeSpan.FromMinutes(2)),
                    //簽名憑證: 安全密鑰、簽名演算法
                    signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256)
                );
                //序列化jwt對象,寫入一個字元串encodedJwt
                var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);

                var responseJson = new
                {
                    access_token = encodedJwt,
                    expires_in = (int)TimeSpan.FromMinutes(2).TotalSeconds
                };
                //以json形式返回
                return Json(responseJson);
            }
            else
            {
                return Json("");
            }
        }
    }

  

  在之前講IS4的第55篇中,講ResourceOwnerPasswords項目,獲取token也是要發送用戶名和密碼,那是由is4來完成,包括自動:驗證用戶,生成jwtToken。這裡由System.IdentityModel.Tokens類庫來生成jwtToken。最後返回jwt令牌token給用戶。

  當catcher用戶請求:http://localhost:9009/api/auth?name=catcher&pwd=123服務時,產生jwt令牌token,下麵是換了行的Token, 如下所示:

{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiJjYXRjaGVyIiwianRpIjoiZWJmNWIyZGItNDg5YS00OTBjLTk0NjUtODZmOTE5YWEzMDRjIiwiaWF0IjoiMjAxOS80LzI1IDE6NTc6MjAiLCJuYmYiOjE1NTYxNTc0NDAsImV4cC
  I6MTU1NjE1NzU2MCwiaXNzIjoiaHR0cDovL3d3dy5jLXNoYXJwY29ybmVyLmNvbS9tZW1iZXJzL2NhdGNoZXItd29uZyIsImF1ZCI6IkNhdGNoZXIgV29uZyJ9
.O2jI7NSnothl9Agbr0VhmdoBsXhDEoxkYNOuGaSEkkg
","expires_in":120}

   簡單瞭解下JWT(JSON Web Token),它是在Web上以JSON格式傳輸的Token。該Token被設計為緊湊聲明表示格式,意味著位元組少,它可以在GET URL中,Header中,Post Parameter中進行傳輸。

  JWT一般由三段構成(Header.Payload.Signature),用"."號分隔開,是base64編碼的,可以把該字元串放到https://jwt.io/中進行查看,如下所示:

  在Header中:alg:聲明加密的演算法,這裡為HS256。typ:聲明類型,這裡為JWT。

  在Payload中:  

    sub: 主題, jwt發佈者名稱。

    jti: jwt的唯一身份標識,主要用來作為一次性token,從而迴避重放攻擊。也就是請求生成的token不一樣。

    iat: 簽發時間

    nbf: 在什麼時間之前,該jwt都是不可用的,是時間戳格式。

    exp:jwt的過期時間,這個過期時間必須要大於簽發時間。

    adu: 訂閱者,接收jwt的一方。

    iss:  jwt的發行方。

  Signature(數字簽名,防止信息被篡改):

    包含了:base64後的Header,Payload ,Secret,secret就是用來進行jwt的簽發和jwt的驗證。相當於服務端的私鑰。該secret在示例中,用在AuthServer和CustomerAPIServices項目中。

 

三. CustomerAPIServices項目

  在該web api 項目中啟用身份驗證來保護api服務,使用JwtBearer,將預設的身份驗證方案設置為TestKey。添加身份驗證代碼如下:

   public void ConfigureServices(IServiceCollection services)
        {
            //獲取當前用戶(訂閱者)信息
            var audienceConfig = Configuration.GetSection("Audience");

            //獲取安全秘鑰
            var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(audienceConfig["Secret"]));
            
            //token要驗證的參數集合
            var tokenValidationParameters = new TokenValidationParameters
            {
                //必須驗證安全秘鑰
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = signingKey,

                //必須驗證發行方
                ValidateIssuer = true,
                ValidIssuer = audienceConfig["Iss"],

                //必須驗證訂閱者
                ValidateAudience = true,
                ValidAudience = audienceConfig["Aud"],

                //是否驗證Token有效期,使用當前時間與Token的Claims中的NotBefore和Expires對比
                ValidateLifetime = true,
                // 允許的伺服器時間偏移量
                ClockSkew = TimeSpan.Zero,
                //是否要求Token的Claims中必須包含Expires
                RequireExpirationTime = true,
            };
            //添加服務驗證,方案為TestKey
            services.AddAuthentication(o =>
            {
                o.DefaultAuthenticateScheme = "TestKey";
            })
            .AddJwtBearer("TestKey", x =>
             {
                 x.RequireHttpsMetadata = false;
                 ////在JwtBearerOptions配置中,IssuerSigningKey(簽名秘鑰)、ValidIssuer(Token頒發機構)、ValidAudience(頒發給誰)三個參數是必須的。
                 x.TokenValidationParameters = tokenValidationParameters;
             });

            services.AddMvc();
        }

  

  新建一個CustomersController類,在api方法中使用Authorize屬性。

   [Route("api/[controller]")]
    public class CustomersController : Controller
    {
        //Authorize]:加了該標記,當用戶請求時,需要發送有效的jwt
        [Authorize]
        [HttpGet]
        public IEnumerable<string> Get()
        {
            return new string[] { "Catcher Wong", "James Li" };
        }

        //未加授權標記,不受保護,任何用戶都可以獲取
        [HttpGet("{id}")]
        public string Get(int id)
        {
            return $"Catcher Wong - {id}";
        }
    }

   下麵運行,在瀏覽器中直接訪問http://localhost:9001/api/customers 報http 500錯誤,而訪問http://localhost:9001/api/customers/1 則成功http 200,顯示“Catcher Wong - 1”

 

四. APIGateway網關

  添加認證服務,基本與CustomerAPIServices項目中的認證服務一樣。代碼如下:

  public void ConfigureServices(IServiceCollection services)
        {
            //獲取當前用戶(訂閱者)信息
            var audienceConfig = Configuration.GetSection("Audience");
            //獲取安全秘鑰
            var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(audienceConfig["Secret"]));
            
            //token要驗證的參數集合
            var tokenValidationParameters = new TokenValidationParameters
            {
                //必須驗證安全秘鑰
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = signingKey,

                //必須驗證發行方
                ValidateIssuer = true,
                ValidIssuer = audienceConfig["Iss"],

                //必須驗證訂閱者
                ValidateAudience = true,
                ValidAudience = audienceConfig["Aud"],

                //是否驗證Token有效期,使用當前時間與Token的Claims中的NotBefore和Expires對比
                ValidateLifetime = true,

                // 允許的伺服器時間偏移量
                ClockSkew = TimeSpan.Zero,

                //是否要求Token的Claims中必須包含Expires
                RequireExpirationTime = true,
            };
            //添加服務驗證,方案為TestKey
            services.AddAuthentication(o =>
            {
                o.DefaultAuthenticateScheme = "TestKey";
            })
            .AddJwtBearer("TestKey", x =>
             {
                 x.RequireHttpsMetadata = false;
                 //在JwtBearerOptions配置中,IssuerSigningKey(簽名秘鑰)、ValidIssuer(Token頒發機構)、ValidAudience(頒發給誰)三個參數是必須的。
                 x.TokenValidationParameters = tokenValidationParameters;
             });
            //這裡也可以使用IS4承載令牌
            /*
             var authenticationProviderKey = "TestKey";
             Action<IdentityServerAuthenticationOptions> options = o =>
            {
                 o.Authority = "https://whereyouridentityserverlives.com";
                 o.ApiName = "api";
                 o.SupportedTokens = SupportedTokens.Both;
                 o.ApiSecret = "secret";
             };
            services.AddAuthentication()
                .AddIdentityServerAuthentication(authenticationProviderKey, options);
            */


            //添加Ocelot網關服務時,包括Secret秘鑰、Iss發佈者、Aud訂閱者
            services.AddOcelot(Configuration);
        }

  在IS4中是由Authority參數指定OIDC服務地址,OIDC可以自動發現Issuer, IssuerSigningKey等配置,而o.Audience與x.TokenValidationParameters = new TokenValidationParameters { ValidAudience = "api" }是等效的。

   下麵應該修改configuration.json文件。添加一個名為AuthenticationOptions的新節點,並使AuthenticationProviderKey與我們在Startup類中定義的相同。

        "ReRoutes": [
      {
        "DownstreamPathTemplate": "/api/customers",
        "DownstreamScheme": "http",
        "DownstreamHostAndPorts": [
          {
            "Host": "localhost",
            "Port": 9001
          }
        ],
        "UpstreamPathTemplate": "/customers",
        "UpstreamHttpMethod": [ "Get" ],
        "AuthenticationOptions": {
          "AuthenticationProviderKey": "TestKey",
          "AllowedScopes": []
        }
      }

  APIGateway網關項目和CustomerAPIServices項目的appsettings.json文件,都配置了訂閱者信息如下:

{
    "Audience": {
        "Secret": "Y2F0Y2hlciUyMHdvbmclMjBsb3ZlJTIwLm5ldA==",
        "Iss": "http://www.c-sharpcorner.com/members/catcher-wong",
        "Aud": "Catcher Wong"
    }
}

 

五. ClientApp項目

  最後使用的客戶端應用程式,來模擬API網關的一些請求。首先,我們需要添加一個方法來獲取access_token。

        /// <summary>
        /// 獲取jwtToken
        /// </summary>
        /// <returns></returns>
        private static string GetJwt()
        {
            HttpClient client = new HttpClient();

            //9000是網關,會自動轉發到下游伺服器,
            client.BaseAddress = new Uri( "http://localhost:9000");
            client.DefaultRequestHeaders.Clear();

            //轉發到AuthServer的9009
            var res2 = client.GetAsync("/api/auth?name=catcher&pwd=123").Result;

            dynamic jwt = JsonConvert.DeserializeObject(res2.Content.ReadAsStringAsync().Result);

            return jwt.access_token;
        }

  接著,編寫了三段代碼 , 通過API Gateway網關, 來訪問CustomerAPIServices項目中的api服務:

    static void Main(string[] args)
        {
            HttpClient client = new HttpClient();

            client.DefaultRequestHeaders.Clear();
            client.BaseAddress = new Uri("http://localhost:9000");

            // 1. 需要授權的api訪問,沒有token時,返回http狀態401
            var resWithoutToken = client.GetAsync("/customers").Result;

            Console.WriteLine($"Sending Request to /customers , without token.");
            Console.WriteLine($"Result : {resWithoutToken.StatusCode}");

            //2. 需要授權的api訪問,獲取令牌請求api,返回http狀態200正常
            client.DefaultRequestHeaders.Clear();
            Console.WriteLine("\nBegin Auth....");
            var jwt = GetJwt();
            Console.WriteLine("End Auth....");
            Console.WriteLine($"\nToken={jwt}");

            client.DefaultRequestHeaders.Add("Authorization", $"Bearer {jwt}");
            var resWithToken = client.GetAsync("/customers").Result;

            Console.WriteLine($"\nSend Request to /customers , with token.");
            Console.WriteLine($"Result : {resWithToken.StatusCode}");
            Console.WriteLine(resWithToken.Content.ReadAsStringAsync().Result);

            //3.不需要授權的api訪問,返回http狀態200正常
            Console.WriteLine("\nNo Auth Service Here ");
            client.DefaultRequestHeaders.Clear();
            var res = client.GetAsync("/customers/1").Result;

            Console.WriteLine($"Send Request to /customers/1");
            Console.WriteLine($"Result : {res.StatusCode}");
            Console.WriteLine(res.Content.ReadAsStringAsync().Result);

            Console.Read();
        }

  

  參考文獻

    在ASP.NET核心中使用Ocelot構建API網關 - 身份驗證

      官方文檔


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文主要目的在於實現一個後臺心跳廣播包,所有連接到 SignalR 的客戶端,通過訂閱心跳包廣播頻道,能夠自動收到伺服器發送的心跳廣播 ...
  • 什麼是NPOI ? 簡而言之,NPOI就是可以在沒有Office的情況下對Word或Excel文檔進行讀寫等操作。 使用方式 : 1、準備NPOI的dll文件 下載鏈接:https://npoi.codeplex.com/releases 2、將下載的dll文件引入項目中 3、引用命名空間 須知: ...
  • 中間件是被用到管道(pipeline)上來處理請求(request)和響應的(response)。 asp.net core 本身提供了一些內置的中間件,但是有一些場景,你可能會需要寫一些自定義的中間件。 1. 創建一個使用匿名委托的中間件組件的形式 註:app.use中return next()會 ...
  • 異常內容 解決方案 思考:瘋狂Google,找到 https://stackoverflow.com/questions/47822177/swagger net core api ambiguous http action debugging, 但是底下沒有有價值的回答。自己琢磨一下,比起一個標準 ...
  • 異常內容 System.TypeLoadException HResult=0x80131522 Message=Method 'get_IsTraceEnabled' in type 'Abp.Castle.Logging.Log4Net.Log4NetLogger' from assembly ...
  • 最近, 我在用C#開發一個EXCEL Add-In的時候,發現了一些害人不淺的坑,特來總結列舉如下: 這裡我讀寫EXCEL引用的是using Excel = Microsoft.Office.Interop.Excel; 問題一 如何判斷一個單元格去除首尾空格後是不是空的 在獲取單元格內的文本內容進 ...
  • 在前後端分離的大環境下,API介面文檔成為了前後端交流的一個重點。Swagger讓開發人員擺脫了寫介面文檔的痛苦。 官方網址:https://swagger.io/ 在.Net Core WebApi中通過簡單配置即可使用這一強大的功能。 1.新建一個API的項目 選擇 API 項目 2.引入Swa ...
  • 通過使用Http定義的伺服器的交互方法,進行更新與刪除。 PUT通常用於向伺服器發送請求,如果URI不存在,則要求伺服器根據請求創建資源,如果存在,伺服器就接受請求內容,並修改URI資源的原始版本。如果新的資源被創建,這個原始伺服器就必須通過201(Created)響應通知用戶代理。如果已有... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...