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);
    }

 

posted @ 2021-11-29 10:38  怕黑,可是却恋上了夜  阅读(52)  评论(0编辑  收藏  举报