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

 

  

posted @ 2020-05-11 16:04  码农的进击  阅读(1323)  评论(0编辑  收藏  举报