博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring Cloud Zuul 综合使用
阅读量:5759 次
发布时间:2019-06-18

本文共 11391 字,大约阅读时间需要 37 分钟。

Zuul:Pre和Post过滤器

目前我们项目的架构图:

Spring Cloud Zuul 综合使用

从上图中可以看到,Zuul是我们整个系统的入口。当我们有参数校验的需求时,我们就可以利用Zuul的Pre过滤器,进行参数的校验。例如我现在希望请求都一律带上token参数,否则拒绝请求。在项目中创建一个filter包,在该包中新建一个TokenFilter劳累并继承ZuulFilter,代码如下:

package org.zero.springcloud.apigateway.filter;import com.netflix.zuul.ZuulFilter;import com.netflix.zuul.context.RequestContext;import com.netflix.zuul.exception.ZuulException;import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;import org.springframework.http.HttpStatus;import org.springframework.stereotype.Component;import org.springframework.util.StringUtils;import javax.servlet.http.HttpServletRequest;/** * @program: api-gateway * @description: token过滤器 * @author: 01 * @create: 2018-08-25 17:03 **/@Componentpublic class TokenFilter extends ZuulFilter {    @Override    public String filterType() {        // 声明过滤器的类型为Pre        return FilterConstants.PRE_TYPE;    }    @Override    public int filterOrder() {        // 将这个过滤器的优先级放在 PRE_DECORATION_FILTER_ORDER 之前,数字越小优先级越高        return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;    }    @Override    public boolean shouldFilter() {        // 开启这个过滤器        return true;    }    /**     * 这个方法用于自定义过滤器的处理代码     *     * @return Object     * @throws ZuulException ZuulException     */    @Override    public Object run() throws ZuulException {        RequestContext requestContext = RequestContext.getCurrentContext();        // 从上下文中拿到请求对象        HttpServletRequest request = requestContext.getRequest();        // 拿出参数里的token        String token = request.getParameter("token");        if (StringUtils.isEmpty(token)) {            // 验证失败            requestContext.setSendZuulResponse(false);            // 返回401权限不通过            requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());        }        return null;    }}

重启项目,我们来访问一个接口,不带上token参数,看看是否会返回401。如下:

Spring Cloud Zuul 综合使用

带上token参数再测试一下,请求成功:

Spring Cloud Zuul 综合使用

从以上的示例中,可以看到利用Pre可以对请求进行一些预处理。如果希望在请求处理完成后,对返回的数据进行处理的话。就需要使用的Post过滤器,例如我们要在http返回头中,加上一个自定义的X-Foo属性。代码如下:

package org.zero.springcloud.apigateway.filter;import com.netflix.zuul.ZuulFilter;import com.netflix.zuul.context.RequestContext;import com.netflix.zuul.exception.ZuulException;import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletResponse;import java.util.UUID;/** * @program: api-gateway * @description: * @author: 01 * @create: 2018-08-25 17:10 **/@Componentpublic class AddResponseHeaderFilter extends ZuulFilter {    @Override    public String filterType() {        return FilterConstants.POST_TYPE;    }    @Override    public int filterOrder() {        return FilterConstants.SEND_RESPONSE_FILTER_ORDER - 1;    }    @Override    public boolean shouldFilter() {        return true;    }    @Override    public Object run() throws ZuulException {        RequestContext requestContext = RequestContext.getCurrentContext();        HttpServletResponse response = requestContext.getResponse();        response.setHeader("X-Foo", UUID.randomUUID().toString());        return null;    }}

重启项目,同样访问之前那个接口,测试结果如下:

Spring Cloud Zuul 综合使用


Zuul:限流

Zuul充当API网关的角色,所有的请求都经过它,所以很适合在其之上对API做限流保护,防止网络×××。需要注意的是,用于限流的过滤器应该在请求被转发之前调用,常见的限流算法有计数器、漏铜和令×××桶算法。

令×××桶算法示意图:

Spring Cloud Zuul 综合使用

Google开源工具包Guava提供了限流工具类RateLimiter,该类基于令×××桶算法(Token Bucket)来完成限流,非常易于使用。RateLimiter经常用于限制对一些物理资源或者逻辑资源的访问速率,它支持两种获取permits接口,一种是如果拿不到立刻返回false,一种会阻塞等待一段时间看能不能拿到。

我们来创建一个过滤器,简单使用一下这个RateLimiter。代码如下:

package org.zero.springcloud.apigateway.filter;import com.google.common.util.concurrent.RateLimiter;import com.netflix.zuul.ZuulFilter;import com.netflix.zuul.exception.ZuulException;import org.springframework.stereotype.Component;import org.zero.springcloud.apigateway.exception.RateLimiterException;import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SERVLET_DETECTION_FILTER_ORDER;/** * @program: api-gateway * @description: 限流过滤器 * @author: 01 * @create: 2018-08-25 21:04 **/@Componentpublic class RateLimiterFilter extends ZuulFilter {    /**     * 每秒钟放入100个令×××     */    private static final RateLimiter RATE_LIMITER = RateLimiter.create(100);    @Override    public String filterType() {        // 限流肯定是得在Pre类型的过滤器里做        return PRE_TYPE;    }    @Override    public int filterOrder() {        // 设置过滤器的优先级为最高        return SERVLET_DETECTION_FILTER_ORDER - 1;    }    @Override    public boolean shouldFilter() {        return true;    }    @Override    public Object run() throws ZuulException {        // 尝试从令×××桶中获取令×××        if (!RATE_LIMITER.tryAcquire()) {            // 获取失败抛出异常,或做其他处理            throw new RateLimiterException();        }        return null;    }}

除了这个RateLimiter之外,GitHub上也有一些开源的实现。我这里发现了一个还不错的,地址如下:


Zuul:完成权限校验

以上我们演示了pre、post过滤器的简单使用,以及在Zuul上做限流,接下来我们看看如何通过Zuul实现鉴权。通常来说,我们鉴权的对象往往都是用户,我这里已经事先准备好了用户服务以及相关接口。

需求,利用Zuul实现如下功能:

/** * /buyer/order/create 只能买家访问 (cookie里有openid) * /buyer/order/finish 只能卖家访问 (cookie里有token,并且redis存储了session数据) * /buyer/product/list 都可以访问 */

因为判断用户角色权限的时候,需要通过cookie和redis里缓存的数据进行判断,所以修改配置文件如下:

Spring Cloud Zuul 综合使用

将之前做实验的所有过滤器都注释掉,然后新建一个AuthBuyerFilter过滤器,用于拦截订单创建的请求。代码如下:

package org.zero.springcloud.apigateway.filter;import com.netflix.zuul.ZuulFilter;import com.netflix.zuul.context.RequestContext;import com.netflix.zuul.exception.ZuulException;import org.apache.commons.lang.StringUtils;import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;import org.springframework.http.HttpStatus;import org.springframework.stereotype.Component;import org.zero.springcloud.apigateway.utils.CookieUtil;import javax.servlet.http.Cookie;import javax.servlet.http.HttpServletRequest;/** * @program: api-gateway * @description: 买家权限过滤器 * @author: 01 * @create: 2018-08-25 17:03 **/@Componentpublic class AuthBuyerFilter extends ZuulFilter {    private static final String ORDER_CREATE = "/order/buyer/order/create";    @Override    public String filterType() {        // 声明过滤器的类型为Pre        return FilterConstants.PRE_TYPE;    }    @Override    public int filterOrder() {        // 将这个过滤器的优先级放在 PRE_DECORATION_FILTER_ORDER 之前        return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;    }    @Override    public boolean shouldFilter() {        RequestContext requestContext = RequestContext.getCurrentContext();        // 从上下文中拿到请求对象        HttpServletRequest request = requestContext.getRequest();        // 如果访问的是 ORDER_CREATE 则进行拦截,否则不进行拦截        return ORDER_CREATE.equals(request.getRequestURI());    }    /**     * 这个方法用于自定义过滤器的处理代码     *     * @return Object     * @throws ZuulException ZuulException     */    @Override    public Object run() throws ZuulException {        RequestContext requestContext = RequestContext.getCurrentContext();        // 从上下文中拿到请求对象        HttpServletRequest request = requestContext.getRequest();        // /buyer/order/create 只能买家访问 (cookie里有openid)        Cookie cookie = CookieUtil.get(request, "openid");        if (cookie == null || StringUtils.isBlank(cookie.getValue())) {            requestContext.setSendZuulResponse(false);            requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());        }        return null;    }}

接着再新建一个AuthSellerFilter过滤器,用于拦截订单完结的请求。代码如下:

package org.zero.springcloud.apigateway.filter;import com.netflix.zuul.ZuulFilter;import com.netflix.zuul.context.RequestContext;import com.netflix.zuul.exception.ZuulException;import org.apache.commons.lang.StringUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.http.HttpStatus;import org.springframework.stereotype.Component;import org.zero.springcloud.apigateway.constant.RedisConstant;import org.zero.springcloud.apigateway.utils.CookieUtil;import javax.servlet.http.Cookie;import javax.servlet.http.HttpServletRequest;/** * @program: api-gateway * @description: 卖家权限过滤器 * @author: 01 * @create: 2018-08-25 17:03 **/@Componentpublic class AuthSellerFilter extends ZuulFilter {    private final StringRedisTemplate redisTemplate;    private static final String ORDER_FINISH = "/order/buyer/order/finish";    @Autowired    public AuthSellerFilter(StringRedisTemplate redisTemplate) {        this.redisTemplate = redisTemplate;    }    @Override    public String filterType() {        // 声明过滤器的类型为Pre        return FilterConstants.PRE_TYPE;    }    @Override    public int filterOrder() {        // 将这个过滤器的优先级放在 PRE_DECORATION_FILTER_ORDER 之前        return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;    }    @Override    public boolean shouldFilter() {        RequestContext requestContext = RequestContext.getCurrentContext();        // 从上下文中拿到请求对象        HttpServletRequest request = requestContext.getRequest();        // 如果访问的是 ORDER_FINISH 则进行拦截,否则不进行拦截        return ORDER_FINISH.equals(request.getRequestURI());    }    /**     * 这个方法用于自定义过滤器的处理代码     *     * @return Object     * @throws ZuulException ZuulException     */    @Override    public Object run() throws ZuulException {        RequestContext requestContext = RequestContext.getCurrentContext();        // 从上下文中拿到请求对象        HttpServletRequest request = requestContext.getRequest();        // /buyer/order/finish 只能卖家访问 (cookie里有token,并且redis存储了session数据)        if (ORDER_FINISH.equals(request.getRequestURI())) {            Cookie cookie = CookieUtil.get(request, "token");            if (cookie == null ||                    StringUtils.isBlank(cookie.getValue()) ||                    StringUtils.isNotBlank(redisTemplate.opsForValue().get(String.format(RedisConstant.TOKEN_PREFIX, cookie.getValue())))) {                requestContext.setSendZuulResponse(false);                requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());            }        }        return null;    }}

额外话题:

  • 网关不要连接任何服务的关系型数据库
  • 获取数据应该通过调用服务接口的方式进行获取
  • 经常需要获取的数据有必要缓存到redis中,例如需要进行简单的权限缓存

Zuul:跨域

现在我们的项目基本都是前后端分离的,前端通过ajax来请求后端接口。由于浏览器的同源策略,所以会出现跨域的问题。而在微服务架构中,我们可以在网关上统一解决跨域的问题。

在Zuul里增加CorsFilter过滤器的配置类即可。代码如下:

package org.zero.springcloud.apigateway.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.cors.CorsConfiguration;import org.springframework.web.cors.UrlBasedCorsConfigurationSource;import org.springframework.web.filter.CorsFilter;/** * @program: api-gateway * @description: 跨域配置 * @author: 01 * @create: 2018-08-27 23:02 **/@Configurationpublic class CorsConfig {    private CorsConfiguration buildConfig() {        CorsConfiguration corsConfiguration = new CorsConfiguration();        // 允许cookie跨域        corsConfiguration.setAllowCredentials(true);        // 允许任何域名使用        corsConfiguration.addAllowedOrigin("*");        // 允许任何头        corsConfiguration.addAllowedHeader("*");        // 允许任何方法(post、get等)        corsConfiguration.addAllowedMethod("*");        // 设置跨域缓存时间,单位为秒        corsConfiguration.setMaxAge(300L);        return corsConfiguration;    }    @Bean    public CorsFilter corsFilter() {        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();        // 对接口配置跨域设置        source.registerCorsConfiguration("/**", buildConfig());        return new CorsFilter(source);    }}

转载于:https://blog.51cto.com/zero01/2173360

你可能感兴趣的文章
Swoole 4.1.0 正式版发布,支持原生 Redis/PDO/MySQLi 协程化 ...
查看>>
开发网络视频直播系统需要注意的地方
查看>>
haproxy mysql实例配置
查看>>
强化学习的未来— 第一部分
查看>>
掌握Python系统管理-调试和分析脚本1-debugging
查看>>
TableStore:用户画像数据的存储和查询利器
查看>>
2019 DockerCon 大会即将召开,快来制定您的专属议程吧!
查看>>
15分钟构建超低成本数据大屏:DataV + DLA
查看>>
1月9日云栖精选夜读 | Mars 算法实践——人脸识别
查看>>
SparkSQL Catalyst解析
查看>>
jSearch(聚搜) 1.0.0 终于来了
查看>>
盘点2018云计算市场,变化大于需求?
查看>>
极光推送(一)集成
查看>>
Android项目实战(三十九):Android集成Unity3D项目(图文详解)
查看>>
MySQL 8.0 压缩包版安装方法
查看>>
@Transient注解输出空间位置属性
查看>>
Ansible-playbook 条件判断when、pause(学习笔记二十三)
查看>>
编码服务正在步入云端
查看>>
5种你未必知道的JavaScript和CSS交互的方法(转发)
查看>>
线程进程间通信机制
查看>>