SpringCloud Zuul详解篇自定义路由,鉴权,重试,限流

Zuul网关过滤器||||鉴权校验、动态路由

路由介绍


 /**
 * Zuul过滤器,必须继承ZuulFilter父类。
 * 当前类型的对象必须交由Spring容器管理。使用@Component注解描述。
 * 继承父类后,必须实现父类中定义的4个抽象方法。
 * shouldFilter、 run、 filterType、 filterOrder
 */
@Component
public class TestFilter extends ZuulFilter {

    /**
     * 返回boolean类型。代表当前filter是否生效。
     * 默认值为false。
     * 返回true代表开启filter。
     */
    @Override
    public boolean shouldFilter() {
	    //具体逻辑
		if(true){
		   return true
		}else{
		  return false
		}
       
    }

    /**
     * run方法就是过滤器的具体逻辑。
     * return 可以返回任意的对象,当前实现忽略。(spring-cloud-zuul官方解释)
     * 直接返回null即可。
     */
    @Override
    public Object run() throws ZuulException {
        // 通过zuul,获取请求上下文
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
		
         if(false){
		    ctx.setResponseStatusCode(401);
			//设置为flase,不会继续往下执行 不会调用服务接口了 网关直接响应给客户
            ctx.setSendZuulResponse(false);
            //设置返回内容类型及编码
            ctx.getResponse().setContentType("text/html;charset=UTF-8");
            ctx.setResponseBody("Token认证失败......");
		 }
       
        return null;
    }

    /**
     * 过滤器的类型。可选值有:
     * pre - 前置过滤
     * route - 路由后过滤
     * error - 异常过滤
     * post - 远程服务调用后过滤
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * 同种类的过滤器的执行顺序。
     * 按照返回值的自然升序执行。
     */
    @Override
    public int filterOrder() {
        return 0;
    }
}

++++++++网关鉴权案列,具体的逻辑自己替换即可+++++++++++++

1、前置路由鉴权

@Component
 public class AuthorizedFilter extends ZuulFilter {

    Logger logger = LogManager.getLogger(getClass());

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() {
        Date sttime = new Date();
        RequestContext ctx = RequestContext.getCurrentContext();
        ctx.set("dt", sttime);
        HttpServletRequest req = ctx.getRequest();
        String requestType = req.getMethod();
        String requestURL = req.getRequestURL().toString();
        logger.info("Gataway请求URI: " + requestURL);
        //日志记录通道
        if (requestURL.contains("loger")) {
            logger.info("记录插入开始.........");
            return null;
        }
        //获取Token通道
        if (getToken(ctx, req, requestURL)) {
            return null;
        }
        //验证参数
        LinkedHashMap<String,String> validationParam=new LinkedHashMap<>();
        //验证参数在head中的验证
        Boolean bl = "routeServer".equals(req.getHeader("routeServer"));
        Boolean paramCheak;
        if(bl){
            //获取验证参数,返回布尔(参数在header中)
            paramCheak= getHeaderParam(req, validationParam,ctx);
        }else {
            //获取验证参数,返回布尔(参数在body或请求链接中)
            paramCheak= getValidationParam(ctx,req, requestType, validationParam);
        }
        //跟踪标识
        String identification = getIdentification(validationParam);
        logger.info(identification+"请求方式类型:" + requestType);
        //参数校检
        if(!paramCheak){
            logger.error(identification+"参数传递错误......");
            ctx.setResponseStatusCode(401);
            ctx.setSendZuulResponse(false);
            //设置返回内容类型及编码
            ctx.getResponse().setContentType("text/html;charset=UTF-8");
            ctx.setResponseBody("校检参数传递错误......");
        }
        //验证Token
        Boolean tokenCheck = checkToken(req,validationParam);
        if (tokenCheck) {
            logger.info(identification+"Token校验通过........");
         } else {
            logger.info(identification+"Token校验不通过........");
            ctx.setResponseStatusCode(401);
            ctx.setSendZuulResponse(false);
            //设置返回内容类型及编码
            ctx.getResponse().setContentType("text/html;charset=UTF-8");
            ctx.setResponseBody("Token认证失败......");
         }
        return null;
    }
    /**
     *获取验证参数,在header中
     * */
    private Boolean  getHeaderParam(HttpServletRequest req,LinkedHashMap<String, String> validationParam,RequestContext ctx) {
        String token = req.getHeader("token");
        String consumerName = req.getHeader("consumerName");
        String consumerIp = req.getHeader("consumerIp");
        String consumerMark = req.getHeader("consumerMark");
        String uuid = req.getHeader("uuid");
        if(StringUtils.isEmpty(uuid)){
            //网关进入获取不到uuid,自己生成
            uuid=getUuid();
        }
        setUuid(ctx, req, uuid);
        ctx.set("uuid",uuid);
        validationParam.put("uuid",uuid);
        validationParam.put("token",token);
        validationParam.put("consumerName",consumerName);
        validationParam.put("consumerIp",consumerIp);
        //系统用户和管理员标识,否则认定普通用户
        validationParam.put("consumerMark",consumerMark);
        return true;
    }

    /**
     *获取验证参数
     * */
    private Boolean  getValidationParam(RequestContext ctx,HttpServletRequest req, String requestType, LinkedHashMap<String, String> validationParam) {
        if ("GET".equals(requestType) || "DELETE".equals(requestType)) {
            validationParam.put("token",req.getParameter("token"));
            validationParam.put("consumerName",req.getParameter("consumerName"));
            validationParam.put("consumerIp",req.getParameter("consumerIp"));
            //系统用户和管理员标识,否则认定普通用户
            validationParam.put("consumerMark",req.getParameter("consumerMark"));
            String uuid = req.getParameter("uuid");
            if(StringUtils.isEmpty(uuid)){
                //网关进入获取不到uuid,自己生成
                uuid=getUuid();
            }
            setUuid(ctx, req, uuid);
            ctx.set("uuid",uuid);
            validationParam.put("uuid",uuid);
            return true;
        }
        if ("POST".equals(requestType) || "PUT".equals(requestType)) {
            RequestWrapper requestWrapper = new RequestWrapper(req);
            String body = requestWrapper.getBody();
            try {
                JsonAndSqlUtils<AuthorizedDTO> jsonandsqlutils = new JsonAndSqlUtils();
                AuthorizedDTO authorizedDTO = jsonandsqlutils.jsonTObject(body, AuthorizedDTO.class);
                validationParam.put("token",authorizedDTO.getToken());
                validationParam.put("consumerName",authorizedDTO.getConsumerName());
                validationParam.put("consumerIp",authorizedDTO.getConsumerIp());
                validationParam.put("consumerIp",authorizedDTO.getConsumerIp());
                String uuid = authorizedDTO.getUuid();
                if(StringUtils.isEmpty(uuid)){
                    //网关进入获取不到uuid,自己生成
                    uuid=getUuid();
                }

                setUuid(ctx, req, uuid);
                ctx.set("uuid",uuid);
                validationParam.put("uuid",uuid);
                //系统用户和管理员标识,否则认定普通用户
                validationParam.put("consumerMark",authorizedDTO.getConsumerMark());
                return true;
            } catch (IOException e) {
                logger.error("参数传递错误......", e);
                return false;
            }
        }
        return false;
    }

    /**
     *标识跟踪
     * */
    private String getIdentification(LinkedHashMap<String,String> validationParam){
        String consumerName = validationParam.get("consumerName");
        String uuid = validationParam.get("uuid");
        return consumerName+"_"+uuid+" ";
    }
    /**
     *校检Token
     * */
    private Boolean checkToken(HttpServletRequest req,LinkedHashMap<String,String> validationParam) {
        String token = validationParam.get("token");
        String consumerName = validationParam.get("consumerName");
        String consumerIp = validationParam.get("consumerIp");
        String consumerMark = validationParam.get("consumerMark");
        //跟踪标识
        String identification = getIdentification(validationParam);
        //校检后的token
        String tokenAuthorized = "";
        LocalDate now = LocalDate.now();
        //时间字符串
        String dt = now.toString();
        //系统用户和管理员
        if ("1".equals(consumerMark) || "2".equals(consumerMark)) {
            String log = "1".equals(consumerMark) ? "系统用户" : "管理员";
            logger.info(identification+log + ":校检TOKEN......");
            String ipAddr = consumerIp;
            if (consumerIp == null || "".equals(consumerIp)) {
                ipAddr = IpUtils.getIpAddr(req);
            }
            logger.info(identification+"网关获取系统IP:" + ipAddr);
            tokenAuthorized = Md5Utils.getMD5(consumerName + ipAddr);
        } else {
            logger.info(identification+"普通用户校检TOKEN......");
            logger.info(identification+"网关获取普通用户IP:"+ consumerIp);
            tokenAuthorized = Md5Utils.getMD5(consumerName + consumerIp + dt);
        }
        logger.info(identification+"传递token为:" + token);
        logger.info(identification+"校检token为:" + tokenAuthorized);
        boolean equals = tokenAuthorized.equals(token);
        return equals;
    }

    /**
    *获取token
    * */
    private boolean getToken(RequestContext ctx, HttpServletRequest req, String requestURL) {
        if (requestURL.contains("getTokenByPostMethod") || requestURL.contains("getTokenByGetMethod") || requestURL.contains("getTokenWithAdmin")) {
            //判定从transfer进入,客户端IP已经传递
            if ("transfer".equals(req.getParameter("transfer")) || "transfer".equals(req.getHeader("transfer"))) {
                logger.info("transfer获取Token,校检通过.........");
                return true;
            } else {
                //管理员获取token
                if (requestURL.contains("getTokenWithAdmin")) {
                    logger.info("网关获取Token,校检通过.........");
                    return true;
                }
                //由网关直接进入属于系统用户,在网关里面获取ip
                String ipAddr = IpUtils.getIpAddr(req);
                logger.info("获取token系统IP :" + ipAddr);
                // 一定要get一下,requestQueryParams才能取到值
                req.getParameterMap();
                Map<String, List<String>> requestQueryParams = ctx.getRequestQueryParams();
                if (requestQueryParams == null) {
                    requestQueryParams = new HashMap<>();
                }
                ArrayList<String> paramsList = new ArrayList<>();
                paramsList.add(ipAddr);
                requestQueryParams.put("ipAddr", paramsList);
                ctx.setRequestQueryParams(requestQueryParams);
                logger.info("网关获取Token,校检通过.........");
                return true;
            }

        }
        return false;
    }

    /**
     *设置跟踪标识到后续服务
     * */
    private void setUuid(RequestContext ctx, HttpServletRequest req, String uuid) {
        // 一定要get一下,requestQueryParams才能取到值
        req.getParameterMap();
        Map<String, List<String>> requestQueryParams = ctx.getRequestQueryParams();
        if (requestQueryParams == null) {
            requestQueryParams = new HashMap<>();
        }
        ArrayList<String> paramsList = new ArrayList<>();
        paramsList.add(uuid);
        requestQueryParams.put("uuid", paramsList);
        ctx.setRequestQueryParams(requestQueryParams);
    }

}

2、+++兼容业务自定义路由+++

@Component
public class RouteFilter extends ZuulFilter {
    @Value("${zullurl}")
    private String zullurl;
    //判定系统是否记录日志
    @Value("${logServer}")
    private String logServer;

    Logger logger = LogManager.getLogger(getClass());
    @Autowired
    RestTemplate restTemplate;

    @Override
    public String filterType() {
        return FilterConstants.ROUTE_TYPE;
    }

    @Override
    public int filterOrder() {
        return 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() {

        RequestContext ctx = RequestContext.getCurrentContext();
		//更具业务逻辑切换服务
        if(false){
            ctx.put(FilterConstants.PROXY_KEY,"idxs-service");//服务名称
            ctx.put(FilterConstants.SERVICE_ID_KEY,"idxs-service");//服务名称
            ctx.put(FilterConstants.REQUEST_URI_KEY,"/idxsPostApi");//服务的url
        }
        return null;
    }


}

3、+++最后记录日志等操作+++

 /**
 * @Author:zenglengceng
 * @Remark 请求成功或失败 记录日志到数据库
 * @Date: Created in 2019/05/27 15:40
 */
@Component
public class PostFilter extends ZuulFilter {
    @Value("${zullurl}")
    private String zullurl;
    //判定系统是否记录日志
    @Value("${logServer}")
    private String logServer;

    Logger logger = LogManager.getLogger(getClass());
    @Autowired
    RestTemplate restTemplate;

    @Override
    public String filterType() {
        return "post";
    }

    @Override
    public int filterOrder() {
        return 2;
    }

    @Override
    public boolean shouldFilter() {
	   //正常不需要拦截的在这里放行就好了return false即可
        return true;
    }

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletResponse response = ctx.getResponse();
        HttpServletRequest req = ctx.getRequest();
        String requestType = req.getMethod();
        String requestURL = req.getRequestURL().toString();
        if (requestURL.contains("loger") || requestURL.contains("error")) {
            logger.info("记录插入结束.........");
            return null;
        }
        if (requestURL.contains("getTokenByPostMethod") || requestURL.contains("getTokenByGetMethod") || requestURL.contains("getTokenWithAdmin")) {
            logger.info("用户获取Token结束.........");
            return null;
        }
        Date startTmie = (Date) ctx.get("dt");
        Date endTime = new Date();
        //获取代理
        String micro = (String) ctx.get("proxy");
        String consumerIp = "";
        String indexcode = "";
        String param = "";
        String consumerName = "";
        String uuid = "";
        String identification="";
        if (isaBoolean(requestType, micro)) {
            RequestWrapper requestWrapper = new RequestWrapper(req);
            String body = requestWrapper.getBody();
            JsonAndSqlUtils<AuthorizedDTO> jsonandsqlutils = new JsonAndSqlUtils();
            try {
                AuthorizedDTO authorizedDTO = jsonandsqlutils.jsonTObject(body, AuthorizedDTO.class);
                consumerIp = authorizedDTO.getConsumerIp();
                param = authorizedDTO.getParam() != null ? authorizedDTO.getParam().toString() : "";
                if (consumerIp == null) {
                    consumerIp = req.getRemoteAddr();
                }
                indexcode = authorizedDTO.getIndexcode();
                uuid = authorizedDTO.getUuid();
                if(StringUtils.isEmpty(uuid)){
                    uuid=(String)ctx.get("uuid");
                }
                consumerName = authorizedDTO.getConsumerName();
            } catch (IOException e) {
                logger.error("访问的微服务为:"+micro);
                logger.error(micro+"传递参数为:["+body+"]");
                logger.error("参数转换错误", e);
            }
            identification = getIdentification(consumerName, uuid);
            insertRecord(response, startTmie, endTime, consumerName, consumerIp, indexcode, param, micro,identification);
        }

        return null;
    }

    private boolean isaBoolean(String requestType, String micro) {
        String[] split = logServer.split(",");
        List<String> list = new ArrayList<>();
        if (split.length > 0) {
            list = Arrays.asList(split);
        }
        boolean bl = ("POST".equals(requestType) || "PUT".equals(requestType) || "DELETE".equals(requestType)) &&
                list.stream().anyMatch(x -> micro.equals(x));
        return bl;
    }

    private void insertRecord(HttpServletResponse response, Date startTmie, Date endTime,String consumerCode,
                              String consumerIp, String indexcode, String body, String micro,String identification) {
        int status = response.getStatus();
        if (status == 200) {
            logger.info(identification+"[指标:"+indexcode+"] 服务调用完成...........");
            String result = "SUCCESS";
            HttpHeaders header = new HttpHeaders();
            //设置请求参数传播类型
            header.setContentType(MediaType.valueOf(MediaType.APPLICATION_JSON_VALUE));
            DmspIdxAccessRecDTO dmspidxaccessrecdto = setParam(startTmie, endTime, consumerCode, consumerIp, indexcode, body, status, result, micro);
            HttpEntity<DmspIdxAccessRecDTO> httpEntity = new HttpEntity<>(dmspidxaccessrecdto, header);

            try {
                restTemplate.postForObject(zullurl + "public-service/loger/insertMicroserviceRecord", httpEntity, String.class);
            } catch (RestClientResponseException ex) {
                //捕获非200状态码异常
                logger.error("记录插入失败", ex);

            }
        } else {
            logger.error(identification+"[指标:"+indexcode+"] 服务调用失败...........");
            String result = "FAILD";
            HttpHeaders header = new HttpHeaders();
            //设置请求参数传播类型
            header.setContentType(MediaType.valueOf(MediaType.APPLICATION_JSON_VALUE));
            DmspIdxAccessRecDTO dmspidxaccessrecdto = setParam(startTmie, endTime, consumerCode, consumerIp, indexcode, body, status, result, micro);
            HttpEntity<DmspIdxAccessRecDTO> httpEntity = new HttpEntity<>(dmspidxaccessrecdto, header);
            try {
                restTemplate.postForObject(zullurl +"public-service/loger/insertMicroserviceRecord", httpEntity, String.class);
            } catch (RestClientResponseException ex) {
                //捕获非200状态码异常
                logger.error("记录插入失败", ex);

            }
        }
    }

    private DmspIdxAccessRecDTO setParam(Date startTmie, Date endTime, String consumerCode, String consumerIp, String indexcode, String body, int status, String result, String micro) {
        DmspIdxAccessRecDTO dmspidxaccessrecdto = new DmspIdxAccessRecDTO();
        dmspidxaccessrecdto.setConsumerIp(consumerIp);
        dmspidxaccessrecdto.setConsumerCode(consumerCode);
        dmspidxaccessrecdto.setRuleCode(indexcode);
        dmspidxaccessrecdto.setParams(body);
        dmspidxaccessrecdto.setStartDate(startTmie);
        dmspidxaccessrecdto.setEndDate(endTime);
        dmspidxaccessrecdto.setResult(result);
        dmspidxaccessrecdto.setMicroService(micro);
        if (status != 200) {
            dmspidxaccessrecdto.setErrorCode(String.valueOf(status));
        }
        return dmspidxaccessrecdto;
    }
    /**
     *标识跟踪
     * */
    private String getIdentification(String consumerName,String uuid){
        String result=consumerName+"_"+uuid;
        return result;
    }

Zuul网关的限流保护(默认false)

Zuul网关组件也提供了限流保护。当请求并发达到阀值,自动触发限流保护
   
   1、依赖
    <dependency>
    <groupId>com.marcosbarbero.cloud</groupId>
    <artifactId>spring-cloud-zuul-ratelimit</artifactId>
    <version>1.3.4.RELEASE</version>
   </dependency>
   
    --全局限流配置
	
	#按粒度拆分的临时变量key前缀
	zuul.ratelimit.key-prefix=springcloud-book 
	zuul.ratelimit.enabled=true
	#key存储类型,默认是IN_MEMORY本地内存,此外还有多种形式
	zuul.ratelimit.repository=IN_MEMORY
	zuul.ratelimit.behind-proxy=true
	#在一个单位时间窗口的请求数量
	zuul.ratelimit.default-policy.limit=500
	#在一个单位时间窗口的请求时间限制
	zuul.ratelimit.default-policy.quota=2000
	#单位时间窗口
	zuul.ratelimit.default-policy.refresh-interval=60
	#可指定限流粒度,user-用户粒度,origin-客户端地址粒度,url-url粒度
	zuul.ratelimit.default-policy.type=url

另一种方法在过滤器中限制
    //每秒产生1000个令牌
      private static final RateLimiter RATE_LIMITER = RateLimiter.create(1000); 
	
	 //相当于每调用一次tryAcquire()方法,令牌数量减1,当1000个用完后,那么后面进来的用户无法访问上面接口
	RATE_LIMITER.tryAcquire()

Zuul重试机制

1、依赖

org.springframework.retry spring-retry

2、如果Hystrix超时,直接返回超时异常。如果ribbon超时,同时Hystrix未超时,ribbon会自动进行服务集群轮询重试,直到
Hystrix超时为止。如果Hystrix超时时长小于ribbon超时时长,ribbon不会进行服务集群轮询重试。

 配置如下
 
# 开启zuul网关重试
zuul.retryable=true
# hystrix超时时间设置
# 线程池隔离,默认超时时间1000ms
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=8000

# ribbon超时时间设置:建议设置比Hystrix小
# 请求连接的超时时间: 默认5000ms
ribbon.ConnectTimeout=5000
# 请求处理的超时时间: 默认5000ms
ribbon.ReadTimeout=5000
# 重试次数:MaxAutoRetries表示访问服务集群下原节点(同路径访问);MaxAutoRetriesNextServer表示访问服务集群下其余节点(换台服务器)
ribbon.MaxAutoRetries=1
ribbon.MaxAutoRetriesNextServer=1
# 开启重试
ribbon.OkToRetryOnAllOperations=true

Zuul端转发请求的线程数与Service端处理请求的线程数的关系:

限制一:单点部署的Zuul同时处理的最大线程数为server.tomcat.max-threads;

限制二:向所有后端Service同时转发的请求数的最大值为server.tomcat.max-threads、ribbon.MaxTotalConnections和zuul.semaphore.max-semaphores的最小值,这也是所有后端Service能够同时处理请求的最大并发线程数;

限制三:单个后端Service能同时处理的最大请求数为其server.tomcat.max-threads和ribbon.MaxConnectionsPerHost中的最小值。

注意:很多博客提到使用zuul.host.maxTotalConnections与zuul.host.maxPerRouteConnections这两个参数。经过查阅和实践,这两个参数在使用Service ID配置Zuul的路由规则时无效,只适用于指定微服务的url配置路由的情景。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值