ocelot 中間件的變化


ocelot 中間件的變化

Intro

之前我們使用 ocelot 的時候自定義了一些中間件來實現我們定制化的一些需求,最近博客園上有小伙伴問我怎么使用,他用的版本是 16.0 版本,16.0 和 17.0 版本的差異不是特別大,就以 17.0 版本為例看一下 ocelot 中間件的變化

Sample

還是拿之前的一個自定義認證授權的一個中間件為例,中間件做的事情主要是

  1. 基於 Resource(API Path) 以及 請求 Method 查詢需要的權限
  2. 如果不需要用戶登錄就可以訪問,就直接往下游服務轉發
  3. 如果需要權限,判斷當前登錄用戶的角色是否有對應的角色可以訪問
  4. 如果可以訪問就轉發到下游服務,如果沒有權限訪問根據用戶是否登錄,已登錄返回 403 Forbidden,未登錄返回 401 Unauthorized

Before

之前的實現(基於 13.x 版本)詳細可以參考:https://www.cnblogs.com/weihanli/p/custom-authentication-authorization-in-ocelot.html

大致代碼如下:

public class UrlBasedAuthenticationMiddleware : Ocelot.Middleware.OcelotMiddleware
{
    private readonly IConfiguration _configuration;
    private readonly IMemoryCache _memoryCache;
    private readonly OcelotRequestDelegate _next;

    public UrlBasedAuthenticationMiddleware(OcelotRequestDelegate next, IConfiguration configuration, IMemoryCache memoryCache, IOcelotLoggerFactory loggerFactory) : base(loggerFactory.CreateLogger<UrlBasedAuthenticationMiddleware>())
    {
        _next = next;

        _configuration = configuration;
        _memoryCache = memoryCache;
    }

    public async Task Invoke(DownstreamContext context)
    {
        var permissions = await _memoryCache.GetOrCreateAsync("ApiPermissions", async entry =>
                                                              {
                                                                  using (var conn = new SqlConnection(_configuration.GetConnectionString("ApiPermissions")))
                                                                  {
                                                                      entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1);
                                                                      return (await conn.QueryAsync<ApiPermission>("SELECT * FROM dbo.ApiPermissions")).ToArray();
                                                                  }
                                                              });

        var result = await context.HttpContext.AuthenticateAsync(context.DownstreamReRoute.AuthenticationOptions.AuthenticationProviderKey);
        context.HttpContext.User = result.Principal;

        var user = context.HttpContext.User;
        var request = context.HttpContext.Request;

        var permission = permissions.FirstOrDefault(p =>
                                                    request.Path.Value.Equals(p.PathPattern, StringComparison.OrdinalIgnoreCase) && p.Method.ToUpper() == request.Method.ToUpper());

        if (permission == null)// 完全匹配不到,再根據正則匹配
        {
            permission =
                permissions.FirstOrDefault(p =>
                                           Regex.IsMatch(request.Path.Value, p.PathPattern, RegexOptions.IgnoreCase) && p.Method.ToUpper() == request.Method.ToUpper());
        }

        if (!user.Identity.IsAuthenticated)
        {
            if (permission != null && string.IsNullOrWhiteSpace(permission.AllowedRoles)) //默認需要登錄才能訪問
            {
                //context.HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, "Anonymous") }, context.DownstreamReRoute.AuthenticationOptions.AuthenticationProviderKey));
            }
            else
            {
                SetPipelineError(context, new UnauthenticatedError("unauthorized, need login"));
                return;
            }
        }
        else
        {
            if (!string.IsNullOrWhiteSpace(permission?.AllowedRoles) &&
                !permission.AllowedRoles.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Any(r => user.IsInRole(r)))
            {
                SetPipelineError(context, new UnauthorisedError("forbidden, have no permission"));
                return;
            }
        }

        await _next.Invoke(context);
    }
}

New

來看一下在新版本(16.x/17.x)的 ocelot 中實現代碼是怎樣的

public class ApiPermission
{
    public string AllowedRoles { get; set; }

    public string PathPattern { get; set; }

    public string Method { get; set; }
}

public class UrlBasedAuthenticationMiddleware : Ocelot.Middleware.OcelotMiddleware
{
    private readonly IConfiguration _configuration;
    private readonly IMemoryCache _memoryCache;
    private readonly RequestDelegate _next;

    public UrlBasedAuthenticationMiddleware(RequestDelegate next, IConfiguration configuration, IMemoryCache memoryCache, IOcelotLoggerFactory loggerFactory) : base(loggerFactory.CreateLogger<UrlBasedAuthenticationMiddleware>())
    {
        _next = next;

        _configuration = configuration;
        _memoryCache = memoryCache;
    }

    public async Task Invoke(HttpContext httpContext)
    {
        // var permissions = await _memoryCache.GetOrCreateAsync("ApiPermissions", async entry =>
        //{
        //    using (var conn = new SqlConnection(_configuration.GetConnectionString("ApiPermissions")))
        //    {
        //        entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1);
        //        return (await conn.QueryAsync<ApiPermission>("SELECT * FROM dbo.ApiPermissions")).ToArray();
        //    }
        //});

        var permissions = new[]
        {
           new ApiPermission()
           {
               PathPattern = "/api/test/values",
               Method = "GET",
               AllowedRoles = ""
           },
           new ApiPermission()
           {
               PathPattern = "/api/test/user",
               Method = "GET",
               AllowedRoles = "User"
           },
           new ApiPermission()
           {
               PathPattern = "/api/test/admin",
               Method = "GET",
               AllowedRoles = "Admin"
           },
        };

        var downstreamRoute = httpContext.Items.DownstreamRoute();

        var result = await httpContext.AuthenticateAsync(downstreamRoute.AuthenticationOptions.AuthenticationProviderKey);
        if (result.Principal != null)
        {
            httpContext.User = result.Principal;
        }

        var user = httpContext.User;
        var request = httpContext.Request;

        var permission = permissions.FirstOrDefault(p =>
            request.Path.ToString().Equals(p.PathPattern, StringComparison.OrdinalIgnoreCase) && p.Method.ToUpper() == request.Method.ToUpper());

        if (permission == null)
        {
            permission =
                permissions.FirstOrDefault(p =>
                    Regex.IsMatch(request.Path.ToString(), p.PathPattern, RegexOptions.IgnoreCase) && p.Method.ToUpper() == request.Method.ToUpper());
        }

        if (user.Identity?.IsAuthenticated == true)
        {
            if (!string.IsNullOrWhiteSpace(permission?.AllowedRoles) &&
                !permission.AllowedRoles.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
                    .Any(r => user.IsInRole(r)))
            {
                httpContext.Items.SetError(new UnauthorizedError("forbidden, have no permission"));
                return;
            }
        }
        else
        {
            if (permission != null && string.IsNullOrWhiteSpace(permission.AllowedRoles))
            {
            }
            else
            {
                httpContext.Items.SetError(new UnauthenticatedError("unauthorized, need login"));
                return;
            }
        }

        await _next.Invoke(httpContext);
    }
}

Diff

主要的區別在於 ocelot 中間件的變化,在之前的版本,ocelot 是自己的中間件,簽名是 Task Invoke(DownstreamContext context) 是 ocelot 自己的 DownstreamContext,在之后 ,Ocelot 為了和 asp.net core 中間件保持一樣的簽名,以更好的復用 asp.net core 中的中間件,更新了自己的中間件, ocelot 自己的 context 等信息現在放在了 HttpContext.Items 中,並通過一系列的擴展方法來獲取和更新對應的信息

但是目前的實現並不能夠完全等同於 asp.net core 中間件,因為如果你想要中斷某一個中間件的話現在大概是有問題的,因為現在 ocelot 中間件里的 HttpContext 並不是原始的 HttpContext ocelot 會在真正開始處理請求之前新建一個 HttpContext 把基本的請求信息復制過去,主要實現代碼: https://github.com/ThreeMammals/Ocelot/blob/17.0.0/src/Ocelot/Multiplexer/MultiplexingMiddleware.cs

如果想要在自定義中間件中實現中斷,需要使用 ocelot 的中間件,通過 SetError 來去處理而不要直接使用 httpContext.Response 去中斷請求

API Diff

  1. 中間件 Invoke 方法簽名,從原來的 Task Invoke(DownstreamContext context) 更新成 Task Invoke(HttpContext context)
  2. SetPipelineError 不再是 OcelotMiddleware 中的一個方法,通過 httpContext.Items.SetError 方法來代替
  3. 通過 httpContext.Items.DownstreamRoute() 來獲取當前請求的 DownstreamRoute 信息

More

除了中間件的變化,配置也發生了變化,原來的 ReRoute 也變成了 Route,升級的時候需要注意一下配置的變化,否則可能就會 404 了,在 17.0 之后,authorisation 更新成了 authorization, authorise 也更新成了 authorize

更多更新可以參考 ocelot 的 PR changes 和文檔

文中提到的示例在 Github 上可以獲取完整的代碼 https://github.com/WeihanLi/AspNetCorePlayground/tree/master/OcelotDemo

Reference


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM