zoukankan      html  css  js  c++  java
  • springcloud gateway过滤器

    一:过滤器

    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONObject;
    import com.auth0.jwt.interfaces.DecodedJWT;
    import com.roncoo.education.common.core.base.BaseException;
    import com.roncoo.education.common.core.enums.ResultEnum;
    import com.roncoo.education.common.core.tools.Constants;
    import com.roncoo.education.common.core.tools.JWTUtil;
    import com.roncoo.education.common.core.tools.MD5Util;
    import com.roncoo.education.util.RequestAddParaUtils;
    import com.roncoo.education.util.ServerRequestUtil;
    import io.netty.buffer.ByteBufAllocator;
    import lombok.extern.slf4j.Slf4j;
    import org.bouncycastle.util.Strings;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.core.Ordered;
    import org.springframework.core.io.buffer.DataBuffer;
    import org.springframework.core.io.buffer.DataBufferUtils;
    import org.springframework.core.io.buffer.NettyDataBufferFactory;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpMethod;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
    import org.springframework.http.server.reactive.ServerHttpResponse;
    import org.springframework.util.StringUtils;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Flux;
    import reactor.core.publisher.Mono;
    
    import java.net.URI;
    import java.nio.CharBuffer;
    import java.nio.charset.StandardCharsets;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.atomic.AtomicReference;
    
    /**
     * 请求开始前执行
     */
    @Slf4j
    public class FilterPre implements GlobalFilter, Ordered {
    
        private static final Logger logger = LoggerFactory.getLogger(FilterPre.class);
        private static final String TOKEN = "token";
        private static final String USERNO = "userNo";
        private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";
    
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            String traceId = MD5Util.getTraceID();
    
            ServerHttpRequest serverHttpRequest = exchange.getRequest();
            HttpMethod method = serverHttpRequest.getMethod();
            HttpHeaders httpHeaders = serverHttpRequest.getHeaders();
            String token = httpHeaders.getFirst(TOKEN);
            String ip = ServerRequestUtil.getIpAddr(serverHttpRequest);
            String url = serverHttpRequest.getURI().toString();
            log.debug("traceId[" + traceId + "]" + "uri=" + url + "  method=" + method + "  token=" + token + "  ip=" + ip);
    
            //验证请求地址
            boolean validateResult = false;
            //判断请求是否过滤接口
            if (url.contains("/callback")) {
                // 回调使用
                validateResult = false;
            } else if (url.startsWith("/zuul")) {
                // 文件上传
                validateResult = false;
            } else if (url.contains("/api")) {
                // 不鉴权
                validateResult = false;
            } else {
                validateResult = true;
            }
            if (!validateResult) {
                log.debug("traceId[" + traceId + "]" + "validateResult=" + validateResult + "  filter=filter");
                return chain.filter(exchange);
            }
    
            Long userNo = getUserNoByToken(token);
            if (!StringUtils.isEmpty(userNo)) {
                URI uri = serverHttpRequest.getURI();
                if (HttpMethod.POST.equals(method)) {
                    String requestBody = getRequestBody(serverHttpRequest.getBody());
                    //封装userNo
                    JSONObject jsonObject = JSON.parseObject(requestBody);
                    log.info("request body is:{}", jsonObject);
                    jsonObject.put(USERNO, userNo);
                    requestBody = jsonObject.toJSONString();
    
                    //下面的将请求体再次封装写回到request里,传到下一级,否则,由于请求体已被消费,后续的服务将取不到值
                    ServerHttpRequest request = serverHttpRequest.mutate().uri(uri).build();
                    DataBuffer bodyDataBuffer = stringBuffer(requestBody);
                    Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);
    
                    // 定义新的消息头
                    HttpHeaders headers = new HttpHeaders();
                    headers.putAll(exchange.getRequest().getHeaders());
    
                    // 添加消息头
    //                    headers.set(ServiceConstants.SHIRO_SESSION_PRINCIPALS,GsonUtils.toJson(authenticationVO));
    
                    // 由于修改了传递参数,需要重新设置CONTENT_LENGTH,长度是字节长度,不是字符串长度
                    int length = requestBody.getBytes().length;
                    headers.remove(HttpHeaders.CONTENT_LENGTH);
                    headers.setContentLength(length);
    
    //                // 设置CONTENT_TYPE
    //                if (StringUtils.isEmpty(contentType)) {
    //                    headers.set(HttpHeaders.CONTENT_TYPE, contentType);
    //                }
    
                    request = new ServerHttpRequestDecorator(request) {
                        @Override
                        public HttpHeaders getHeaders() {
                            long contentLength = headers.getContentLength();
                            HttpHeaders httpHeaders = new HttpHeaders();
                            httpHeaders.putAll(super.getHeaders());
                            if (contentLength > 0) {
                                httpHeaders.setContentLength(contentLength);
                            } else {
                                // TODO: this causes a 'HTTP/1.1 411 Length Required' on httpbin.org
                                httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                            }
                            return httpHeaders;
                        }
    
                        @Override
                        public Flux<DataBuffer> getBody() {
                            return bodyFlux;
                        }
                    };
                    //封装request,传给下一级
                    request.mutate().header(HttpHeaders.CONTENT_LENGTH, Integer.toString(requestBody.length()));
                    return chain.filter(exchange.mutate().request(request).build());
                } else {
                    ServerHttpRequest newRequest = RequestAddParaUtils.addPara(exchange, USERNO, userNo.toString());
                    return chain.filter(exchange.mutate().request(newRequest).build());
                }
    
            }
    
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            response.getHeaders().add("Content-Type", "application/json; charset=utf-8");
            String json = JSONObject.toJSONString("token失效,请重新登录");
            // 解决页面字符集乱码
            DataBuffer buffer = response.bufferFactory().wrap(json.getBytes(StandardCharsets.UTF_8));
            return response.writeWith(Flux.just(buffer));
        }
    
        @Override
        public int getOrder() {
            return 0;
        }
    
        /**
         * token 拦截功能
         */
        private Long getUserNoByToken(String token) {
            if (StringUtils.isEmpty(token)) { // token不存在,则从报文里面获取
                throw new BaseException(ResultEnum.TOKEN_PAST);
            }
    
            // 解析 token
            DecodedJWT jwt = null;
            try {
                jwt = JWTUtil.verify(token);
            } catch (Exception e) {
                logger.error("token异常,token={}", token.toString());
                throw new BaseException(ResultEnum.TOKEN_ERROR);
            }
    
            // 校验token
            if (null == jwt) {
                throw new BaseException(ResultEnum.TOKEN_ERROR);
            }
            Long userNo = JWTUtil.getUserNo(jwt);
            if (userNo <= 0) {
                throw new BaseException(ResultEnum.TOKEN_ERROR);
            }
    
            // token正常
            String type = JWTUtil.getType(jwt);
            if (Constants.XCX.equals(type)) {
                // 小程序请求
                // 注意,登录的时候必须要放入缓存
                if (!stringRedisTemplate.hasKey(Constants.XCX.concat(userNo.toString()))) {
                    // 不存在,则登录异常,有效期为1小时
                    throw new BaseException(ResultEnum.TOKEN_PAST);
                }
            } else {
                // PC端、安卓端、苹果端、小程序端
                //  单点登录处理,注意,登录的时候必须要放入缓存
                checkToken(userNo.toString(), token, type);
            }
    
            return userNo;
        }
    
        /**
         * PC端、安卓端、苹果端、小程序端
         * 校验 token
         *
         * @param userNo
         * @param token
         */
        private void checkToken(String userNo, String token, String type) {
            if (!stringRedisTemplate.hasKey(type.concat(userNo))) {
                // 不存在,则登录异常,有效期为1小时
                throw new BaseException(ResultEnum.TOKEN_PAST);
            }
            // 存在,判断是否token相同
            String tk = stringRedisTemplate.opsForValue().get(type.concat(userNo));
            if (!token.equals(tk)) {
                // 不同则为不同的用户登录,这时候提示异地登录
                throw new BaseException(ResultEnum.REMOTE_ERROR);
            }
            // 更新时间,使token不过期
            if (!Constants.PC.equals(type)) {
                //移动端设置token为30天有效
                stringRedisTemplate.opsForValue().set(type.concat(userNo), token, 30, TimeUnit.DAYS);
            } else {
                stringRedisTemplate.opsForValue().set(type.concat(userNo), token, 1, TimeUnit.HOURS);
            }
        }
    
        /**
         * 用于获取请求参数
         *
         * @param body
         * @return
         */
        private static String getRequestBody(Flux<DataBuffer> body) {
            AtomicReference<String> rawRef = new AtomicReference<>();
            body.subscribe(buffer -> {
                byte[] bytes = new byte[buffer.readableByteCount()];
                buffer.read(bytes);
                DataBufferUtils.release(buffer);
                rawRef.set(Strings.fromUTF8ByteArray(bytes));
            });
            return rawRef.get();
        }
    
        private DataBuffer stringBuffer(String value) {
            byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
    
            NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
            DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
            buffer.write(bytes);
            return buffer;
        }
    
    
        /**
         * 从Flux<DataBuffer>中获取字符串的方法
         */
        private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) {
            Flux<DataBuffer> body = serverHttpRequest.getBody();
            AtomicReference<String> bodyRef = new AtomicReference<>();
            body.subscribe(buffer -> {
                CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
                DataBufferUtils.release(buffer);
                bodyRef.set(charBuffer.toString());
            });
            return bodyRef.get();
        }

    二:工具类

    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.util.StringUtils;
    import org.springframework.web.server.ServerWebExchange;
    import org.springframework.web.util.UriComponentsBuilder;
    
    import java.net.URI;
    
    /**
     * @ClassName RequestAddParaUtils
     * @Author ZhangRF
     * @CreateDate 2021/11/26
     * @Decription 请求添加自定义参数
     */
    public class RequestAddParaUtils {
        /**
         * get添加请求参数
         *
         * @param exchange  原有请求
         * @param paraName  参数名称
         * @param paraValue 参数值
         * @return ServerHttpRequest
         */
        public static ServerHttpRequest addPara(ServerWebExchange exchange, String paraName, String paraValue) {
            URI uri = exchange.getRequest().getURI();
            StringBuilder query = new StringBuilder();
            String originalQuery = uri.getRawQuery();
            if (StringUtils.hasText(originalQuery)) {
                query.append(originalQuery);
                if (originalQuery.charAt(originalQuery.length() - 1) != '&') {
                    query.append('&');
                }
            }
    
            query.append(paraName);
            query.append('=');
            query.append(paraValue);
    
            URI newUri = UriComponentsBuilder.fromUri(uri).replaceQuery(query.toString()).build(true).toUri();
            return exchange.getRequest().mutate().uri(newUri).build();
    
        }

    三:创建过滤器bean及其解决跨域配置

    import com.roncoo.education.gateway.common.FilterPre;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.cors.CorsConfiguration;
    import org.springframework.web.cors.reactive.CorsWebFilter;
    import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
    
    /**
     * @ClassName GatewayConfig
     * @Author ZhangRF
     * @CreateDate 2021/11/23
     * @Decription
     */
    @Configuration
    public class GatewayConfig {
        @Bean
        public FilterPre requestGlobalFilter() {
            return new FilterPre();
        }
    
        @Bean
        public CorsWebFilter corsWebFilter() {
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    
            CorsConfiguration corsConfiguration = new CorsConfiguration();
            //1、配置跨域
            //允许哪些头进行跨域
            corsConfiguration.addAllowedHeader("*");
            //允许哪些请求方式进行跨域
            corsConfiguration.addAllowedMethod("*");
            //允许哪些请求来源进行跨域
            corsConfiguration.addAllowedOrigin("*");
            //是否允许携带cookie进行跨域,否则跨域请求会丢失cookie信息
            corsConfiguration.setAllowCredentials(true);
    
            source.registerCorsConfiguration("/**", corsConfiguration);
    
            return new CorsWebFilter(source);
        }
  • 相关阅读:
    jmeter压测-05-xpath表达式
    测试那些事-测试资源篇
    测试那些事-沟通篇
    测试那些事-前端
    测试那些事儿-后端
    记一次大数据量不同处理方式下服务器负载
    jmeter压测dubbo接口,参数为dto时如何写传参及有错误时的分析思路
    pyton3 字典排序
    python测试dubbo接口
    记录一下telnet测试dubbo接口,参数为dto时怎么测试,枚举类型传参
  • 原文地址:https://www.cnblogs.com/zhangrongfei/p/15618291.html
Copyright © 2011-2022 走看看