一、使用注意事項
1、全局過濾器作用於所有的路由,不需要單獨配置。
2、通過@Order來指定執行的順序,數字越小,優先級越高。
二、默認全局攔截器的整體架構

三、實戰場景,例如,校驗token、記錄請求參數(可參考這邊https://www.cnblogs.com/hyf-huangyongfei/p/12849406.html)、替換負載均衡以后的路由等等
1、校驗token
@Slf4j
public class AuthenFilter implements GlobalFilter, Ordered {
@Resource
private IFeignClient feignClient;
private static final String GATEWAY_ROUTE_BEAN = "org.springframework.cloud.gateway.support" +
".ServerWebExchangeUtils.gatewayRoute";
private static final String BEAR_HEAD = "bear";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String requestUrl = request.getPath().pathWithinApplication().value();
//判斷過濾器是否執行
if (!RequestUtils.isFilter(requestUrl)) {
//該請求轉發,因為訪問/leap,需要展示登錄頁
if (requestUrl.equals("/leap/") || requestUrl.equals("/leap")) {
ServerHttpRequest authErrorReq = request.mutate()
.path("/index.html")
.build();
ServerWebExchange indexExchange = exchange.mutate().request(authErrorReq).build();
return chain.filter(indexExchange);
}
ResEntity res;
ServerHttpResponse response = exchange.getResponse();
Map<String, String> cookiesInfo = getCookiesInfo(request);
String account = cookiesInfo.get("account");
String token = cookiesInfo.get("token");
//校驗token
res = feignClient.verifyToken(token);
log.info("校驗token:{}", res.getMsg());
//如果token失效清除cookies ,讓用戶解鎖或者重新登錄
if (200 == res.getHttpStatus()) {
response.addCookie(ResponseCookie.from("token", token).path("/").build());
response.addCookie(ResponseCookie.from("userAccount", account).path("/").build());
} else {
log.error("網關過濾器AuthenFilter:{}", res.getMsg());
//token失效,通過cookies失效告知前端,重新解鎖
response.addCookie(ResponseCookie.from("token", token).path("/").maxAge(Duration.ofSeconds(0L)).build());
response.addCookie(ResponseCookie.from("userAccount", account).path("/").maxAge(Duration.ofSeconds(0L)).build());
ServerHttpRequest authErrorReq = request.mutate()
.path("/index.html")
.build();
ServerWebExchange indexExchange = exchange.mutate().request(authErrorReq).build();
return chain.filter(indexExchange);
}
final ResEntity resEntity = feignClient.findUserByAccount(account);
//判斷用戶是否存在
if (200 != resEntity.getHttpStatus() || null == resEntity.getData()) {
throw new BusinessException(ExceptionEnum.AUTH_USER_NOT_FOUND, account);
}
//設置請求頭信息
exchange = setHeader(exchange, resEntity);
}
return chain.filter(exchange);
}
/**
* 獲取cookies中的數據
*
* @param request 請求對象
*/
private Map<String, String> getCookiesInfo(ServerHttpRequest request) {
Map<String, String> map = new HashMap<>();
Set<Map.Entry<String, List<HttpCookie>>> cookies = request.getCookies().entrySet();
for (Map.Entry<String, List<HttpCookie>> entry : cookies) {
if ("userAccount".equals(entry.getKey())) {
map.put("account", entry.getValue().get(0).getValue());
}
if ("token".equals(entry.getKey())) {
map.put("token", entry.getValue().get(0).getValue());
}
}
return map;
}
/**
* 設置頭信息
* am exchange
*
* @param resEntity
* @return
* @throws UnsupportedEncodingException
*/
private ServerWebExchange setHeader(ServerWebExchange exchange, ResEntity resEntity) {
final HashMap<String, String> claims = Maps.newHashMap();
claims.put("jwt", UUID.randomUUID().toString().replaceAll("-", ""));
ServerHttpRequest userInfo = null;
try {
String user = URLEncoder.encode(JSON.toJSONString(resEntity.getData()), "UTF-8");
userInfo = exchange.getRequest().mutate()
.header(BEAR_HEAD, JwtHelper.genToken(claims))
.header("userInfo", user)
.build();
exchange = exchange.mutate().request(userInfo).build();
//feign攔截器的線程局部變量
FeignRequestInterceptor.setContext(user);
} catch (UnsupportedEncodingException e) {
throw new BusinessException(ExceptionEnum.COMMON_ENCODE_EXCEPTION, e, "網關攔截器");
}
return exchange;
}
/**
* 過濾器的優先級
*
* @return
*/
@Override
public int getOrder() {
return 4;
}
}
@Slf4j
@Configuration
public class FeignRequestInterceptor implements RequestInterceptor {
private static final String BEAR_HEAD = "bear";
private static final String USER_INFO_HEAD = "hd-user";
private static final ThreadLocal<String> USER_INFO = new ThreadLocal<>();
public static void setContext(String userInfo) {
USER_INFO.set(userInfo);
}
public static void clean() {
USER_INFO.remove();
}
@Override
public void apply(RequestTemplate requestTemplate) {
final HashMap<String, String> claims = Maps.newHashMap();
claims.put("jwt", UUID.randomUUID().toString().replaceAll("-", ""));
requestTemplate.header(BEAR_HEAD, JwtHelper.genToken(claims));
if (null != USER_INFO.get()) {
requestTemplate.header(USER_INFO_HEAD, USER_INFO.get());
}
}
}
2、更改負載均衡后的url
@Slf4j
public class VersionControlFilter implements GlobalFilter, Ordered {
private static final int VERSION_CONTROL_FILTER_ORDER = 101001;
private static final String HTTP_PREFIX = "http://";
private static final String SLASH = "/";
private static final String STAR = "*";
private static final String COLON = ":";
private final RedisUtil redisUtil;
private final ValueAnnotationUtils valueAnnotationUtils;
public VersionControlFilter(RedisUtil redisUtil, ValueAnnotationUtils valueAnnotationUtils) {
this.redisUtil = redisUtil;
this.valueAnnotationUtils = valueAnnotationUtils;
}
@Override
public int getOrder() {
return VERSION_CONTROL_FILTER_ORDER;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
//獲取遠程ip地址
InetSocketAddress inetSocketAddress = request.getRemoteAddress();
if (null == inetSocketAddress) {
return chain.filter(exchange);
}
String clientIp = inetSocketAddress.getAddress().getHostAddress();
//獲取path
URI uri = request.getURI();
String path = uri.getPath();
//只有非白名單路徑才版本控住
String requestPath = RequestUtils.getCurrentRequest(request);
if (!RequestUtils.isFilter(requestPath)) {
//判斷redis中是否存在key
boolean hasKey =
redisUtil.exists(valueAnnotationUtils.getVersionControl() + valueAnnotationUtils.getActiveEnv());
if (!hasKey) {
redisUtil.set(valueAnnotationUtils.getVersionControl() + valueAnnotationUtils.getActiveEnv(),
JSON.toJSONString(new HashMap<>()));
}
//先取出原本的key
Map<String, String> preMap =
JSON.parseObject(redisUtil.get(valueAnnotationUtils.getVersionControl() + valueAnnotationUtils.getActiveEnv()),
HashMap.class);
//正常url 例如 /platform/user/me
String clientAddress = clientIp + path;
String serviceIp = preMap.get(clientAddress);
//非正常,匹配正則表達式 例如 /platform/user/* 或者 /platform/user/**
URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
if (StringUtils.isBlank(serviceIp)) {
serviceIp = getRegx(clientIp, path, preMap);
}
if (StringUtils.isBlank(serviceIp)) {
return chain.filter(exchange);
}
//負載均衡以后的路由地址 例如:http://160.5.34.210:9772/platform/user/me
int port = requestUrl.getPort();
//替換到灰度的版本中
StringBuilder forwardAddress = new StringBuilder(HTTP_PREFIX);
forwardAddress.append(serviceIp)
.append(COLON)
.append(port)
.append(path);
//追加參數
if ("GET".equalsIgnoreCase(request.getMethodValue())) {
forwardAddress.append("?").append(uri.getQuery());
}
log.debug("VersionControlFilter 灰度轉發的地址:{}", forwardAddress.toString());
try {
requestUrl = new URI(forwardAddress.toString());
} catch (URISyntaxException e) {
log.error("VersionControlFilter URI不合法:{}", requestUrl);
}
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
}
return chain.filter(exchange);
}
/**
* 匹配正則規則
*
* @param clientIp 客戶端ip
* @param path 路徑
* @param map redis中的數據
* @return 服務器地址
*/
private String getRegx(String clientIp, String path, Map<String, String> map) {
String[] paths = path.split(SLASH);
if (1 > paths.length) {
log.error(" VersionControlFilter 請求路徑:{}", path);
throw new BusinessException(" VersionControlFilter 請求路徑不合法");
}
for (int i = 0; i < paths.length; i++) {
StringBuilder clientAddress = new StringBuilder(clientIp);
String item = paths[i];
if (StringUtils.isBlank(item)) {
continue;
}
for (int j = 0; j <= i; j++) {
if (StringUtils.isBlank(paths[j])) {
continue;
}
if (j == paths.length - 1) {
clientAddress.append(SLASH + STAR);
} else {
clientAddress.append(SLASH).append(paths[j]);
}
}
if (i != paths.length - 1) {
clientAddress.append(SLASH + STAR + STAR);
}
String serverIp = map.get(clientAddress.toString());
if (StringUtils.isNotBlank(serverIp)) {
return serverIp;
}
}
return null;
}
}
注意點:如果開啟熔斷,要注意熔斷的線程隔離級別,否則Feign的請求攔截器在頭中放入的數據,下游無法拿到。
