一、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() {
}
}