以前咱們在 Ocelot 網關的基礎上自定義了一個認證受權的 Ocelot 中間件,根據請求的路徑和 Method 進行匹配,找到對應的權限配置,並判斷是否能夠擁有訪問資源的角色,若是沒有則返回 401/403,若是有權限則轉發到下游服務。html
原來的匹配方式是首先根據請求路徑和方法徹底匹配,若是匹配不到則嘗試使用正則匹配。git
咱們此次要作的就是將原來的正則匹配替換成 Ocelot 內部的路由匹配方式,這樣咱們在配置的時候就再也不須要配置兩套了,一邊寫 Ocelot 路由的配置,一邊寫權限的配置,這樣能減小很多工做量github
咱們想使用 Ocelot 的路由匹配,首先應該瞭解 Ocelot 的執行過程,而後找到對應的路由匹配的地方,看路由匹配使用到了哪個服務,用這個服務在咱們本身的業務邏輯裏匹配便可。api
先來看一下 Ocelot 的服務註冊,Ocelot 的服務註冊async
能夠看到主要的服務註冊代碼應該在 OcelotBuilder
中,查看 OcelotBuilder
https://github.com/ThreeMammals/Ocelot/blob/13.5.2/src/Ocelot/DependencyInjection/OcelotBuilder.csui
能夠看到,Ocelot 的服務註冊都在這裏, Ocelot 內部好多都是基於接口的,因此須要找對應的實現的話能夠看它的服務註冊是註冊的哪個服務便可。url
簡單分析一下,Ocelot 的路由匹配過程必定在尋找下游地址的時候,根據上游的請求信息(直接請求網關的請求)匹配,因此咱們首先找到 DownstreamRouteFinderMiddleware
https://github.com/ThreeMammals/Ocelot/blob/13.5.2/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs3d
由上面的代碼,咱們能夠看到,下游路由地址是經過 IDownstreamRouteFinder
來找下游路由的,轉到對應的實現代碼: https://github.com/ThreeMammals/Ocelot/blob/13.5.2/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cscode
這裏咱們能夠看到是經過 IUrlPathToUrlTemplateMatcher
來進行路由匹配的,因此咱們須要用到這個服務,而後看這個 Match
方法的參數,前兩個參數比較明確,第一個參數是上游請求的地址,第二個參數是請求的 queryString,第三個參數則是 Ocelot 內部構建出來的路由模板信息 UpstreamPathTemplate
,而後咱們就須要知道怎麼構建一個 UpstreamPathTemplate
對象,繼續探索htm
直接看 UpstreamPathTemplate
,表示一臉懵逼,不知道怎麼構建, 全局搜素了一下,發現有一個 IUpstreamTemplatePatternCreator
裏面定義了一個 Create
的方法
這個方法看上去簡單了好多,查看 IReRoute
的定義 https://github.com/ThreeMammals/Ocelot/blob/13.5.2/src/Ocelot/Configuration/File/IReRoute.cs
咱們只須要根據路徑模板構建一個 IReRoute
對象便可,Ocelot 中有一個實現了 IReRoute
的類 FileReRoute
,可是感受有些複雜,就沒有用,自定義了一個類型實現了 IReRoute
自此,咱們就已經找到了要使用 Ocelot 路由匹配所須要的服務了:
IUrlPathToUrlTemplateMatcher
IUpstreamTemplatePatternCreator
上面提到了咱們沒有使用 FileReRoute
對象,因此咱們就須要自定義一個 IReRoute
對象:
private class FakeReRoute : IReRoute { public string UpstreamPathTemplate { get; set; } public bool ReRouteIsCaseSensitive { get; set; } public int Priority { get; set; } }
使用 Ocelot 路由匹配示例:
public class UrlBasedAuthenticationMiddleware : Ocelot.Middleware.OcelotMiddleware { private readonly GatewayOptions _gatewayOptions; private readonly IMemoryCache _memoryCache; private readonly OcelotRequestDelegate _next; private readonly IUrlPathToUrlTemplateMatcher _urlTemplateMatcher; private readonly IUpstreamTemplatePatternCreator _templatePatternCreator; public UrlBasedAuthenticationMiddleware(OcelotRequestDelegate next, IOptions<GatewayOptions> options, IMemoryCache memoryCache, IOcelotLoggerFactory loggerFactory, IUrlPathToUrlTemplateMatcher urlTemplateMatcher, IUpstreamTemplatePatternCreator templatePatternCreator) : base(loggerFactory.CreateLogger<UrlBasedAuthenticationMiddleware>()) { _next = next; _gatewayOptions = options.Value; _memoryCache = memoryCache; _urlTemplateMatcher = urlTemplateMatcher; _templatePatternCreator = templatePatternCreator; } public async Task Invoke(DownstreamContext context) { var permissions = await _memoryCache.GetOrCreateAsync(_gatewayOptions.ApiPermissionsCacheKey, async entry => { using (var conn = new SqlConnection(_gatewayOptions.PermissionsConnectionString)) { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1); return (await conn.QueryAsync<ApiPermission>(@"")).Select(_ => _.GetViewModel()).ToArray(); } }); var request = context.HttpContext.Request; var permission = permissions.FirstOrDefault(p => request.Path.Value.Equals(p.PathPattern, StringComparison.OrdinalIgnoreCase) && p.Method == request.Method.ToUpper()); if (null == permission) { permission = permissions.FirstOrDefault(p => p.Method == request.Method.ToUpper() && _urlTemplateMatcher.Match(request.Path.Value, request.QueryString.Value, _templatePatternCreator.Create(new FakeReRoute() { UpstreamPathTemplate = p.PathPattern })).Data.Match ); } // ... await _next.Invoke(context); } private class FakeReRoute : IReRoute { public string UpstreamPathTemplate { get; set; } public bool ReRouteIsCaseSensitive { get; set; } public int Priority { get; set; } } }
這樣,apiPermission 的 Path 配置基本可使用和 Ocelot 配置同樣的路由,能夠更方便的配置,避免 996 咯