需求:
为了数据安全, 前端对传过来的数据进行了加密, 后端这边为了在不修改原有接口的情况下,需要在业务层(Controller)之前做解密,并能在业务层拿到需要的参数。
思路:
众所周知, Request是不允许修改参数的,这个估计和数据安全性有关。但是因为后端使用的Spring MVC 到Controller那层时候, 是获取Request的参数, 进行自动封装的, 所以,为了不大规模的修改原有接口, 只能在Srping MVC在提取Request的参数之前, 对数据进行修改。所以大概的要点有:
- 获取Request的加密数据,进行解密。
- 将解密之后的数据,传入Request。
- 必须要在Spring MVC处理之前。
实现步骤:
- 修改Request 的Paramter
想来大家都知道, 获取request的参数, 无非就是关键的几个方法
-
getParameter(String name)
获取name对应的value, 如果有多个返回第一个
-
getParameterNames()
获取request里面所有的name,返回一个Enumeration类型
-
getParameterValues(String name)
获取name对应的所有value
但Request是没有提供类似于setParamter(String name, Object value)的方法的,所以, 我的插入点,就是通过重写Request的三个获取参数的主要方法,来达到修改参数的目的。
找到插入点之后, 就是开始实现HttpServletRequest
, 可是你会发现你要实现的方法如此之多。。。

全部实现一遍确实不是可好主意...但通过HttpServletRequest的实现结构里,发现了这么一个类:HttpServletRequestWrapper

看名字就知道, 这就是HttpServletRequest的包装类, 进去一看,也果然如此。


这个时候, 一切需要的准备就绪, 开始写我们自定义的Request吧
public class ParameterRequestWrapper extends HttpServletRequestWrapper {
private Map<String,String[]> params;//定义参数集合
//需要一个request和 篡改之后的参数进行实例化。
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() };
}
}
}
- 解密参数, 使用解密后的参数创建一个自定义的Request。
在做这一步的时候, 要考虑一个问题就是,如何把修改后的Request供Spring MVC去使用?Spring MVC的处理是从DispatcherServlet开始的,通过查看Servlet文档可以发现,在执行Servlet的时候, 会先执行Filter。
Filter也称之为过滤器,它是Servlet技术中最实用的技术,Web开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。
它主要用于对用户请求进行预处理,也可以对HttpServletResponse进行后处理。使用Filter的完整流程:Filter对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应,最后Filter再对服务器响应进行后处理。
通过描述可以看到,Filter可以在处理Servlet之前进行Request进行预处理, 嗯, 这就是我们想要的了~
找到着手点, 就可以开始写啦。
首先, 约定好请求参数形式(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来约定是否对数据进行加密, content是原有的数据以name=value&name1=value2
的形式进行拼接,通过加密得到的数据。具体的加密方式以及数据约定方式根据需求自己定义
- 编写自己的Filter, 可以实现Spring的Filter,也可以实现
javax.servlet.Filter
, 在这里我实现了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的请求数据
String bodyInfoEn = req.getParameter("content");
if (org.apache.commons.lang3.StringUtils.isNotEmpty(bodyInfoEn)) {
logger.info("参数解密前: {}", bodyInfoEn);
String bodyInfoDe = DecodeTool.decrypt(bodyInfoEn);//对content进行解密
logger.info("url: {}, 解密后: {}", req.getRequestURI(), bodyInfoDe);
if (org.apache.commons.lang3.StringUtils.isNotEmpty(bodyInfoDe)) {
//解析body里面的参数与uri头里面的参数信息
Map<String, String[]> parametersMap = getParamterMap(req, bodyInfoDe);
//由于request请求没有修改参数的权限,使用篡改后的request代替原先的
ParameterRequestWrapper requestWrapper = new ParameterRequestWrapper(req, parametersMap);
chain.doFilter(requestWrapper, res);
return;
}
}
//如果content不存在则返回错误信息
} catch (Exception e) {
logger.error("请求参数解密失败...", e);
}
} else { //normal
chain.doFilter(req, res);
}
}
/**
* 获取解析好的参数信息
*
* @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就开发完成了。整个Filter完成的事情主要是
- 判断是否加密,不是加密则直接执行下一个Filter
- 加密则根据约定好的参数进行解密。
- 将解密之后的参数放到篡改之后的request。
- 使用篡改之后的Request执行下一个Filter
最后,整个请求数据解密的功能就已经实现,如果需要将返回数据进行加密的同学,请参考我下一篇文章《对返回数据进行自定义加密》~
网友评论