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 {@Resourceprivate IFeignClient feignClient;private static final String GATEWAY_ROUTE_BEAN = "org.springframework.cloud.gateway.support" +".ServerWebExchangeUtils.gatewayRoute";private static final String BEAR_HEAD = "bear";@Overridepublic Mono 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 cookiesInfo = getCookiesInfo(request);String account = cookiesInfo.get("account");String token = cookiesInfo.get("token");//校验tokenres = 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 getCookiesInfo(ServerHttpRequest request) {Map map = new HashMap<>();Set>> cookies = request.getCookies().entrySet();for (Map.Entry> 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 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*/@Overridepublic int getOrder() {return 4;}
}
RequestInterceptor该类为Fegin请求的拦截器,你可以在通过Feign调用其他服务时,在请求头放数据,例如系统内部各系统通过fegin调用时,需要校验请求是否合法,各系统写个拦截器获取请求头约定的key然后通过内部公共的加解密方式校验key是否合法来提高系统的安全性。
@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 USER_INFO = new ThreadLocal<>();public static void setContext(String userInfo) {USER_INFO.set(userInfo);}public static void clean() {USER_INFO.remove();}@Overridepublic void apply(RequestTemplate requestTemplate) {final HashMap 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;}@Overridepublic int getOrder() {return VERSION_CONTROL_FILTER_ORDER;}@Overridepublic Mono 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();//获取pathURI uri = request.getURI();String path = uri.getPath();//只有非白名单路径才版本控住String requestPath = RequestUtils.getCurrentRequest(request);if (!RequestUtils.isFilter(requestPath)) {//判断redis中是否存在keyboolean hasKey =redisUtil.exists(valueAnnotationUtils.getVersionControl() + valueAnnotationUtils.getActiveEnv());if (!hasKey) {redisUtil.set(valueAnnotationUtils.getVersionControl() + valueAnnotationUtils.getActiveEnv(),JSON.toJSONString(new HashMap<>()));}//先取出原本的keyMap preMap =JSON.parseObject(redisUtil.get(valueAnnotationUtils.getVersionControl() + valueAnnotationUtils.getActiveEnv()),HashMap.class);//正常url 例如 /platform/user/meString 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/meint 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 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;}}
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
