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() {

    }

}

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