美文网首页
【参数加密】前后端分离请求参数加密与响应结果加密处理

【参数加密】前后端分离请求参数加密与响应结果加密处理

作者: miniy_7 | 来源:发表于2019-12-16 15:51 被阅读0次

    对于安全性要求的加强,避免出现篡改请求结果问题的出现,现对系统中所有的请求和结果响应进行加密处理。系统使用前后端分离设计架构,同时前端部分有Vue 项目也有 jQuery 项目。
    遇到坑最多的地方是Axios 的get方式与jQuery的get方式

    Java 后台处理

    • 定义 request Filter CustomRequestFilter 处理请求参数,拦截所有请求进行解密
    /**
     * 请求拦截器 -- 处理参数解密
     *
     * @author miniy
     */
    @Setter
    public class CustomRequestFilter extends UsernamePasswordAuthenticationFilter {
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            HttpServletRequest req = (HttpServletRequest) servletRequest;
            ParameterRequestWrapper request = new ParameterRequestWrapper(req);
            filterChain.doFilter(request, servletResponse);
        }
    }
    
    • 定义代理类处理参数
    
    /**
     * 请求代理类
     *
     * @author miniy
     */
    public class ParameterRequestWrapper extends HttpServletRequestWrapper {
    
        private byte[] body;
        private Map<String, String[]> params;
    
        public ParameterRequestWrapper(HttpServletRequest request) {
            super(request);
            String method = request.getMethod();
            if (method.toUpperCase().equals("GET")) {
                params = HttpHelper.getParamMap(request);
            } else {
                body = HttpHelper.getBodyString(request).getBytes(Charset.forName("UTF-8"));
            }
    
        }
    
        @Override
        public BufferedReader getReader() {
            return new BufferedReader(new InputStreamReader(getInputStream()));
        }
    
        @Override
        public ServletInputStream getInputStream() {
    
            final ByteArrayInputStream bais = new ByteArrayInputStream(body);
    
            return new ServletInputStream() {
    
                @Override
                public int read() throws IOException {
                    return bais.read();
                }
    
                @Override
                public boolean isFinished() {
                    return false;
                }
    
                @Override
                public boolean isReady() {
                    return false;
                }
    
                @Override
                public void setReadListener(ReadListener listener) {
    
                }
    
            };
        }
    
        @Override
        public String getParameter(String name) {
            String[] values = params.get(name);
            if (values == null || values.length == 0) {
                return null;
            }
            return values[0];
        }
    
        @Override
        public Enumeration<String> getParameterNames() {
            Vector<String> v = new Vector<>();
            Set<Map.Entry<String, String[]>> entrySet = params.entrySet();
            for (Map.Entry<String, String[]> entry : entrySet) {
                v.add(entry.getKey());
            }
            Enumeration<String> en = v.elements();
            return v.elements();
        }
    
        @Override
        public String[] getParameterValues(String name) {
            return params.get(name);
        }
    }
    
    • 注册Filter 到spring security,注册为最外层Filter,拦截所有请求 此实现方式存在问题,将自定义Filter 注册到spring security 中,spring security ignoring 后的url不会被过滤器拦截,改为spring注册

      image.png
    • 提供参数处理工具类

    /**
     * 工具类
     */
    @Slf4j
    public class HttpHelper {
    
        /**
         * 处理 post 请求体参数
         *
         * @param request
         * @return
         */
        public static String getBodyString(HttpServletRequest request) {
            StringBuilder sb = new StringBuilder();
            String result = "";
    
            try (InputStream inputStream = request.getInputStream();
                 BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    sb.append(line);
                }
                result = sb.toString();
                if (!StrUtils.isEmpty(result)) {
                    // 数据处理
                    String[] split = result.split(";");
                    String ras = split[0];
                    // 解密
                    Map<String, Object> map = YamlUtil.builder("application-config.yml");
                    Map<String, Object> sys = (Map<String, Object>) map.get("sys");
                    Map<String, String> requestKey = (Map<String, String>) sys.get("requestKey");
                    String privateKey = requestKey.get("privateKey");
                    String publicKey = requestKey.get("publicKey");
                    RSA rsa = SecureUtil.rsa(privateKey, publicKey);
                    byte[] decrypt = rsa.decrypt(ras, KeyType.PrivateKey);
                    // todo 如果中文乱码,前台需要使用URL encode 加密 此處使用decode 解密
                    String keyIV = IOUtils.toString(decrypt, "utf-8");
                    String[] aesAttr = keyIV.split(";");
                    String key = aesAttr[0];
                    String iv = aesAttr[1];
                    request.setAttribute("aesKey", key);
                    request.setAttribute("aesIv", iv);
    
                    if (split.length > 1) {
                        String bodyData = split[1];
                        AES aes = new AES(Mode.CBC, Padding.ZeroPadding, key.getBytes());
                        aes.setIv(new IvParameterSpec(iv.getBytes()));
                        result = aes.decryptStr(bodyData);
                    }
                }
    
                result = xssBaseLeach(result);
                if (!isPassUrl(request.getRequestURI())) {
                    // xss sql 过滤
                    result = xssSqlLeach(result);
                }
            } catch (IOException e) {
                log.error(e.toString());
            }
            return result;
        }
    
        /**
         * 处理 get 方式请求参数
         *
         * @param request
         * @return
         */
        public static Map<String, String[]> getParamMap(HttpServletRequest request) {
            HashMap<String, String[]> hashMap = new HashMap<>();
    
            String result;
            try {
                result = request.getQueryString();
                String regex = "params=";
                if (result.indexOf(regex) > -1) {
                    // 解决+ 号变成空格的处理
                    result = result.replace(regex, "").replace("+", "%2B");
                    result = URLUtil.decode(result);
                }
                String[] split = result.split(";");
                String ras = split[0];
                // 解密
                Map<String, Object> map = YamlUtil.builder("application-config.yml");
                Map<String, Object> sys = (Map<String, Object>) map.get("sys");
                Map<String, String> requestKey = (Map<String, String>) sys.get("requestKey");
                String privateKey = requestKey.get("privateKey");
                String publicKey = requestKey.get("publicKey");
                RSA rsa = SecureUtil.rsa(privateKey, publicKey);
                byte[] decrypt = rsa.decrypt(ras, KeyType.PrivateKey);
                // todo 如果中文乱码,前台需要使用URL encode 加密 此處使用decode 解密
                String keyIV = IOUtils.toString(decrypt, "utf-8");
                String[] aesAttr = keyIV.split(";");
                String key = aesAttr[0];
                String iv = aesAttr[1];
                request.setAttribute("aesKey", key);
                request.setAttribute("aesIv", iv);
                if (split.length > 1) {
                    String bodyData = split[1];
                    AES aes = new AES(Mode.CBC, Padding.ZeroPadding, key.getBytes());
                    aes.setIv(new IvParameterSpec(iv.getBytes()));
                    result = aes.decryptStr(bodyData);
                    //xss sql 注入处理
                    result = xssBaseLeach(result);
                    if (!isPassUrl(request.getRequestURI())) {
                        // xss sql 过滤
                        result = xssSqlLeach(result);
                    }
    
                    Map<String, Object> toMap = JSONUtils.jsonObjToMap(JSONUtils.ToJsonObj(result));
                    for (String mapKey : toMap.keySet()) {
                        hashMap.put(mapKey, new String[]{toMap.get(mapKey).toString()});
                    }
                }
    
            } catch (IOException e) {
                log.error(e.toString());
            }
            return hashMap;
        }
    
        /**
         * 过滤 参数中存在的 js 代码
         *
         * @param body
         * @return
         */
        private static String xssBaseLeach(String body) {
            return HtmlUtils.removeHtmlTag(body, "script");
        }
    
        /**
         * xss 字符替换
         *
         * @param body
         * @return
         */
        private static String xssSqlLeach(String body) {
    
            if (body == null || body.isEmpty()) {
                return body;
            }
            StringBuilder sb = new StringBuilder(body.length());
            for (int i = 0; i < body.length(); i++) {
                char c = body.charAt(i);
                switch (c) {
                    case '>':
                        sb.append("》");// 转义大于号
                        break;
                    case '<':
                        sb.append("《");// 转义小于号
                        break;
                    case '\'':
                        sb.append("‘");// 转义单引号
                        break;
                    case '\"':
                        sb.append('"');// 转义双引号
                        break;
                    case '&':
                        sb.append("&");// 转义&
                        break;
                    default:
                        String s1 = c + "";
                        String s = s1.replaceAll(".*([';]+|(--)+).*", "");
                        sb.append(s);
                        break;
                }
    
            }
            return sb.toString();
        }
    
        private static boolean isPassUrl(String url) {
            Map<String, Object> map = YamlUtil.builder("application-config.yml");
            Map<String, Object> sys = (Map<String, Object>) map.get("sys");
            List<String> whitelist = (List<String>) sys.get("xssList");
            return whitelist.contains(url);
        }
    
    }
    
    • 定义 response Filter 拦截所有响应结果
    
    /**
     * 响应拦截器 -- 处理参数加密
     *
     * @author miniy
     */
    public class CustomResponseFilter implements Filter {
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    
            HttpServletResponse res = (HttpServletResponse) response;
            ResultResponseWrapper responseWrapper = new ResultResponseWrapper(res);
            chain.doFilter(request, responseWrapper);
            byte[] content = responseWrapper.getContent();
            if (content.length > 0) {
                // 响应结果解密
                String json = IOUtils.toString(content, "utf-8");
                ResponseUtil.out((HttpServletRequest) request, (HttpServletResponse) response, JSONUtils.ToJsonObj(json));
            }
        }
    }
    
    • 定义代理类
    
    /**
     * 响应代理类
     *
     * @author miniy
     */
    public class ResultResponseWrapper extends HttpServletResponseWrapper {
    
        private ByteArrayOutputStream buffer;
        private ServletOutputStream outputStream;
    
        public ResultResponseWrapper(HttpServletResponse response) {
            super(response);
    
            buffer = new ByteArrayOutputStream();
            outputStream = new WrapperOutputStream(buffer);
        }
    
        @Override
        public ServletOutputStream getOutputStream() {
            return outputStream;
        }
    
        @Override
        public void flushBuffer() throws IOException {
            if (null != outputStream) {
                outputStream.flush();
            }
        }
    
        public byte[] getContent()
                throws IOException {
            flushBuffer();
            return buffer.toByteArray();
        }
    
        class WrapperOutputStream extends ServletOutputStream {
            private ByteArrayOutputStream bos;
    
            public WrapperOutputStream(ByteArrayOutputStream bos) {
                this.bos = bos;
            }
    
            @Override
            public void write(int b)
                    throws IOException {
                bos.write(b);
            }
    
            @Override
            public boolean isReady() {
    
                // TODO Auto-generated method stub
                return false;
    
            }
    
            @Override
            public void setWriteListener(WriteListener arg0) {
                // TODO Auto-generated method stub
    
            }
        }
    
    }
    
    • ~~注册过滤器 1. spring security 最后一个过滤器 ~~此实现方式存在问题,将自定义Filter 注册到spring security 中,spring security ignoring 后的url不会被过滤器拦截,改为spring注册


      image.png
    • 提供工具类
    /**
     * 响应加密工具类
     */
    @Slf4j
    public class ResponseUtil {
    
        public static void out(HttpServletRequest request, HttpServletResponse response, Object json) {
            try {
                // 响应结果加密
                // 方式1 aes 加密返回
                String key = (String) request.getAttribute("aesKey");
                String iv = (String) request.getAttribute("aesIv");
                AES aes = new AES(Mode.CBC, Padding.ZeroPadding, key.getBytes());
                aes.setIv(new IvParameterSpec(iv.getBytes()));
                String data = JSON.toJSONString(json);
                String result = aes.encryptBase64(data);
    
                // 方式2 rsa 加密返回
             /*   Map<String, Object> map = YamlUtil.builder("application-config.yml");
                Map<String, Object> sys = (Map<String, Object>) map.get("sys");
                Map<String, String> requestKey = (Map<String, String>) sys.get("responseKey");
                String privateKey = requestKey.get("privateKey");
                String publicKey = requestKey.get("publicKey");
                RSA rsa = SecureUtil.rsa(privateKey, publicKey);
                String data = JSON.toJSONString(json);
                // 防止中文乱码先进行 data url 编码
                String encode = URLUtil.encode(data);
                String result = rsa.encryptBase64(encode, KeyType.PublicKey);*/
    
                response.setContentType("text/html;charset=utf-8");
                response.setHeader("Content-type", "application/json;charset=utf-8");
                response.setStatus(HttpServletResponse.SC_OK);
                response.getWriter().print(result);
            } catch (java.io.IOException e) {
                log.error(e.toString());
            }
        }
    }
    

    编辑更改过滤器的注册
    因注册到spring security 组件上无法拦截ignoing 的请求,更改为spring boot 方式注册,注意点为order 排序的设置,响应最简单设置为最大就好。关键点是请求filter的位置非常重要。这里要放在 spring security 内置过滤器前,spring CorsFilter 之后,此处多次测试猜的数为-100,暂未找到更科学方法。

       @Bean
        public FilterRegistrationBean requestFilterRegistration() {
            FilterRegistrationBean registration = new FilterRegistrationBean();
            registration.setFilter(new CustomRequestFilter());
            registration.addUrlPatterns("/*");
    //        registration.setOrder(Integer.MIN_VALUE);
            registration.setOrder(-100);
            registration.setName("requestFilter");
            return registration;
        }
    
        @Bean
        public FilterRegistrationBean responseFilterRegistration() {
            FilterRegistrationBean registration = new FilterRegistrationBean();
            registration.setFilter(new CustomResponseFilter());
            registration.addUrlPatterns("/*");
            registration.setOrder(Integer.MAX_VALUE);
            registration.setName("responseFilter");
            return registration;
        }
    

    JsonUtils 工具类

    import cn.hutool.json.JSONObject;
    import cn.hutool.json.JSONUtil;
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONArray;
    import com.alibaba.fastjson.serializer.SerializerFeature;
    
    import java.util.List;
    import java.util.Map;
    
    /**
     * json 处理工具类
     */
    public class JSONUtils {
    
        public static JSONObject parseObj(Object object) {
            return JSONUtil.parseObj(object);
        }
    
        /**
         * 字符串转JSONArray
         *
         * @param json
         * @return
         */
        public static JSONArray toJsonList(String json) {
            return JSON.parseArray(json);
        }
    
    
        /**
         * 对象转化为字符串
         *
         * @param obj
         * @return
         */
        public static String JsonToString(Object obj) {
            return JSON.toJSONString(obj);
        }
    
        /**
         * 带类型信息的转json串
         *
         * @param obj
         * @return
         */
        public static String JsonToStringAndType(Object obj) {
            return JSON.toJSONString(obj, SerializerFeature.WriteClassName);
        }
    
        /**
         * Json 字符串转为 JSONObject
         * JSONObject 对象类似Map
         * 直接通过get()方法 获取对象
         *
         * @return
         */
        public static com.alibaba.fastjson.JSONObject ToJsonObj(String json) {
            return JSON.parseObject(json);
        }
    
        public static cn.hutool.json.JSONObject ToJsonObj(Object obj) {
            return JSONUtil.parseObj(obj);
        }
    
        /**
         * map 转 Bean
         *
         * @param map
         * @param beanClass
         * @param <T>
         * @return
         */
        public static <T> Object toBean(Map map, Class<T> beanClass) {
            return JSONUtil.toBean(JSONUtil.parseFromMap(map), beanClass, true);
        }
    
        public static <T> Object toBean(com.alibaba.fastjson.JSONObject jsonObject, Class<T> beanClass) {
            return JSONUtil.toBean(JSONUtil.parseObj(jsonObject.toJSONString()), beanClass, true);
        }
    
    
        /**
         * JsonArray转java的List
         *
         * @param jsonArray
         * @param c
         * @return
         */
        public static List JSONArrayToList(JSONArray jsonArray, Class c) {
            String string = com.alibaba.fastjson.JSONObject.toJSONString(jsonArray, SerializerFeature.WriteClassName);
            List list = com.alibaba.fastjson.JSONObject.parseArray(string, c);
            return list;
        }
    
        public static Map jsonObjToMap(com.alibaba.fastjson.JSONObject jsonObject) {
            return JSONUtil.toBean(JSONUtil.parseObj(jsonObject.toJSONString()), Map.class);
        }
    
    }
    
    

    Java结束

    前端处理

    • jQuery
      引入CryptoJS与jsencrypt支持
    view.ajaxFilter = function () {
    
          var ajax = $.ajax;//  修改ajax方法的默认实现
          $.ajax = function (options) {
              // aes 数据加密
              var u32 = uuid(32);
              var u16 = uuid(16);
              var key = CryptoJS.enc.Latin1.parse(u32);
              var iv = CryptoJS.enc.Latin1.parse(u16);
    
              if (options.type == 'get' || options.type == 'GET') {
                  if (options.url.indexOf('?') > -1) {
                      var split = options.url.split('?');
                      var params = split[1];
                      var paramObj = params.split("&");
                      for (var i = 0; i < paramObj.length; i++) {
                          options.data[paramObj[i].split("=")[0]] = unescape(paramObj[i].split("=")[1]);
                      }
                      options.url = split[0];
                  }
              }
              // data加密
              if (typeof options.data == 'object') {
                  options.data = JSON.stringify(options.data);
              }
    
              var encrypted = CryptoJS.AES.encrypt(options.data, key, {
                  iv: iv,
                  mode: CryptoJS.mode.CBC,
                  padding: CryptoJS.pad.ZeroPadding
              });
              // ras 数据加密
              var publicKey = ' 11';
              jsencrypt = new JSEncrypt();
              jsencrypt.setPublicKey(publicKey);
              // todo 如果中文乱码,前台需要使用URL encode 加密 此處使用decode 解密
              var ras = jsencrypt.encrypt(u32 + ";" + u16);
    
              options.data = ras + ";" + encrypted.toString();
    
    
              var dataFilter = options.dataFilter;    //  对用户配置的success方法进行代理  
              function ns(datas, type) {
                  // 数据解密
                  // 方式1 aes 解密
                  var decrypt = CryptoJS.AES.decrypt(datas, key, {
                      iv: iv,
                      mode: CryptoJS.mode.CBC,
                      padding: CryptoJS.pad.ZeroPadding
                  });
                  var data = decrypt.toString(CryptoJS.enc.Utf8);
    
                  // 方式2 rsa 解密
                  /* var privateKey = '私密';
                   var decrypt = new JSEncrypt();
                   decrypt.setPrivateKey(privateKey);
                   var uncrypted = decrypt.decryptLong2(datas);
                   var data = decodeURIComponent(uncrypted);*/
                  return data;
              }
    
              options.dataFilter = ns;
              return ajax(options);
          }
      }
    
    • Vue
      安装crypto-js与jsencrypt
    npm install jsencrypt
    npm install crypto-js
    
    import Vue from 'vue'
    import vueAxios from 'vue-axios'
    import axios from 'axios'
    import { getToken } from '@/utils/auth'
    import { uuid } from '@/utils/utils'
    import cryptoJs from 'crypto-js'
    import JSEncrypt from 'jsencrypt'
    import LE from '@/assets/config'
    import store from '../store'
    import router from '../router'
    import { Toast } from 'vant'
    
    Vue.use(vueAxios, axios)
    Vue.use(Toast).use(cryptoJs)
    
    // 创建axios实例
    const service = axios.create({
    baseURL: process.env.BASE_API, // api 的 base_url
    timeout: 5000
    })
    
    const u32 = uuid(32)
    const u16 = uuid(16)
    const key = cryptoJs.enc.Latin1.parse(u32)
    const iv = cryptoJs.enc.Latin1.parse(u16)
    
    // request拦截器
    service.interceptors.request.use(
    (config) => {
      const con = config
      if (store.getters.token) {
        con.headers[LE.RequestTokenKey] = getToken()
      }
      // data数据加密
      if (typeof con.data === 'object') {
        con.data = JSON.stringify(con.data)
      }
      const encrypted = cryptoJs.AES.encrypt(con.data, key, {
        iv: iv,
        mode: cryptoJs.mode.CBC,
        padding: cryptoJs.pad.ZeroPadding
      })
      // ras 数据加密
      var publicKey = '123123'
      const jsencrypt = new JSEncrypt()
      jsencrypt.setPublicKey(publicKey)
      const ras = jsencrypt.encrypt(u32 + ';' + u16)
    
      if (con.method === 'post' || con.method === 'POST') {
        con.headers['Content-Type'] = 'application/json; charset=utf-8'
        con.data = ras + ';' + encrypted.toString()
      } else if (con.method === 'get' || con.method === 'GET') {
        con.headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'
        const params = ras + ';' + encrypted.toString()
        con.params = { params }
      }
      return con
    },
    (error) => {
      Promise.reject(error)
    },
    )
    
    // response 拦截器
    service.interceptors.response.use(
    response => {
      let res = response.data
      // 数据解密
      // 方式1 aes 解密
      const decrypt = cryptoJs.AES.decrypt(res, key, {
        iv: iv,
        mode: cryptoJs.mode.CBC,
        padding: cryptoJs.pad.ZeroPadding
      })
      res = cryptoJs.enc.Utf8.stringify(decrypt).toString()
      res = JSON.parse(res)
    
      if (res.code === LE.response.NO_AUTH) {
        store.dispatch('FedLogOut')
          .then(() => {
            router.push({ path: '/login' })
          })
      } else if (res.code === LE.response.OK) {
        return res
      } else {
        const msg = '状态码:' + res.code + '异常提醒【' + res.msg + '】'
        Toast.fail({
          message: msg
        })
      }
    }
    ,
    error => {
      store.dispatch('FedLogOut')
        .then(() => {
          router.push({ path: '/login' })
        })
      Promise.reject(error)
    },
    )
    
    export default service
    
    
    

    加密方式

    上诉把程序以及思路提供,可以根据自己需要的加密解密方式进行处理,以下两种方式我进行了尝试最终选择了第二种。

    • 方式一:RAS 加密
      • 什么是RAS,RSA加密算法是一种非对称加密算法,由私钥与公钥组成一对密钥,通过公钥加密私钥界面方式处理。公钥可以暴漏在客户端,私钥必须保密存储在服务端。非对称加密算法安全性更高,但解密效率特别慢
      • 在请求时,使用请求的公钥对请求参数进行加密,达到服务端过滤器时,使用私钥进行解密。完成请求参数的加密。
      • 在服务端响应时,服务端采用响应的公钥加密,客户端私钥进行解密。完成结果的加密。
        上述中,使用两对密钥处理。优点安全性强。缺点1 响应私钥存储在客户端 2 当出现大文本或者长文本进行加解密时效率非常慢,同时大部分工具类不支持长文本的直接解密,需要使用分段加密-分段解密处理,尽量不要这样使用
    • 方式二:RAS + AES 加密
      • 什么是AES,AES算法称为密码学中的高级加密标准,这个标准用来替代原先的DES。是一种对称加密的算法。加解密效率较快,但安全性相对较弱。
      • 请求时,客户端生成AES的密钥Key以及偏移量IV,对文本内容进行对称加密,然后使用RAS对客户端生成AES的密钥Key以及偏移量IV进行非对称加密,进行服务端传递。
      • 响应时,后台使用传递的客户端生成AES的密钥Key以及偏移量IV进行结果加密,客户端是用同一个密钥Key以及偏移量IV进行解密。
        RAS + AES 方式加密是指采用RAS算法对AES的加密的密钥key(32位)以及偏移量iv(16位)进行非对称加密,使用AES算法对需要传递的文本内容进行对称加密。这种方式即可以保证安全性又能提高文本解析的效率。推荐使用。

    相关文章

      网友评论

          本文标题:【参数加密】前后端分离请求参数加密与响应结果加密处理

          本文链接:https://www.haomeiwen.com/subject/aogrnctx.html