第七節:基於Ocelot網關層的微服務校驗(手寫jwt校驗中間件和利用IdentityModel.Tokens.Jwt校驗)


一. Ocelot+jwt 方案1

本節架構圖:

 

 

 

 

1. 方案背景

  截至目前,我們已經完成了可以通過Ocelot轉發請求給業務服務器了,但現在還有一項工作沒有做,那就是身份校驗,當然我們可以直接寫在業務服務器上,但是業務服務器會非常多,不利於維護,所以最佳的寫法是寫在Ocelot網關上,讓Ocelot進行校驗,校驗通過了,才進行轉發給業務服務器,並且業務服務器可能是在內網,外部的客戶端無法直接訪問。

2. 涉及到的項目

  GateWay1、RequestTokenServer1、GoodsService、OrderService

  用到的程序集:【Ocelot 16.0.1】【Ocelot.Provider.Consul 16.0.1】【JWT 7.2.1】

3. 設計思路

 (1). RequestTokenServer1是認證服務器,提供GetAccessToken接口,客戶端通過賬號和密碼訪問,驗證通過后返回授權碼token。

 代碼如下:

    [Route("api/[controller]/[action]")]
    [ApiController]
    public class AuthController : ControllerBase
    {
        public IConfiguration Configuration;
        public AuthController(IConfiguration configuration)
        {
            Configuration = configuration;
        }
        /// <summary>
        /// 獲取請求Token
        /// </summary>
        /// <param name="account"></param>
        /// <param name="pwd"></param>
        /// <returns></returns>
        [HttpGet]
        public string GetAccessToken(string account, string pwd)
        {
            if (account != "admin" && pwd != "123456")
            {
                return JsonHelp.ToJsonString(new
                {
                    status = "error",
                    msg = "獲取失敗",
                    token = ""
                });
            }
            string secretKey = Configuration["SecretKey"];

            //1.加密
            //1.1 額外的header參數也可以不設置
            var extraHeaders = new Dictionary<string, object>
                    {
                         {"myName", "ypf" },
                    };
            //過期時間(可以不設置,下面表示簽名后 20分鍾過期)
            double exp = (DateTime.UtcNow.AddMinutes(20) - new DateTime(1970, 1, 1)).TotalSeconds;
            //進行組裝
            var payload = new Dictionary<string, object>
                    {
                         {"userId", "00000000001" },
                         {"userAccount", account },
                         {"exp",exp }
                    };

            //1.2 進行JWT簽名
            var myToken = JWTHelp.JWTJiaM(payload, secretKey, extraHeaders);

            return JsonHelp.ToJsonString(new
            {
                status = "ok",
                msg = "獲取成功",
                token = myToken
            });
        }

 (2). GoodsService和OrderService分別以7001 和 7004端口啟動,注冊到Consul中。

 (3). 新建GateWay1為網關服務器,這里主要使用路由功能進行演示,用於將客戶端的請求轉發給 GoodsService和OrderService 。

配置文件如下:

//模式三:將Ocelot與consul結合處理,在consul中已經注冊業務服務器地址,在Ocelot端不需要再注冊了(推薦用法)
{
  "Routes": [
    {
      "DownstreamPathTemplate": "/api/{url}",
      "DownstreamScheme": "http",
      "ServiceName": "GoodsService", //Consul中的服務名稱
      "LoadBalancerOptions": {
        "Type": "RoundRobin" //輪詢算法:依次調用在consul中注冊的服務器
      },
      "UseServiceDiscovery": true, //啟用服務發現(可以省略,因為會默認賦值)
      "UpstreamPathTemplate": "/GoodsService/{url}",
      "UpstreamHttpMethod": [ "Get", "Post" ]
    },
    {
      "DownstreamPathTemplate": "/api/{url}",
      "DownstreamScheme": "http",
      "ServiceName": "OrderService",
      "LoadBalancerOptions": {
        "Type": "LeastConnection" //最小連接數算法
      },
      "UseServiceDiscovery": true,
      "UpstreamPathTemplate": "/OrderService/{url}",
      "UpstreamHttpMethod": [ "Get", "Post" ]
    }
  ],
  //下面是配置Consul的地址和端口
  "GlobalConfiguration": {
    //對應Consul的ip和Port(可以省略,因為會默認賦值)
    "ServiceDiscoveryProvider": {
      "Host": "127.0.0.1",
      "Port": 8500
    }
  }
}

 (4). 核心:在GateWay1的Configure管道中 自定義jwt校驗中間件,eg: app.UseMiddleware<JwtSafeMiddleware>(); 該中間件中攔截Get和Post請求,獲取Header中的token,然后進行jwt校驗,校驗通過,則await _next.Invoke(context);繼續走后面的中間件;校驗不通過則 返回401未授權。

代碼如下:

 public class JwtSafeMiddleware
    {
        private readonly RequestDelegate _next;
        public IConfiguration _configuration;
        public JwtSafeMiddleware(RequestDelegate next, IConfiguration configuration)
        {
            _next = next;
            _configuration = configuration;
        }
       
        public async Task Invoke(HttpContext context)
        {
            //表示如果RequestTokenServer1配置在網關下,則訪問它獲取token的請求不走jwt校驗哦
            //if(!context.Request.Path.Value.StartsWith("/auth")) 

            if (context.Request.Method == "GET" || context.Request.Method == "POST")
            {
                string myToken = context.Request.Headers["token"].FirstOrDefault();
                if (string.IsNullOrEmpty(myToken))
                {
                    context.Response.StatusCode = 401; //401未授權
                    await context.Response.WriteAsync("token為空");
                    return;
                }
                //校驗auth的正確性
                var result = JWTHelp.JWTJieM(myToken, _configuration["SecretKey"]);
                if (result == "expired")
                {
                    context.Response.StatusCode = 401; //401未授權
                    await context.Response.WriteAsync("非法請求,參數已經過期");
                    return;
                }
                else if (result == "invalid")
                {
                    context.Response.StatusCode = 401; //401未授權
                    await context.Response.WriteAsync("非法請求,未通過校驗");
                    return;
                }
                else if (result == "error")
                {
                    context.Response.StatusCode = 401; //401未授權
                    await context.Response.WriteAsync("非法請求,未通過校驗");
                    return;
                }
                else
                {
                    //表示校驗通過
                    
                }

            }
            await _next.Invoke(context);
        }
  }

PS:

  這里RequestTokenServer1認證服務器沒有配置在GateWay1網關下,客戶端可以直接訪問該認證服務器。 當然,配置在網關下也行(eg: /auth/{url}代表認證server),那么只是需要在JwtSafeMiddleware中間件中加一層判斷,比如if(!context.Request.Path.Value.StartsWith("/auth")) 即可。

4. 用PostMan充當客戶端進行測試

 (1).通過【consul.exe agent -dev】啟動Consul。

 (2).分別啟動業務服務器:【dotnet GoodsService.dll --urls="http://*:7001" --ip="127.0.0.1" --port=7001 】

              【dotnet OrderService.dll --urls="http://*:7004" --ip="127.0.0.1" --port=7004】

  啟動網關服務器:【dotnet GateWay1.dll --urls="http://*:7030" --ip="127.0.0.1" --port=7030】

  啟動認證服務器:【dotnet RequestTokenServer1.dll --urls="http://*:7031" --ip="127.0.0.1" --port=7031】

 (3).測試1:

  用PostMan以Get請求訪問:http://127.0.0.1:7030/GoodsService/Catalog/GetMsg 報401,錯誤內容為“token為空”

  用PostMan以Post且表單提交的方式訪問:http://127.0.0.1:7030/OrderService/Buy/pOrder1 報401,錯誤內容為“token為空”。

 (4). 測試2:

  用PostMan以Get請求訪問:http://127.0.0.1:7031/api/Auth/GetAccessToken?account=admin&pwd=123456 獲取返回值中的token,然后把該token值放到header中,即token =“xxxx”, 然后再

 

  用PostMan以Get請求訪問:http://127.0.0.1:7030/GoodsService/Catalog/GetMsg 訪問成功

  用PostMan以Post且表單提交的方式訪問:http://127.0.0.1:7030/OrderService/Buy/pOrder1 訪問成功

 

 

二. Ocelot+jwt 方案2

  利用【System.IdentityModel.Tokens.Jwt】程序集寫的那套,由於需要加[Authorize],這個驗證只能寫在各個業務服務器上, 不是很好,此處不再重復了,如果需要,參照:https://www.cnblogs.com/yaopengfei/p/12162507.html

 

   后續將介紹 Ocelot+ID4 做授權校驗

 

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鵬飛)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 聲     明1 : 如有錯誤,歡迎討論,請勿謾罵^_^。
  • 聲     明2 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。
 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM