使用 Ocelot 匹配路由的方法匹配路由 Intro 之前我們在 Ocelot 網關的基礎上 "自定義了一個認證授權的 Ocelot 中間件" ,根據請求的路徑和 Method 進行匹配,找到對應的許可權配置,並判斷是否可以擁有訪問資源的角色,如果沒有則返回 401/403,如果有許可權則轉發到下游服 ...
使用 Ocelot 匹配路由的方法匹配路由
Intro
之前我們在 Ocelot 網關的基礎上自定義了一個認證授權的 Ocelot 中間件,根據請求的路徑和 Method 進行匹配,找到對應的許可權配置,並判斷是否可以擁有訪問資源的角色,如果沒有則返回 401/403,如果有許可權則轉發到下游服務。
原來的匹配方式是首先根據請求路徑和方法完全匹配,如果匹配不到則嘗試使用正則匹配。
我們這次要做的就是將原來的正則匹配替換成 Ocelot 內部的路由匹配方式,這樣我們在配置的時候就不再需要配置兩套了,一邊寫 Ocelot 路由的配置,一邊寫許可權的配置,這樣能減少不少工作量
深入 Ocelot 路由匹配
我們想使用 Ocelot 的路由匹配,首先應該瞭解 Ocelot 的執行過程,然後找到對應的路由匹配的地方,看路由匹配使用到了哪一個服務,用這個服務在我們自己的業務邏輯里匹配即可。
先來看一下 Ocelot 的服務註冊,Ocelot 的服務註冊
可以看到主要的服務註冊代碼應該在 OcelotBuilder
中,查看 OcelotBuilder
https://github.com/ThreeMammals/Ocelot/blob/13.5.2/src/Ocelot/DependencyInjection/OcelotBuilder.cs
可以看到,Ocelot 的服務註冊都在這裡, Ocelot 內部好多都是基於介面的,所以需要找對應的實現的話可以看它的服務註冊是註冊的哪一個服務即可。
簡單分析一下,Ocelot 的路由匹配過程一定在尋找下游地址的時候,根據上游的請求信息(直接請求網關的請求)匹配,所以我們首先找到 DownstreamRouteFinderMiddleware
https://github.com/ThreeMammals/Ocelot/blob/13.5.2/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs
由上面的代碼,我們可以看到,下游路由地址是通過 IDownstreamRouteFinder
來找下游路由的,轉到對應的實現代碼: https://github.com/ThreeMammals/Ocelot/blob/13.5.2/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs
這裡我們可以看到是通過 IUrlPathToUrlTemplateMatcher
來進行路由匹配的,所以我們需要用到這個服務,然後看這個 Match
方法的參數,前兩個參數比較明確,第一個參數是上游請求的地址,第二個參數是請求的 queryString,第三個參數則是 Ocelot 內部構建出來的路由模板信息 UpstreamPathTemplate
,然後我們就需要知道怎麼構建一個 UpstreamPathTemplate
對象,繼續探索
直接看 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
使用 Ocelot 路由匹配
上面提到了我們沒有使用 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; }
}
}
More
這樣,apiPermission 的 Path 配置基本可以使用和 Ocelot 配置一樣的路由,可以更方便的配置,避免 996 咯
Reference
- https://github.com/ThreeMammals/Ocelot
- https://github.com/ThreeMammals/Ocelot/blob/13.5.2/src/Ocelot/DependencyInjection/OcelotBuilder.cs#L95
- https://github.com/ThreeMammals/Ocelot/blob/13.5.2/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathToUrlTemplateMatcher.cs