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