修改Request对象的Paramter。---对请求数据进行解密

需求:

为了数据安全, 前端对传过来的数据进行了加密, 后端这边为了在不修改原有接口的情况下,需要在业务层(Controller)之前做解密,并能在业务层拿到需要的参数。

思路:

众所周知, Request是不允许修改参数的,这个估计和数据安全性有关。但是因为后端使用的Spring MVC 到Controller那层时候, 是获取Request的参数, 进行自动封装的, 所以,为了不大规模的修改原有接口, 只能在Srping MVC在提取Request的参数之前, 对数据进行修改。所以大概的要点有:

  1. 获取Request的加密数据,进行解密。
  2. 将解密之后的数据,传入Request。
  3. 必须要在Spring MVC处理之前。

实现步骤:

  1. 修改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, 可是你会发现你要实现的方法如此之多。。。

修改Request对象的Paramter。---对请求数据进行解密_第1张图片
HttpServletRequest需要实现的一部分方法

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

修改Request对象的Paramter。---对请求数据进行解密_第2张图片
查看实现或继承了HttpServletRequest的类

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


修改Request对象的Paramter。---对请求数据进行解密_第3张图片
HttpServletRequest的构造函数, 一定需要个request作为参数进行包装
修改Request对象的Paramter。---对请求数据进行解密_第4张图片
默认使用Request的获取参数方法

这个时候, 一切需要的准备就绪, 开始写我们自定义的Request吧

public class ParameterRequestWrapper extends HttpServletRequestWrapper {

    private Map params;//定义参数集合

    //需要一个request和 篡改之后的参数进行实例化。
    public ParameterRequestWrapper(HttpServletRequest request, Map 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() };
        }
    }

}
  1. 解密参数, 使用解密后的参数创建一个自定义的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 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
     */
    private Map getParamterMap(HttpServletRequest req, String bodyInfoDe) throws UnsupportedEncodingException {

        Map parametersMap = new HashMap(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完成的事情主要是

  1. 判断是否加密,不是加密则直接执行下一个Filter
  2. 加密则根据约定好的参数进行解密。
  3. 将解密之后的参数放到篡改之后的request。
  4. 使用篡改之后的Request执行下一个Filter

最后,整个请求数据解密的功能就已经实现,如果需要将返回数据进行加密的同学,请参考我下一篇文章《对返回数据进行自定义加密》~

你可能感兴趣的:(修改Request对象的Paramter。---对请求数据进行解密)