一、使用注意事项

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的请求拦截器在头中放入的数据,下游无法拿到。

  

Spring Cloud Gateway之全局过滤器在工作中的使用场景的更多相关文章

  1. Spring Cloud Alibaba学习笔记(21) - Spring Cloud Gateway 自定义全局过滤器

    在前文中,我们介绍了Spring Cloud Gateway内置了一系列的全局过滤器,本文介绍如何自定义全局过滤器. 自定义全局过滤需要实现GlobalFilter 接口,该接口和 GatewayFi ...

  2. Spring Cloud Gateway的全局异常处理

    Spring Cloud Gateway中的全局异常处理不能直接用@ControllerAdvice来处理,通过跟踪异常信息的抛出,找到对应的源码,自定义一些处理逻辑来符合业务的需求. 网关都是给接口 ...

  3. Spring Cloud gateway 三 自定义过滤器GatewayFilter

    之前zuul 网关介绍.他有过滤器周期是四种,也是四种类型的过滤器.而gateway 只有俩种过滤器:"pre" 和 "post". PRE: 这种过滤器在请求 ...

  4. Spring Cloud Gateway之全局异常拦截器

    /** * @version 2019/8/14 * @description: 异常拦截器 * @modified: */ @Slf4j public class JsonExceptionHand ...

  5. 看完就会的Spring Cloud Gateway

    在前面几节,我给大家介绍了当一个系统拆分成微服务后,会产生的问题与解决方案:服务如何发现与管理(Nacos注册中心实战),服务与服务如何通信(Ribbon, Feign实战) 今天我们就来聊一聊另一个 ...

  6. Spring Cloud Alibaba学习笔记(20) - Spring Cloud Gateway 内置的全局过滤器

    参考:https://cloud.spring.io/spring-cloud-static/Greenwich.SR2/single/spring-cloud.html#_global_filter ...

  7. Spring Cloud Alibaba学习笔记(19) - Spring Cloud Gateway 自定义过滤器工厂

    在前文中,我们介绍了Spring Cloud Gateway内置了一系列的内置过滤器工厂,若Spring Cloud Gateway内置的过滤器工厂无法满足我们的业务需求,那么此时就需要自定义自己的过 ...

  8. API网关spring cloud gateway和负载均衡框架ribbon实战

    通常我们如果有一个服务,会部署到多台服务器上,这些微服务如果都暴露给客户,是非常难以管理的,我们系统需要有一个唯一的出口,API网关是一个服务,是系统的唯一出口.API网关封装了系统内部的微服务,为客 ...

  9. Spring Cloud Gateway GatewayFilter的使用

    Spring Cloud Gateway GatewayFilter的使用 一.GatewayFilter的作用 二.Spring Cloud Gateway内置的 GatewayFilter 1.A ...

随机推荐

  1. JDK 16 正式发布,一次性发布 17 个新特性…不服不行!

    上一篇:Java 15 正式发布, 14 个新特性 JDK 16 正式发布 牛逼啊,JDK 15 刚发布半年(2020/09/15),JDK 16 又如期而至(2021/03/16),老铁们,跟上. ...

  2. 基于Centos7xELK+Kafka集群部署方案

    本次集群部署使用ELK版本统一为6.8.10,kafka为2.12-2.51 均可在官网下载 elasticsearch下载地址:https://www.elastic.co/cn/downloads ...

  3. java例题_47 读取 7 个数(1—50)的整数值,每读取一个值,程序打印出该值个数的*

    1 /*47 [程序 47 打印星号] 2 题目:读取 7 个数(1-50)的整数值,每读取一个值,程序打印出该值个数的*. 3 */ 4 5 /*分析 6 * 1.多次读取---for循环 7 * ...

  4. 基于SageMath的数学网站——本科毕业开发项目

    1 绪论 1.1研究背景 我国是一个拥有15亿人口的大国.其中,据2017年的统计,全国共有大学生2600万左右.如此数量众多的大学生,都会有着学习基础数理课程的需求.而在高校的数学教学中,教授最多最 ...

  5. 【Azure Developer】Azure Graph SDK获取用户列表的问题: SDK中GraphServiceClient如何指向中国区的Endpoint:https://microsoftgraph.chinacloudapi.cn/v1.0

    问题描述 想通过Java SDK的方式来获取Azure 门户中所列举的用户.一直报错无法正常调用接口,错误信息与AAD登录认证相关,提示tenant not found. 想要实现的目的,通过代码方式 ...

  6. 华为云PB级数据库GaussDB(for Redis)揭秘第七期:高斯Redis与强一致

    摘要:在KV数据库领域,"强一致性"不仅是一个技术名词,它更是业务与运维的重要需求. 清明刚过,五一假期就要来了.大好春光,不如去婺源看油菜花吧!小云迅速打开APP刷出余票2张,赶 ...

  7. oo第二单元——多线程魔鬼电梯

    在初步认识了面向对象思想后,立刻进入了多线程的学习,本单元的难点主要是锁的理解,需要保证线程安全的同时防止死锁的发生,也要尽可能缩小锁的范围,提高性能.这一单元以电梯为载体,让我们从生活出发,从电梯运 ...

  8. Java内置内存分析

    Java内存分析 package com.chao.reflection; public class Test05 { public static void main(String[] args) { ...

  9. spieces-in-pieces动画编辑器

    前言: 制作灵感来源于 http://species-in-pieces.com/ 这个网站,此网站作者是来自阿姆斯特丹的设计师 Bryan James,其借用纯CSS技术表现出30种濒危动物的碎片拼 ...

  10. 关于Vim/Neovim/SpaceVim的一些思考

    1 前言 最近看到了Neovim以及SpaceVim,于是上手试了一下. 2 Neovim与SpaceVim Neovim是Vim的一个分支,具有更加现代的GUI.嵌入式以及脚本化的终端.异步工作控制 ...