SpringCloud gateway 统一请求拦截

2年前 (2022) 程序员胖胖胖虎阿
315 0 0

在SpringCloud微服务框架下,可以通过网关gateway来进行统一的接口请求拦截,这里我主要用来做接口数据的加解密传输,这里使用了RSA非对称加密算法。(后面会附上完整代码)

首先先定义一个FilterConfig,实现GlobalFilter和Ordered两个接口

SpringCloud gateway 统一请求拦截

主要是实现filter拦截方法

SpringCloud gateway 统一请求拦截 

 SpringCloud gateway 统一请求拦截

rsaFilter函数的实现:

SpringCloud gateway 统一请求拦截 整个拦截过程做了两件事:1、解密get请求参数,也就是url中的参数,2、解密body体中的请求参数,也就是post请求参数,这里前后端约定好了使用json传输。
注:这里不会直接修改请求,而创建了一个新的请求然后分发下去,如果有多个filter也是一样。
最终效果:
如果是get请求,后端收到的格式统一为:https://xxx?param=xxxxxxx
如果是post请求,后端收到的请求体格式统一为:{param: "xxxxxxx"}

 GatewayFilterConfig:

import com.alibaba.fastjson.JSONObject;
import com.huaihai.common.utils.RSA.*;
import io.jsonwebtoken.Claims;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.lang.reflect.Field;
import java.net.URI;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.stream.Collectors;


/**
 * @author yeguodong
 * @date 2022/3/28
 */
@Configuration
@Component
public class GatewayFilterConfig implements GlobalFilter, Ordered {

    private static final String LOGIN_PATH = "/User/login";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain){

        // 验证token,如果是登录或者非后台不校验Token

//        String requestUrl = exchange.getRequest().getPath().value();
//        String userType = exchange.getRequest().getHeaders().getFirst(UserConstant.USER_TYPE);
//        AntPathMatcher pathMatcher = new AntPathMatcher();
//        if (!pathMatcher.match(LOGIN_PATH, requestUrl) && "admin".equalsIgnoreCase(userType)) {
//            String token = exchange.getRequest().getHeaders().getFirst(UserConstant.TOKEN);
//            Claims claim = TokenUtils.getClaim(token);
//            if (StringUtils.isBlank(token) || claim == null) {
//                return FilterUtils.invalidToken(exchange);
//            }
//        }


        return rsaFilter(exchange, chain);
        // 测试用,不拦截
//        return chain.filter(exchange);

    }

    /**
     * 拦截请求,rsa解密参数
     * @param exchange
     * @param chain
     * @return
     */
    private Mono rsaFilter(ServerWebExchange exchange, GatewayFilterChain chain) {

        MediaType mediaType = exchange.getRequest().getHeaders().getContentType();
        if(!(MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)
                || MediaType.APPLICATION_JSON_UTF8.isCompatibleWith(mediaType))) {
            return chain.filter(exchange);
        }

        try {
            updateRequestParam(exchange);
        } catch (Exception e) {
            e.printStackTrace();
            return FilterUtils.invalidUrl(exchange);
        }

        ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());

        Mono<String> modifiedBody = serverRequest.bodyToMono(String.class)
                .flatMap(body -> {
                    JSONObject jsonObject = JSONObject.parseObject(body);
                    String param = "";
                    try {
                        param = RSAUtils.decrypt(jsonObject.getString("param"), RSAConstant.PRIVATE_KEY);
                    } catch (Exception e) {

                    }

                    return Mono.just(param);
                });

        BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
        HttpHeaders headers = new HttpHeaders();
        headers.putAll(exchange.getRequest().getHeaders());

        headers.remove(HttpHeaders.CONTENT_LENGTH);

        CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
        return bodyInserter.insert(outputMessage,  new BodyInserterContext())
                .then(Mono.defer(() -> {
                    ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(
                            exchange.getRequest()) {
                        @Override
                        public HttpHeaders getHeaders() {
                            long contentLength = headers.getContentLength();
                            HttpHeaders httpHeaders = new HttpHeaders();
                            httpHeaders.putAll(super.getHeaders());
                            if (contentLength > 0) {
                                httpHeaders.setContentLength(contentLength);
                            } else {
                                httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                            }
                            return httpHeaders;
                        }

                        @Override
                        public Flux<DataBuffer> getBody() {
                            return outputMessage.getBody();
                        }
                    };
                    return chain.filter(exchange.mutate().request(decorator).build());
                }));
    }

    /**
     * 修改前端传的Get参数
     */
    private void updateRequestParam(ServerWebExchange exchange)
            throws NoSuchFieldException, IllegalAccessException, NoSuchPaddingException, NoSuchAlgorithmException,
            IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InvalidKeySpecException {
        ServerHttpRequest request = exchange.getRequest();
        URI uri = request.getURI();
        String query = uri.getQuery();
        String[] split;
        if (StringUtils.isNotBlank(query)
                && query.contains("param")
                && (split = query.split("=")).length > 0) {
            String param = RSAUtils.decrypt(split[1], RSAConstant.PRIVATE_KEY);
            Field targetQuery = uri.getClass().getDeclaredField("query");
            targetQuery.setAccessible(true);
            targetQuery.set(uri, getParams(param));
        }
    }

    private String getParams(String query) {
        JSONObject jsonObject = JSONObject.parseObject(query);
        return jsonObject.entrySet()
                .stream()
                .map(e -> e.getKey() + "=" + e.getValue())
                .collect(Collectors.joining("&"));
    }

    @Override
    public int getOrder() {
        return Integer.MIN_VALUE;
    }
}

GatewayContext:

import org.springframework.util.MultiValueMap;

/**
 * @author yeguodong
 * @date 2022/4/6
 */
public class GatewayContext {

    public static final String CACHE_GATEWAY_CONTEXT = "cacheGatewayContext";

    /**
     * cache json body
     */
    private String cacheBody;
    /**
     * cache formdata
     */
    private MultiValueMap<String, String> formData;
    /**
     * cache reqeust path
     */
    private String path;


    public String getCacheBody() {
        return cacheBody;
    }

    public void setCacheBody(String cacheBody) {
        this.cacheBody = cacheBody;
    }

    public MultiValueMap<String, String> getFormData() {
        return formData;
    }

    public void setFormData(MultiValueMap<String, String> formData) {
        this.formData = formData;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }
}

RSAUtils:

import org.apache.commons.codec.binary.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**
 * @author yeguodong
 * @date 2022/3/31
 */
public class RSAUtils {

    public static final String PUBLIC_KEY = "public_key";

    public static final String PRIVATE_KEY = "private_key";

    public static String encrypt(String str, String publicKey)
            throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException,
            InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        //base64编码的公钥
        byte[] decoded = Base64.decodeBase64(publicKey);
        RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
        //RSA加密
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, pubKey);
        return Base64.encodeBase64String(cipher.doFinal(str.getBytes(StandardCharsets.UTF_8)));
    }


    public static String decrypt(String str, String privateKey)
            throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException,
            InvalidKeyException, BadPaddingException, IllegalBlockSizeException {

        //64位解码加密后的字符串
        byte[] inputByte = Base64.decodeBase64(str.getBytes(StandardCharsets.UTF_8));
        //base64编码的私钥
        byte[] decoded = Base64.decodeBase64(privateKey);
        RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
        //RSA解密
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, priKey);
        return new String(cipher.doFinal(inputByte));
    }

    private static Map<String, String> generateRasKey() {
        Map<String, String> rs = new HashMap<>();
        try {
            // KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
            KeyPairGenerator keyPairGen = null;
            keyPairGen = KeyPairGenerator.getInstance("RSA");
            keyPairGen.initialize(1024, new SecureRandom());
            // 生成一个密钥对,保存在keyPair中
            KeyPair keyPair = keyPairGen.generateKeyPair();
            // 得到私钥 公钥
            RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
            RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
            String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
            // 得到私钥字符串
            String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));
            // 将公钥和私钥保存到Map
            rs.put(PUBLIC_KEY, publicKeyString);
            rs.put(PRIVATE_KEY, privateKeyString);
        } catch (Exception e) {

        }
        return rs;
    }

    public static void main(String[] args) {
        System.out.println(generateRasKey());
    }

}

FilterUtils:

import com.alibaba.fastjson.JSONObject;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;

/**
 * @author yeguodong
 * @date 2022/4/1
 */
public class FilterUtils {

    private FilterUtils() {

    }

    public static Mono<Void> invalidToken(ServerWebExchange exchange) {
        JSONObject json = new JSONObject();
        json.put("code", HttpStatus.UNAUTHORIZED.value());
        json.put("msg", "无效的token");
        return buildReturnMono(json, exchange);
    }

    public static Mono<Void> invalidUrl(ServerWebExchange exchange){
        JSONObject json = new JSONObject();
        json.put("code", HttpStatus.BAD_REQUEST.value());
        json.put("msg", "无效的请求");
        return buildReturnMono(json, exchange);
    }


    public static Mono<Void> buildReturnMono(JSONObject json, ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        byte[] bits = json.toJSONString().getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        //指定编码,否则在浏览器中会中文乱码
        response.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }
}

SpringCloud gateway 统一请求拦截

 

版权声明:程序员胖胖胖虎阿 发表于 2022年10月10日 下午6:56。
转载请注明:SpringCloud gateway 统一请求拦截 | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...