學習筆記---- Ocelot中使用Consul+Polly+JWT實現簡單的服務發現與治理以及身份認證

 

前言:上一篇中實現了consul的服務註冊與發現,現在可以把網關係統進行一次升級改造,實現簡單的服務發現與治理以及身份認證

一,Consul服務發現的引用與配置


1,使用NuGet安裝Ocelot.Provider.Consul:

 

 

 

2,使用Startup.cs進行註冊:

public void ConfigureServices(IServiceCollection services)
        {
            
            //添加ocelot服務
            services.AddOcelot()
                    //服務發現
                    .AddConsul();
                  
        }

 


3,修改ocelot.json配置文件:

{
  "Routes": [
    {
      /*下游地址*/
      "DownstreamPathTemplate": "/product/{xx}",
      /*請求方式*/
      "DownstreamScheme": "http",
      /*服務名稱*/
      "ServiceName": "productsevrice",
      /*下游服務的主機和端口*/
      //"DownstreamHostAndPorts": [
      //  {
      //    "Host": "localhost",
      //    "Port": 4001
      //  },
      //  {
      //    "Host": "localhost",
      //    "Port": 4002
      //  }
      //], 
      /*上游路徑地址*/
      "UpstreamPathTemplate": "/api/product/{xx}",
      "UpstreamHttpMethod": [ "Get", "Post", "Put", "Delete" ], /*請求方式*/
      /*負載均衡:RoundRobin - 循環訪問,
        NoLoadBalancer - 從配置或服務發現中獲取第一個可用服務,
       "LeastConnection" 請求最少的服務發送新請求,
        CookieStickySessions - 使用 cookie 將所有請求粘貼到特定服務器
      "*/
      "LoadBalancerOptions": {
        "Type": "LeastConnection" /*請求最少的服務發送新請求*/
      }
      
    }
  ],
  "GlobalConfiguration": {
    /*ocelot網關將要運行的地址*/
    "BaseUrl": "http://localhost:4000",
    "ServiceDiscoveryProvider": {
      "Scheme": "http",
      "Host": "localhost",
      "Port": 8500,
      "Type": "Consul"
    }
  }
}


 

  使用consul服務發現相關配置,在全局GlobalConfiguration增加ServiceDiscoveryProvider, 移除DownstreamHostAndPorts節點。

二,Polly服務治理的引用與配置

服務治理是爲了提高服務質量以及可用性,緩存,限流,熔斷,負載均衡等等都算,實際使用中按需實現即可
1,超時/熔斷:
超時即網關請求服務時的最長響應時間,熔斷某個服務的請求異常次數達到一定量時不對其繼續請求
(1)添加Ocelot.Provider.Polly
(2)修改Startup配置  

  public void ConfigureServices(IServiceCollection services)
        {
            
            //添加ocelot服務
            services.AddOcelot()
                    //服務發現
                    .AddConsul()
                   //添加Polly(防止熔斷)
                    .AddPolly();
        }

 

(3)修改Ocelot.json

      /*熔斷/超時 
      超時就是網關請求服務時可容忍的最長響應時間。
      熔斷的意思就是當請求某個服務的異常次數達到一定量時,
      那麼網關在一定時間內就不再對這個服務發起請求了,直接熔斷*/
      "QoSOptions": {
        "ExceptionsAllowedBeforeBreaking": 3, //發生錯誤的次數
        "DurationOfBreak": 10000, //熔斷時間
        "TimeoutValue": 5000 //請求超過 5 秒,它將自動超時。
      }

 

2,緩存/限流

 (1) 添加 Ocelot.Cache.CacheManager

 (2)修改配置:

       public void ConfigureServices(IServiceCollection services)
        {
            
            //添加ocelot服務
            services.AddOcelot()
                    //服務發現
                    .AddConsul()
                   //添加Polly(防止熔斷)
                   .AddPolly()
                   //添加緩存,限流
                   .AddCacheManager(x =>
                   {
                       x.WithDictionaryHandle();
                   });
        }

 

Ocelot.json配置修改:

 /*緩存*/
      "FileCacheOptions": {
        "TtlSeconds": 5, //過期時間
        "Region": "productcache"
      },
      /*限流*/
      "RateLimitOptions": {
        "ClientWhitelist": [ "SuperClient" ], //白名單中的客戶端可以不受限流的影響
        "EnableRateLimiting": true, //是否限流
        "Period": "5s", //限流的單位時間
        "PeriodTimespan": 2, //請求上限多少秒後可以重試
        "Limit": 1 //定義的時間內可以發出的最大請求數
      }

 

最後修改完成之後的Ocelot.json:

{
  "Routes": [
    {
      /*下游地址*/
      "DownstreamPathTemplate": "/product/{xx}",
      /*請求方式*/
      "DownstreamScheme": "http",
      /*服務名稱*/
      "ServiceName": "productsevrice",
      /*下游服務的主機和端口*/
      //"DownstreamHostAndPorts": [
      //  {
      //    "Host": "localhost",
      //    "Port": 4001
      //  },
      //  {
      //    "Host": "localhost",
      //    "Port": 4002
      //  }
      //], 
      /*上游路徑地址*/
      "UpstreamPathTemplate": "/api/product/{xx}",
      "UpstreamHttpMethod": [ "Get", "Post", "Put", "Delete" ], /*請求方式*/
      /*負載均衡:RoundRobin - 循環訪問,
        NoLoadBalancer - 從配置或服務發現中獲取第一個可用服務,
       "LeastConnection" 請求最少的服務發送新請求,
        CookieStickySessions - 使用 cookie 將所有請求粘貼到特定服務器
      "*/
      "LoadBalancerOptions": {
        "Type": "LeastConnection" /*請求最少的服務發送新請求*/
      },
      /*緩存*/
      "FileCacheOptions": {
        "TtlSeconds": 5, //過期時間
        "Region": "productcache"
      },
      /*限流*/
      "RateLimitOptions": {
        "ClientWhitelist": [ "SuperClient" ], //白名單中的客戶端可以不受限流的影響
        "EnableRateLimiting": true, //是否限流
        "Period": "5s", //限流的單位時間
        "PeriodTimespan": 2, //請求上限多少秒後可以重試
        "Limit": 1 //定義的時間內可以發出的最大請求數
      },
      /*熔斷/超時 
      超時就是網關請求服務時可容忍的最長響應時間。
      熔斷的意思就是當請求某個服務的異常次數達到一定量時,
      那麼網關在一定時間內就不再對這個服務發起請求了,直接熔斷*/
      "QoSOptions": {
        "ExceptionsAllowedBeforeBreaking": 3, //發生錯誤的次數
        "DurationOfBreak": 10000, //熔斷時間
        "TimeoutValue": 5000 //請求超過 5 秒,它將自動超時。
      }
    },
    {
      "DownstreamPathTemplate": "/order/{xx}",
      /*請求方式*/
      "DownstreamScheme": "http",
      /*下游服務的主機和端口*/
      "ServiceName": "orderservice",
      "LoadBalancerOptions": {
        "Type": "LeastConnection"
      },
      "UpstreamPathTemplate": "/api/order/{xx}",
      "UpstreamHttpMethod": [ "Get", "Post" ]
    }
  ],
  "GlobalConfiguration": {
    /*ocelot網關將要運行的地址*/
    "BaseUrl": "http://localhost:4000",
    "ServiceDiscoveryProvider": {
      "Scheme": "http",
      "Host": "localhost",
      "Port": 8500,
      "Type": "Consul"
    },
    "RateLimitOptions": {
      "DisableRateLimitHeaders": false, //是否禁用X-Rate-Limit和Retry-After標頭
      "QuotaExceededMessage": "{\"res_code\":\"999\",\"res_msg\":\"請求中\"}", //請求達到上限時返回
      "HttpStatusCode": 999, //HTTP狀態代碼
      "ClientIdHeader": "Test"
    }
  }
}

 

三,JWT的引用配置與認證
1 ,第一步老規矩Nugget 獲取JWT

2,添加了一個幫助類:

public class JWtHelper
    {
/*生成token*/
public static string JwtEncrypt(string phone, string uid, string openid, string encrypt_key) { var token = ""; try { var claims = new[] { new Claim("openid", openid), new Claim("phone", phone), new Claim("uid", uid),}; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(encrypt_key)); var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var jwtToken = new JwtSecurityToken("MER", "API", claims, expires: DateTime.Now.AddYears(1), signingCredentials: credentials); token = new JwtSecurityTokenHandler().WriteToken(jwtToken); } catch (Exception) { throw; } return token; } /*解析token*/ public static string JwtDecrypt(string token, string encrypt_key) { var josn = ""; try { var param = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(encrypt_key)), ValidateIssuer = true, ValidIssuers = new string[] { "ASD123", "MER"}, ValidateAudience = true, ValidAudience = "API", ValidateLifetime = true, ClockSkew = TimeSpan.FromMinutes(5) }; SecurityToken security; var principal = new JwtSecurityTokenHandler().ValidateToken(token, param, out security); if (principal != null) { var exp = principal.Claims.FirstOrDefault(c => c.Type.Equals("exp"))?.Value; var uid = principal.Claims.FirstOrDefault(c => c.Type.Equals("uid"))?.Value; var phone = principal.Claims.FirstOrDefault(c => c.Type.Equals("phone"))?.Value; var openid = principal.Claims.FirstOrDefault(c => c.Type.Equals("openid"))?.Value; josn = JsonConvert.SerializeObject(new { openid, phone, uid, exp }); } } catch (Exception ex) { return null; } return josn; } }

 

ps:這個幫助類只是爲了看看效果,測試類隨便編寫能不能再生產環境使用自行考慮。

3,添加Jwt中間件

 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)
        {
            //請求不走jwt校驗的一種方式:識別url
            //if(!context.Request.Path.Value.StartsWith("/auth")) 

            context.Response.ContentType = "application/json";

            if (context.Request.Method == "GET" || context.Request.Method == "POST")
            {
                string token = context.Request.Headers["token"].FirstOrDefault();
                if (string.IsNullOrEmpty(token))
                {
                    context.Response.StatusCode = 401; //401未授權              
                    var json = JsonConvert.SerializeObject(new { res_code = 401, res_msg = "token爲空!" });
                    await context.Response.WriteAsync(json);
                    return;
                }
                //校驗auth的正確性
                var result = JWtHelper.JwtDecrypt(token, _configuration["SecretKey"]);
                if (result == "expired")
                {
                    context.Response.StatusCode = 666; 
                    var json = JsonConvert.SerializeObject(new { res_code = 666, res_msg = "參數已經過期!" });
                    await context.Response.WriteAsync(json);
                    return;
                }                
                else
                {
                    //校驗通過
                }

            }
            await _next.Invoke(context);
        }
    }

最後在Startup中的Configure方法註冊:app.UseMiddleware<JwtSafeMiddleware>();

 

 

下一篇 微服務中的CAP

 

 

 

--------to be continue --------



 

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