之前我们在 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 对象:

Copyprivate class FakeReRoute : IReRoute{    public string UpstreamPathTemplate { get; set; }    public bool ReRouteIsCaseSensitive { get; set; }    public int Priority { get; set; }
}

使用 Ocelot 路由匹配示例:

Copypublic 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 咯