过滤器
一般来说,过滤器是对请求的处理,允许某些请求访问,禁止某些请求访问。在zuul中不仅可以过滤,还可以进行加工,再下发到下游服务。
在这里写几个网关常用过滤器的示例。
白名单
白名单,只允许某些请求访问。
package com.shareprog.zuul.config.filter;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* @ClassName: IpWhiteListFilter
* @Description: 白名单
* @author cl
* @date 2021年5月24日
*/
@Component
@Slf4j
@RequiredArgsConstructor
public class IpWhiteListFilter extends ZuulFilter {
private static final String SELECT_IP_WHITELIST_SQL = "select ip from zuul_whitelist";
private static final String WHITELIST_PREFIX = "zuul:whitelist";
private static final String UNKNOWN = "unknown";
private final JdbcTemplate jdbcTemplate;
private final RedisTemplate<String, String> redisTemplate;
@Override
public boolean shouldFilter() {
return false;
}
@Override
public Object run() throws ZuulException {
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest httpServletRequest = currentContext.getRequest();
String ipAddr = getIpAddress(httpServletRequest);
log.info("请求地址IP为:{}", ipAddr);
List<String> whitelist = redisTemplate.opsForList().range(WHITELIST_PREFIX, 0, -1);
if (whitelist.isEmpty()) {
whitelist = jdbcTemplate.queryForList(SELECT_IP_WHITELIST_SQL, String.class);
}
if (!whitelist.contains(ipAddr)) {
currentContext.setSendZuulResponse(false);
currentContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
currentContext.getResponse().setContentType(MediaType.APPLICATION_JSON_VALUE);
}
return null;
}
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return FilterConstants.SEND_ERROR_FILTER_ORDER;
}
/**
* 获取真实IP
* @param request
* @return
*/
private static String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (judgeIp(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (judgeIp(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (judgeIp(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (judgeIp(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (judgeIp(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
private static boolean judgeIp(String ip) {
return ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip);
}
}
限流
限制某个时间段中只允许请求多少次,以下示例是每秒1000次。
package com.shareprog.zuul.config.filter;
import java.util.concurrent.TimeUnit;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import com.google.common.util.concurrent.RateLimiter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Component
@Slf4j
@RequiredArgsConstructor
public class LimitFilter extends ZuulFilter {
private static volatile RateLimiter rateLimiter = RateLimiter.create(100.0);
private final RedisTemplate<String, Object> redisTemplate;
@Override
public boolean shouldFilter() {
return false;
}
@Override
public Object run() throws ZuulException {
RequestContext currentContext = RequestContext.getCurrentContext();
Long currentSecond = System.currentTimeMillis() / 1000;
String key = "dts-api-rate-limit-" + currentSecond;
try {
if (!redisTemplate.hasKey(key)) {
redisTemplate.opsForValue().set(key, 0L, 100L, TimeUnit.SECONDS);
}
int rate = 10;
if (redisTemplate.opsForValue().increment(key, 1L) > rate) {
currentContext.setSendZuulResponse(false);
currentContext.setResponseStatusCode(HttpStatus.SERVICE_UNAVAILABLE.value());
return null;
}
} catch (Exception e) {
log.error("集群限流异常", e);
//单节点限流
rateLimiter.acquire();
}
return null;
}
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return FilterConstants.SEND_ERROR_FILTER_ORDER + 1;
}
}
动态加载过滤器
添加Groovy依赖:
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>3.0.13</version>
</dependency>
添加动态执行的代码
package com.shareprog.zuul.config.zuul;
import com.netflix.zuul.FilterFileManager;
import com.netflix.zuul.FilterLoader;
import com.netflix.zuul.groovy.GroovyCompiler;
import com.netflix.zuul.groovy.GroovyFileFilter;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.io.File;
/**
* groovy 网关脚本执行
*
* @author cl
* @date 2022/11/29
*/
@Component
@Order(value = 1)
public class GroovyLoadLineRunner implements CommandLineRunner {
/**
* Callback used to run the bean.
*
* @param args incoming main method arguments
* @throws Exception on error
*/
@Override
public void run(String... args) throws Exception {
FilterLoader.getInstance().setCompiler(new GroovyCompiler());
//读取配置,获取脚本根目录
String scriptRoot = System.getProperty("zuul.filter.root", "groovy/filters");
//获取刷新间隔
String refreshInterval = System.getProperty("zuul.filter.refreshInterval", "5");
if (scriptRoot.length() > 0) {
scriptRoot = scriptRoot + File.separator;
}
FilterFileManager.setFilenameFilter(new GroovyFileFilter());
FilterFileManager.init(Integer.parseInt(refreshInterval), scriptRoot + "pre",
scriptRoot + "route", scriptRoot + "post");
}
}
groovy脚本在src同级目录的groovy/filters/pre下
import com.netflix.zuul.ZuulFilter
import com.netflix.zuul.context.RequestContext
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants
import javax.servlet.http.HttpServletRequest
class GroovyFilter extends ZuulFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(GroovyFilter.class)
@Override
String filterType() {
return FilterConstants.PRE_TYPE
}
//过滤器优先级
@Override
int filterOrder() {
return 5
}
@Override
boolean shouldFilter() {
return true
}
@Override
Object run() {
HttpServletRequest request = (HttpServletRequest) RequestContext.getCurrentContext().getRequest()
Enumeration<String> headerNames = request.getHeaderNames()
while (headerNames.hasMoreElements()) {
String name = (String) headerNames.nextElement()
String value = request.getHeader(name)
LOGGER.info("header: " + name + ":" + value)
}
LOGGER.info("This is Groovy Filter")
return null
}
}