Spring Cloud 通过在 zuul 修改请求参数——对请求参数进行解密

一、zuul

zuul是netflix开源的一个API Gateway 服务器, 本质上是一个web servlet应用,Zuul 在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架,Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门。zuul的核心是一系列的filters, 其作用可以类比Servlet框架的Filter,或者AOP。

在基于 springcloud 构建的微服务系统中,通常使用网关zuul来进行一些用户验证等过滤的操作,比如 用户在 header 或者 url 参数中存放了 token ,网关层需要 用该 token 查出用户 的 userId ,并存放于 request 中,以便后续微服务可以直接使用而避免再去用 token 查询。

在这里,使用zuul的过滤器对请求参数验签(解密),然后发给后续的微服务。

共三个服务:注册中心,zuul服务,通过zuul能访问到的服务。

流程:zuul服务和另一个服务注册到注册中心上,带有加密过得参数的请求url经过zuul处理参数解密之后发给后续微服务。

二、RSA
1.RSA是基于大数因子分解难题,目前各种主流计算机语言都支持RSA算法的实现,是一种非对称加密方法
2.java6支持RSA算法
3.RSA算法可以用于数据加密和数字签名
4.RSA算法相对于DES/AES等对称加密算法,他的速度要慢的多
5.总原则:公钥加密,私钥解密 / 私钥加密,公钥解密

三、分析

本文前提是前端采用RSA公钥加密请求体和请求参数,明文由后端提供,Redis中保存session-公钥-私钥。

首先获取到request,但是在request中只有getParameter()而没有setParameter()方法,所以直接修改url参数不可行,另外在request中虽然可以setAttribute(),但是可能由于作用域(request)的不同,一台服务器才能getAttribute()出来,在这里设置的Attribute在后续的微服务中是获取不到的,因此必须考虑另外的方式:get方法和其他方法处理方式不同,post和put需重写HttpServletRequestWrapper,即获取请求的输入流,重写json参数,传入重写构造上下文中的request中。

四、实现

1. RSA秘钥生成、加密、解密工具类:RSAUtil

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
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;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * RSA非对称加密
 * @author Administrator
 *
 */
public class RSAUtil {
    
    private final static Logger logger = LoggerFactory.getLogger(RSAUtil.class);
    
  /*  public static void main(String[] args) throws Exception {
        //生成公钥和私钥
        final Map<String, Object> keyMap = genKeyPair();
        //需要加密的字符串
        Map<String, Object> map = new HashMap<>();
        map.put("userName", "test");
        map.put("password", "Test@2018");
        JSONObject json = new JSONObject(map);
        String message = json.toJSONString();
        
        String publicKey = (String) keyMap.get("publicKey");
        String privateKey = (String) keyMap.get("privateKey");

        logger.info("随机生成的公钥为:" + publicKey);
        logger.info("随机生成的私钥为:" + privateKey);
        
        logger.info("明文加密前: " + message);
        
        String encryptMessage = encrypt(message, publicKey);
        logger.info("使用公钥加密后的字符串为: " + encryptMessage);
        String desryptMessage = decrypt(encryptMessage, privateKey);
        logger.info("私钥还原后的字符串为: " + desryptMessage);
        
    }*/

    /** 
     * 随机生成密钥对 
     * @throws NoSuchAlgorithmException 
     */  
    public static Map<String, Object> genKeyPair() {  
        try {
            final Map<String, Object> keyMap = new HashMap<>();
            // KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象  
            KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");  
            // 初始化密钥对生成器,密钥大小为96-1024位  
            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
            keyMap.put("publicKey",publicKeyString);  //0表示公钥
            keyMap.put("privateKey",privateKeyString);  //1表示私钥
            return keyMap;
        } catch (NoSuchAlgorithmException e) {
            logger.error("生成RSA密钥对异常", e);
        }
        return null;
    }  
    /** 
     * RSA公钥加密 
     *  
     * @param str 
     *            加密字符串
     * @param publicKey 
     *            公钥 
     * @return 密文 
     * @throws Exception 
     *             加密过程中的异常信息 
     */  
    public static String encrypt( String message, String publicKey ) {
        //base64编码的公钥
        try {
            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);
            String encryptMessage = Base64.encodeBase64String(cipher.doFinal(message.getBytes("UTF-8")));
            return encryptMessage;
        } catch (InvalidKeyException | InvalidKeySpecException | NoSuchAlgorithmException | NoSuchPaddingException
                | IllegalBlockSizeException | BadPaddingException | UnsupportedEncodingException e) {
            logger.error("使用公钥对数据加密异常", e);
        }
        return null;
    }

    /** 
     * RSA私钥解密
     *  
     * @param str 
     *            加密字符串
     * @param privateKey 
     *            私钥 
     * @return 铭文
     * @throws Exception 
     *             解密过程中的异常信息 
     */  
    public static String decrypt(String message, String privateKey) {
        try {
            //64位解码加密后的字符串
            byte[] inputByte = Base64.decodeBase64(message.getBytes("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);
            String decryptMessage = new String(cipher.doFinal(inputByte));
            return decryptMessage;
        } catch (InvalidKeyException | UnsupportedEncodingException | InvalidKeySpecException | NoSuchAlgorithmException
                | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) {
            logger.error("使用私钥对数据解密异常", e);
        }
        return null;
    }

}

2. request请求修饰类:RequestParamWrapper ,负责对request body 和request param的解密重新赋值

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

import org.springframework.util.StringUtils;

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

public class RequestParamWrapper extends HttpServletRequestWrapper {
    
    private byte[] body;
    private Map<String, String[]> paramMap = new HashMap<>();
    private String queryString;
    
    public RequestParamWrapper(HttpServletRequest request) throws IOException {
        super(request);
        //URL后缀参数解密
        if (!StringUtils.isEmpty(request.getQueryString())) {
            decryptParameterMap(request);
        }
        //由于request并没有提供现成的获取json字符串的方法,所以我们需要将body中的流转为字符串
        if ("post".equalsIgnoreCase(request.getMethod()) || "put".equalsIgnoreCase(request.getMethod())) {
            String json = getRequestJsonBody(request);
            //POST请求体参数解密
            if (!StringUtils.isEmpty(json)) {
                body = decryptRequestJsonBody(json).getBytes();
            }
        }
    }
    
    /**
     * 对URL后缀参数进行解密
     * @param request
     */
    private void decryptParameterMap(HttpServletRequest request) {

        String encryptQueryString = request.getQueryString();

        if (encryptQueryString != null && encryptQueryString.trim().length() > 0) {
            //取出被统一加密的Param的值
            int splitIndex = encryptQueryString.indexOf("=");
            if (splitIndex == -1) {
                return;
            }
//            String param = queryString.substring(0, splitIndex);
            String privateKey = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAIJlFH+WdIqaMxAA/4TgjFRsZAjDwgnfi3qxXnVXIrc/sGeKIRR8NBDnSsB6ABcaraFbAHuP7ZKZnrbfuDd24oNlKTB7JXlF8OqLAvEYrhv7aMQzaV8DIFFHjyq0O8pVDXSPIF+c3Hq1qJXEhiOzf9iSOWheHimVW84+bMUZZsA7AgMBAAECgYB73WcmeBbG3wnohvozEFddjwVLqiF13YuShlCzaI2Kw45gHL+lxQJ0mDHTO1FAoVAUuexwc9166EDzePt2fJFYYOgHUrVUG7UitEU935tfe0N68E0fTq2JFCjZHWmelfMEAp/xlqnvyzEvrkkMhZSQ6nMwunjxNnNdfVqWVDuHKQJBAMoqmyCz5V/liXuyViG1AVlDr02SHrZHrXwiSov5Tt9ShAUiMvAGqCyPeuCjCDpZz9t2vHFvoVUb9R/f9y2x/zUCQQClHeiZocXEIXv0nXbgnUz0cvbfPIvvalmDuge6JHBqoAJuriwfIjDYskpNDTU+03Xsa0amBkpO+gU9SrpgZn+vAkBJ6s4RZPUm3OwpuAjaBi5aDu9Xs2dbSlXaH0eWai82ZBs1LU3miOiQcl2BKNrnStM+8OjxqNkaH0C+yMq9gGlJAkEAh3kZneuwQrKibFpB7hrByBMHYLPhsIbWeRDKRDyfi6xLMppvEwBPiYwHEF8U375KE7cU2SVyFIhoghhtAKk4ewJBAIxSPFHiEMXK5B3r7MSlhHZk61LY6G2W7ZEzLBJoejt8R+5aQ01QW3SG+Jzz45/IkVIrUlvTFeU5MSAyPKK1lGI=";
            String paramValue = RSAUtil.decrypt(encryptQueryString.substring(splitIndex + 1), privateKey);
            //将解密后的真正的参数赋值给queryString
            queryString = paramValue;
            //对解密后的值进行分解
            String[] params = paramValue.split("&");
            for (int i = 0; i < params.length; i++) {
                splitIndex = params[i].indexOf("=");
                if (splitIndex == -1) {
                    continue;
                }
                String key = params[i].substring(0, splitIndex);
                if (!this.paramMap.containsKey(key)) {
                    if (splitIndex < params[i].length()) {
                        String value = params[i].substring(splitIndex + 1);
                        this.paramMap.put(key, new String[]{value});
                    }
                }
            }
        }
    }
    

    /**
     * 对请求体内容进行解密
     * @param jsonStr
     * @return
     */
    private String decryptRequestJsonBody(String jsonStr){
        JSONObject json = JSONObject.parseObject(jsonStr);
        String privateKey = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAIJlFH+WdIqaMxAA/4TgjFRsZAjDwgnfi3qxXnVXIrc/sGeKIRR8NBDnSsB6ABcaraFbAHuP7ZKZnrbfuDd24oNlKTB7JXlF8OqLAvEYrhv7aMQzaV8DIFFHjyq0O8pVDXSPIF+c3Hq1qJXEhiOzf9iSOWheHimVW84+bMUZZsA7AgMBAAECgYB73WcmeBbG3wnohvozEFddjwVLqiF13YuShlCzaI2Kw45gHL+lxQJ0mDHTO1FAoVAUuexwc9166EDzePt2fJFYYOgHUrVUG7UitEU935tfe0N68E0fTq2JFCjZHWmelfMEAp/xlqnvyzEvrkkMhZSQ6nMwunjxNnNdfVqWVDuHKQJBAMoqmyCz5V/liXuyViG1AVlDr02SHrZHrXwiSov5Tt9ShAUiMvAGqCyPeuCjCDpZz9t2vHFvoVUb9R/f9y2x/zUCQQClHeiZocXEIXv0nXbgnUz0cvbfPIvvalmDuge6JHBqoAJuriwfIjDYskpNDTU+03Xsa0amBkpO+gU9SrpgZn+vAkBJ6s4RZPUm3OwpuAjaBi5aDu9Xs2dbSlXaH0eWai82ZBs1LU3miOiQcl2BKNrnStM+8OjxqNkaH0C+yMq9gGlJAkEAh3kZneuwQrKibFpB7hrByBMHYLPhsIbWeRDKRDyfi6xLMppvEwBPiYwHEF8U375KE7cU2SVyFIhoghhtAKk4ewJBAIxSPFHiEMXK5B3r7MSlhHZk61LY6G2W7ZEzLBJoejt8R+5aQ01QW3SG+Jzz45/IkVIrUlvTFeU5MSAyPKK1lGI=";
        String bodyStr = RSAUtil.decrypt(json.get("request").toString(), privateKey);
        ObjectMapper mapper = new ObjectMapper();
        String data = null;
        try {
            JsonNode jsonNode = mapper.readTree(bodyStr);
            data = jsonNode.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return data;
    }
    
    /**
     * 获取请求体内容
     * @param request
     * @return
     */
    public static String getRequestJsonBody(HttpServletRequest request){
        try {

            int contentLength = request.getContentLength();
            if (contentLength < 0) {
                return null;
            }
            byte buffer[] = new byte[contentLength];
            for (int i = 0; i < contentLength;) {

                int readlen = request.getInputStream().read(buffer, i, contentLength - i);
                if (readlen == -1) {
                    break;
                }
                i += readlen;
            }

            String charEncoding = request.getCharacterEncoding();
            if (charEncoding == null) {
                charEncoding = "UTF-8";
            }
            return new String(buffer, charEncoding);

        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
    
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    /**
     * 在使用@RequestBody注解的时候,其实框架是调用了getInputStream()方法,所以我们要重写这个方法
     * @return
     * @throws IOException
     */
    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream bais = new ByteArrayInputStream(body);

        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }

            @Override
            public int read() throws IOException {
                return bais.read();
            }
        };
    }
    
    /* request.getParameter(name)
     * (non-Javadoc)
     * @see javax.servlet.ServletRequestWrapper#getAttribute(java.lang.String)
     */
    @Override
    public Object getAttribute(String name) {
        if (paramMap == null || paramMap.get(name) == null) {
            return null;
        }
        return paramMap.get(name)[0];
    }
    
    /* request.getQueryString()
     * (non-Javadoc)
     * @see javax.servlet.http.HttpServletRequestWrapper#getQueryString()
     */
    @Override
    public String getQueryString() {
        return queryString;
    }
    
    @Override
    public Map<String, String[]> getParameterMap() {
    
        return paramMap;
    }

    @Override
    public String getParameter(String name) {
        if (paramMap == null || paramMap.get(name) == null) {
            return null;
        }
        return paramMap.get(name)[0];
    }
    
    @Override
    public String[] getParameterValues(String name) {
        if (paramMap == null) {
            return null;
        }
        return paramMap.get(name);
    }
}

3. 过滤器:RequestParamModifyFilter,过滤出需要解密的请求

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;

import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.stereotype.Component;

@Component
@ServletComponentScan
@WebFilter(urlPatterns = "/sy/login", filterName = "requestParamModifyFilter")//此处的url根据实际情况而定
public class RequestParamModifyFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        //HttpServletRequest没有提供相关的set方法来修改param 和 body,所以需要用修饰类
        ServletRequest requestWrapper = new RequestParamWrapper((HttpServletRequest) request);

        chain.doFilter(requestWrapper, response);
        
    }

    @Override
    public void destroy() {

    }

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章