前后端分离的参数加解密

4 篇文章 0 订阅
4 篇文章 0 订阅

背景缘由这样, 之前项目里图方便和灵活, 封装了一个可以由前端组装查询条件的接口, 为了应对千变万化的查询需求变更..你懂的.

大概长这样:

/***
* json格式,var obj={}; obj.masterdataid = "=;382378" <br>
*
**/
public ApiResult<E> queryDataForCommon(String tableCode,String jsonArgs,String orderFields){
     //... 大部分时间 tableCode和tableName一致了, 相当于表名/字段暴露了.
     //还有像查询条件里面包含了  or  括号 等一类的条件,直接被云平台当xss攻击拦截了
 
}

渐渐这个接口也被封装成了通用组件接口, 并且衍生出了非常多的CV(复制黏贴)变种, 简单数了一下, 大大小小超过10个了, 真实业务需求各不相同, 也已经被应用散落在各个项目里.

在各个项目都逐渐都上了云之后,除了被识别为xss注入攻击, 还会有部分扫描工具能识别敏感数据明文传输.    设计考虑主要如下: 

 1 接口已被应用,不能直接修改.(数量多,也懒)

2 插件式添加, 项目自行选择.

3 可配置开关, 添加后也可以关.

加密方式选择的是对程加密, 或者转码脱敏.

还是简单来个图吧:

 寻求SM4前端加密组件跟后端SM4加密匹配遇到了点困难, 转入为待办项, 按照实际需求目前也不需要这么高强度的脱敏密文处理, 于是加解密换成了hex+encode,  开始使用了base64+hex, 参数长度膨胀过度了, 放弃掉.

关键核心代码示意:

前端:

参数加密->发起请求


      <script>
      	      function stringToHex(str){
      				var val = new Array();
      				for(var i=0;i<str.length;i++){
     					val.push(str.charCodeAt(i).toString(16));
                                     }
      				return val.join("")*
      			}
              var obj = {};
              obj.clsCode = "tableCode";
              var hexData2 = stringToHex(encodeURI(JSON.stringify(obj)));
              $.post({
                 url:'',
                 param:{dcbEncryptParam:hexData2}
                 ...省略
              });
      </script>
     

后端代码:

  dcbsecurity.yml配置:


#需要加密参数访问的API
  request:
    encrypt:
      #支持 SM4 base64 hex,需要与前端约定, 目前是hex(encodeURI(jsparam))
      algorithm:
      # 根据配置.可以配置通配符, 如 /infoclass/admin/*, 也可以配置全量地址, 多个使用 逗号","分割
      apiList: /infoclass/admin/*,/infoclassdata/web/listDataByClsCode

 拦截器注册:

    @Bean
    public FilterRegistrationBean<RequestDecodeFilter> requestDecodeFilter() {

            FilterRegistrationBean<RequestDecodeFilter> registrationBean = new FilterRegistrationBean<RequestDecodeFilter>();
            RequestDecodeFilter requestDecodeFilter = new RequestDecodeFilter();
            registrationBean.setFilter(requestDecodeFilter);
            //根据配置添加需要参数解密的api
            if(apiList!=null&&apiList.length>0) {
                registrationBean.setUrlPatterns(Arrays.asList(apiList));
            }else{
                List<String> urls = new ArrayList<>();
                //为空时默认添加一个
                urls.add("/encryptRequestParamToAdd");
                registrationBean.setUrlPatterns(urls);
            }
            registrationBean.setOrder(99);
            return registrationBean;
        }

请求解密拦截器:

package com.dcboot.base.config.security.filter;

import com.alibaba.fastjson.JSON;
import com.dcboot.base.config.security.support.DecryptHttpServletRequestWrapper;
import com.dcboot.base.config.web.WebFilterConfig;
import com.dcboot.base.util.ApiResult;
import com.dcboot.module.util.CommonUtil;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 参数加密请求处理过滤器
 * @Author fs_wuhaixin
 * @Date 2022/6/2017:01
 */
public class RequestDecodeFilter implements Filter {

    private String targetApis = "";

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        targetApis = filterConfig.getInitParameter(WebFilterConfig.TARGET_APIS);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        String dcbEncryptParam = request.getParameter(WebFilterConfig.ENCODE_PARAM_NAME);
        if(CommonUtil.isNotBlank(dcbEncryptParam)){
            filterChain.doFilter(new DecryptHttpServletRequestWrapper((HttpServletRequest)request),response);
        }else{
            //如果有开启目标拦截API,则判断请求路径是否在拦截API列表中.
            if(targetApis!=null&&!"".equals(targetApis)){
                String requestApi =     ((HttpServletRequest)request).getRequestURI().replace(((HttpServletRequest) request).getContextPath(),"");
                if(targetApis.contains(requestApi)){
                    renderError((HttpServletResponse) response);
                    return;
                }else{
                    filterChain.doFilter(request,response);
                }
            }else{
                filterChain.doFilter(request,response);
            }
        }
    }

    public void renderError(HttpServletResponse resp) throws IOException {
        ApiResult result = ApiResult.error(500,"您请求的api已要求使用加密参数访问,请联系接口提供方!");
        resp.reset();
        resp.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset = UTF-8");
        resp.getWriter().write(JSON.toJSONString(result));
        resp.flushBuffer();
        return;
    }
}

解密参数Request包装类,重点重写 getParameterMap方法.

/**
 * 用于封装解码后重新封装httpRequest
 * @Author fs_wuhaixin
 * @Date 2022/7/10:11
 */
public class DecryptHttpServletRequestWrapper extends HttpServletRequestWrapper {

    private Map<String,Object> decryptParamMap;

    public DecryptHttpServletRequestWrapper(HttpServletRequest request) throws UnsupportedEncodingException {
        super(request);
        String dcbEncryptParam = request.getParameter(WebFilterConfig.ENCODE_PARAM_NAME);
        String decryptedParam = decryptParam(dcbEncryptParam);
        decryptParamMap = JSONObject.parseObject(decryptedParam,LinkedHashMap.class,Feature.OrderedField);
    }

    @Override
    public String getParameter(String name) {
        return (String) this.decryptParamMap.get(name);
    }

    @Override
    public String[] getParameterValues(String name) {
        return getParameterMap().get(name);
    }

    @Override
    public Map<String, String[]> getParameterMap(){
        Map<String, String[]> newParamMap = new LinkedHashMap<>();
        for(Map.Entry<String,Object> entry:this.decryptParamMap.entrySet()){
            newParamMap.put(entry.getKey(),new String[]{entry.getValue().toString()});
        }
        return newParamMap;
    }

    @Override
    public Enumeration<String> getParameterNames() {
        return Collections.enumeration(decryptParamMap.keySet());
    }

    // todo. 前端SM4加密问题未解决,目前先支持Hex+自定义加解密.
    private String decryptParam(String encryptData) throws UnsupportedEncodingException {
        return URLDecoder.decode(HexUtil.decodeHexStr(encryptData, StandardCharsets.UTF_8),StandardCharsets.UTF_8.name());
    }

单元测试类(已通过):

   /**
     *  明文传参测试
     *  响应结果{"code":"500","data":"","message":"您请求的api已要求使用加密参数访问,请联系接口提供方!","success":false}
     */
    @Test
    public void testInfoDataPostUnCrypt(){
        String url = "http://localhost:8080/dcb/infoclass/web/listDataByClsCode?clsCode=cpw_test_01";
        String str = HttpUtil.get(url);
        log.info(str);
    }
    /**
     * 密文传参测试
     * 需要在 dcbsecurity.yml中添加apiList配置,支持表达式,如 /infoclass/*
     * 响应结果与旧接口无异.
     */
    @Test
    public void testInfoDataPost(){
        String url = "http://localhost:8080/dcb/infoclass/web/listDataByClsCode?dcbEncryptParam=842252347428253232636c73436f64652532323a2532326370775f746573745f3031253232253744";
        String str = HttpUtil.get(url);
        log.info(str);
    }

至此, 前后端分离的参数加解密插件已完成. 可独立封装为spring-boot maven模块使用.

后续:

当天下午又优化了一下, 做了个逻辑调整和简单的加密.

逻辑调整为:  若没配置yml中的ApiList, 默认拦截所有,  并拦截判断参数请求是否包含ENCRYPT_PARAM_NAME参数,  若存在, 则默认按照加密请求处理.

若请求参数中不包含加密请求参数, 同时又存在于encrypt_api_list中, 则返回警告.  

另外一个改动是做了个自定义的简单加密处理, 往hex里添加随机数, 类似加盐的处理.解密方法就不贴了, 有兴趣的同学自行解密一下:

function dcbStringEncrpyt(str){
				var rand1 = getRandNum(10);
				var val = new Array();
				for(var i=0;i<str.length;i++){
					val.push(str.charCodeAt(i).toString(16));
				}
				var hexStr = val.join("");
				hexStr = hexStr.substring(0,rand1)+""+rand1+hexStr.substring(rand1);
				hexStr = ""+rand1+hexStr;
				return hexStr
			}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

it夜猫who

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值