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-retry2、如果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配置路由的情景。