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

tech2023-08-09  107

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)