zoukankan      html  css  js  c++  java
  • 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 更新成了 authorizationauthorise 也更新成了 authorize

  • 相关阅读:
    【转】VS2013编译libjpeg库
    玩转百度地图(二)之画圆,高德地图、搜搜地图、搜狗地图等稍微修改即可
    JAVA自动生成正则表达式工具类
    S2SH商用后台权限系统第三讲
    自定义表单验证指令
    关于input/textarea提交内容空格回车转换问题,以及ng-model去除空格问题
    angular ui-router 缓存问题
    ionic 发送请求返回一直都是404
    ionic中获取坐标方法
    ionic的scroll的使用出现的问题
  • 原文地址:https://www.cnblogs.com/wht123/p/14258084.html
Copyright © 2011-2022 走看看