Spring Cloud Gateway之全局过滤器在工作中的使用场景
一、使用注意事项
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之全局过滤器在工作中的使用场景的更多相关文章
- Spring Cloud Alibaba学习笔记(21) - Spring Cloud Gateway 自定义全局过滤器
在前文中,我们介绍了Spring Cloud Gateway内置了一系列的全局过滤器,本文介绍如何自定义全局过滤器. 自定义全局过滤需要实现GlobalFilter 接口,该接口和 GatewayFi ...
- Spring Cloud Gateway的全局异常处理
Spring Cloud Gateway中的全局异常处理不能直接用@ControllerAdvice来处理,通过跟踪异常信息的抛出,找到对应的源码,自定义一些处理逻辑来符合业务的需求. 网关都是给接口 ...
- Spring Cloud gateway 三 自定义过滤器GatewayFilter
之前zuul 网关介绍.他有过滤器周期是四种,也是四种类型的过滤器.而gateway 只有俩种过滤器:"pre" 和 "post". PRE: 这种过滤器在请求 ...
- Spring Cloud Gateway之全局异常拦截器
/** * @version 2019/8/14 * @description: 异常拦截器 * @modified: */ @Slf4j public class JsonExceptionHand ...
- 看完就会的Spring Cloud Gateway
在前面几节,我给大家介绍了当一个系统拆分成微服务后,会产生的问题与解决方案:服务如何发现与管理(Nacos注册中心实战),服务与服务如何通信(Ribbon, Feign实战) 今天我们就来聊一聊另一个 ...
- Spring Cloud Alibaba学习笔记(20) - Spring Cloud Gateway 内置的全局过滤器
参考:https://cloud.spring.io/spring-cloud-static/Greenwich.SR2/single/spring-cloud.html#_global_filter ...
- Spring Cloud Alibaba学习笔记(19) - Spring Cloud Gateway 自定义过滤器工厂
在前文中,我们介绍了Spring Cloud Gateway内置了一系列的内置过滤器工厂,若Spring Cloud Gateway内置的过滤器工厂无法满足我们的业务需求,那么此时就需要自定义自己的过 ...
- API网关spring cloud gateway和负载均衡框架ribbon实战
通常我们如果有一个服务,会部署到多台服务器上,这些微服务如果都暴露给客户,是非常难以管理的,我们系统需要有一个唯一的出口,API网关是一个服务,是系统的唯一出口.API网关封装了系统内部的微服务,为客 ...
- Spring Cloud Gateway GatewayFilter的使用
Spring Cloud Gateway GatewayFilter的使用 一.GatewayFilter的作用 二.Spring Cloud Gateway内置的 GatewayFilter 1.A ...
随机推荐
- python2文件开头两行
#!/usr/bin/python 或者 #!/usr/bin/env python 告诉操作系统python位置 # -*- coding:utf-8 -*- 设置文件编码为utf-8 (默认 ...
- 密码学系列之:csrf跨站点请求伪造
目录 简介 CSRF的特点 CSRF的历史 CSRF攻击的限制 CSRF攻击的防范 STP技术 Cookie-to-header token Double Submit Cookie SameSite ...
- 爬虫-使用BeautifulSoup4(bs4)解析html数据
Beautiful Soup 是一个HTML/XML的解析器,主要的功能也是如何解析和提取 HTML/XML 数据. 一.安装 sudo pip3 install beautifulsoup4 二.使 ...
- Redis 超详细自动管理Cluster集群工具上手 redis-trib.rb (多图,手把手)
安装介绍 redis-trib.rb是一款由Redis官方提供的集群管理工具,能够大量减少集群搭建的时间. 除此之外,还能够简化集群的检查.槽迁徙.负载均衡等常见的运维操作,但是使用前必须要安 ...
- CVE-2020-1472 Zerologon
CVE-2020-1472 Zerologon 漏洞简介 CVE-2020-1472是继MS17010之后一个比较好用的内网提权漏洞,影响Windows Server 2008R 2至Windows ...
- ES 分页方案
ES 中,存在三种常见的分页方案: FROM, SIZE Search-After Scroll 下面将依次比较三种方案之间的 trede-off,并给出相应建议的应用场景. 常见分页,FROM, S ...
- B. 【例题2】移位包含
解析 判断是否是子串,可以将这个一个环 #include <bits/stdc++.h> using namespace std; int f = 0; string a, b; int ...
- kernel base
基础知识 学习网址:ctfwiki 安全客 Kernel:又称核心 维基百科:在计算机科学中是一个用来管理软件发出的数据I/O(输入与输出)要求的电脑程序,将这些要求转译为数据处理的指令并交由中央处理 ...
- 广告成本控制-PID算法
今天我们来聊聊广告成本控制中常用的PID算法. 0.PID算法简介 首先我们可以看下维基百科中给PID算法的定义:由比例单元(Proportional).积分单元(Integral)和微分单元(Der ...
- 1. chmod命令
(一) 简介 chmod命令可以修改文件和目录的权限.控制文件或目录的,读,写,执行权限. 可以采用数字或字符的方式对文件或目录的权限进行变更. 通过命令 ls -l 查看到的9位权限位,rw- ...