需求:
為了數(shù)據(jù)安全食铐, 前端對傳過來的數(shù)據(jù)進行了加密匕垫, 后端這邊為了在不修改原有接口的情況下,需要在業(yè)務層(Controller)之前做解密虐呻,并能在業(yè)務層拿到需要的參數(shù)象泵。
思路:
眾所周知, Request是不允許修改參數(shù)的斟叼,這個估計和數(shù)據(jù)安全性有關(guān)偶惠。但是因為后端使用的Spring MVC 到Controller那層時候, 是獲取Request的參數(shù)朗涩, 進行自動封裝的忽孽, 所以,為了不大規(guī)模的修改原有接口谢床, 只能在Srping MVC在提取Request的參數(shù)之前兄一, 對數(shù)據(jù)進行修改。所以大概的要點有:
- 獲取Request的加密數(shù)據(jù)识腿,進行解密出革。
- 將解密之后的數(shù)據(jù),傳入Request渡讼。
- 必須要在Spring MVC處理之前骂束。
實現(xiàn)步驟:
- 修改Request 的Paramter
想來大家都知道, 獲取request的參數(shù)成箫, 無非就是關(guān)鍵的幾個方法
-
getParameter(String name)
獲取name對應的value展箱, 如果有多個返回第一個
-
getParameterNames()
獲取request里面所有的name,返回一個Enumeration類型
-
getParameterValues(String name)
獲取name對應的所有value
但Request是沒有提供類似于setParamter(String name, Object value)的方法的蹬昌,所以混驰, 我的插入點,就是通過重寫Request的三個獲取參數(shù)的主要方法凳厢,來達到修改參數(shù)的目的账胧。
找到插入點之后, 就是開始實現(xiàn)HttpServletRequest
先紫, 可是你會發(fā)現(xiàn)你要實現(xiàn)的方法如此之多治泥。。遮精。
全部實現(xiàn)一遍確實不是可好主意...但通過HttpServletRequest的實現(xiàn)結(jié)構(gòu)里居夹,發(fā)現(xiàn)了這么一個類:HttpServletRequestWrapper
看名字就知道败潦, 這就是HttpServletRequest的包裝類, 進去一看准脂,也果然如此劫扒。
這個時候狸膏, 一切需要的準備就緒沟饥, 開始寫我們自定義的Request吧
public class ParameterRequestWrapper extends HttpServletRequestWrapper {
private Map<String,String[]> params;//定義參數(shù)集合
//需要一個request和 篡改之后的參數(shù)進行實例化。
public ParameterRequestWrapper(HttpServletRequest request, Map<String,String[]> newParams) {
super(request);
this.params = newParams;
}
//查找自定的Map進行返回
@Override
public String getParameter(String name) {
Object v = params.get(name);
if (v == null) {
return null;
} else if (v instanceof String[]) { //一個name可能對應多個value湾戳, 返回第一個
String[] strArr = (String[]) v;
if (strArr.length > 0) {
return strArr[0];
} else {
return null;
}
} else if (v instanceof String) {
return (String) v;
} else {
return v.toString();
}
}
@Override
public Map getParameterMap() {
return params;
}
@Override
public Enumeration getParameterNames() {
return new Vector(params.keySet()).elements();
}
@Override
public String[] getParameterValues(String name) {
Object v = params.get(name);
if (v == null) {
return null;
} else if (v instanceof String[]) {
return (String[]) v;
} else if (v instanceof String) {
return new String[] { (String) v };
} else {
return new String[] { v.toString() };
}
}
}
- 解密參數(shù)贤旷, 使用解密后的參數(shù)創(chuàng)建一個自定義的Request。
在做這一步的時候砾脑, 要考慮一個問題就是幼驶,如何把修改后的Request供Spring MVC去使用?Spring MVC的處理是從DispatcherServlet開始的韧衣,通過查看Servlet文檔可以發(fā)現(xiàn)盅藻,在執(zhí)行Servlet的時候, 會先執(zhí)行Filter畅铭。
Filter也稱之為過濾器氏淑,它是Servlet技術(shù)中最實用的技術(shù),Web開發(fā)人員通過Filter技術(shù)顶瞒,對web服務器管理的所有web資源:例如Jsp, Servlet, 靜態(tài)圖片文件或靜態(tài) html 文件等進行攔截夸政,從而實現(xiàn)一些特殊的功能。例如實現(xiàn)URL級別的權(quán)限訪問控制榴徐、過濾敏感詞匯、壓縮響應信息等一些高級功能匀归。
它主要用于對用戶請求進行預處理坑资,也可以對HttpServletResponse進行后處理。使用Filter的完整流程:Filter對用戶請求進行預處理穆端,接著將請求交給Servlet進行處理并生成響應袱贮,最后Filter再對服務器響應進行后處理。
通過描述可以看到体啰,F(xiàn)ilter可以在處理Servlet之前進行Request進行預處理攒巍, 嗯, 這就是我們想要的了~
找到著手點荒勇, 就可以開始寫啦柒莉。
首先, 約定好請求參數(shù)形式(POST請求
)
isEnc: Y
content: u1oKuV89GRAaA5/ZCnLUChHAkLfNZq+l/GBkKD4h5G3izv64fABV10ITVFZORRzt+BPgs7ym+adG
ObnHhQAr3aoxa1LDKAsZc/Bn8/iW3m2CdHni+moj2D5sTWFpa0Zu9wAUp2qYiKU6DG0hhZ/gAoVb
t+vYekZfzsvQpU+mEhlggF2GPpva519VPTB2I5O/aTlGcuThjVQEkqhhe6jjR3qi77t0R5jVHVXo
ZLv/94hcm5WUKLxS03TOz4nGyjbE+RzWSlt36/5oYJS+pjmSwo8iKNpcBUYox/PUT4cUVJCQZzSD
jGOpu75H+mG1H4OZ
這里通過isEnc來約定是否對數(shù)據(jù)進行加密沽翔, content是原有的數(shù)據(jù)以name=value&name1=value2
的形式進行拼接兢孝,通過加密得到的數(shù)據(jù)窿凤。具體的加密方式以及數(shù)據(jù)約定方式根據(jù)需求自己定義
- 編寫自己的Filter, 可以實現(xiàn)Spring的Filter跨蟹,也可以實現(xiàn)
javax.servlet.Filter
雳殊, 在這里我實現(xiàn)了Spring的OncePerRequestFilter
@WebFilter({"/*"}) //攔截所有的請求
public class DecodeFilter extends OncePerRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(DecodeFilter.class);
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws ServletException, IOException {
String isEnc = req.getParameter("isEnc");
//是否加密, 是則進行解密
if (org.apache.commons.lang3.StringUtils.isNotEmpty(isEnc) && isEnc.equals("Y")) {
try {
//獲取request的請求數(shù)據(jù)
String bodyInfoEn = req.getParameter("content");
if (org.apache.commons.lang3.StringUtils.isNotEmpty(bodyInfoEn)) {
logger.info("參數(shù)解密前: {}", bodyInfoEn);
String bodyInfoDe = DecodeTool.decrypt(bodyInfoEn);//對content進行解密
logger.info("url: {}窗轩, 解密后: {}", req.getRequestURI(), bodyInfoDe);
if (org.apache.commons.lang3.StringUtils.isNotEmpty(bodyInfoDe)) {
//解析body里面的參數(shù)與uri頭里面的參數(shù)信息
Map<String, String[]> parametersMap = getParamterMap(req, bodyInfoDe);
//由于request請求沒有修改參數(shù)的權(quán)限夯秃,使用篡改后的request代替原先的
ParameterRequestWrapper requestWrapper = new ParameterRequestWrapper(req, parametersMap);
chain.doFilter(requestWrapper, res);
return;
}
}
//如果content不存在則返回錯誤信息
} catch (Exception e) {
logger.error("請求參數(shù)解密失敗...", e);
}
} else { //normal
chain.doFilter(req, res);
}
}
/**
* 獲取解析好的參數(shù)信息
*
* @param req
* @param bodyInfoDe
* @return Map<String,String[]>
*/
private Map<String,String[]> getParamterMap(HttpServletRequest req, String bodyInfoDe) throws UnsupportedEncodingException {
Map<String, String[]> parametersMap = new HashMap<String, String[]>(20);
String[] paramFlex = bodyInfoDe.split("&"); //通過&進行分割
for (String s : paramFlex) {
String[] pairparam = s.split("=");
if (pairparam.length > 1) {
parametersMap.put(pairparam[0], new String[]{pairparam[1]});
}
}
return parametersMap;
}
}
至此, 這個解密Filter就開發(fā)完成了痢艺。整個Filter完成的事情主要是
- 判斷是否加密仓洼,不是加密則直接執(zhí)行下一個Filter
- 加密則根據(jù)約定好的參數(shù)進行解密。
- 將解密之后的參數(shù)放到篡改之后的request腹备。
- 使用篡改之后的Request執(zhí)行下一個Filter
最后衬潦,整個請求數(shù)據(jù)解密的功能就已經(jīng)實現(xiàn),如果需要將返回數(shù)據(jù)進行加密的同學植酥,請參考我下一篇文章《對返回數(shù)據(jù)進行自定義加密》~