一. 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 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。