九天猎人 发表于 2024-10-26 16:39:12

接口安全防线加解密:springboot 全局-指定接口解密(同时支持参数在body和

接口安全防线加解密:springboot 全局/指定接口解密(同时支持参数在body和param)

上风:通过注解形式,不必要改变原接口请求参数,在拦截器里面把加密数据解密为原接口请求参数。同时支持application/x-www-form-

urlencoded和application/json 的解密
1.原理

1.1.过滤器,过滤所有请求,使用HttpServletRequestWrapper解决request中流读取一次的处理,方便后续修改请求内容

1.2.自界说注解,通过自界说注解可以标识,指定哪些接口在拦截器中处理数据

1.3.拦截器,拦截带有指定注解的请求,把数据进行加密解密后返回处理

2.支持范围

2.1.实际可以自己改造适合多种环境处理,已支持以下

1.application/json 加密参数在body
2.application/x-www-form-urlencoded 支持参数在body大概在param
2.2.为什么不用RequestBodyAdvice

1.因为RequestBodyAdvice只支持body内容的数据加解密处理,具有局限性。
3.详细实现代码

3.1. 过滤器(目的读取request,为后续自界说数据做铺垫)

/**
* @Author: bright chen
*/
@WebFilter(value = "/*", filterName = "uriFormatFilter")
public class UriFormatFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {

      String uri = httpServletRequest.getRequestURI();
      String newUri = uri.replace("//","/");
      httpServletRequest = new HttpServletRequestWrapper(httpServletRequest){
            @Override
            public String getRequestURI() {
                return newUri;
            }
      };
      ServletRequest requestWrapper=new RequestWrapper(httpServletRequest);
      if(requestWrapper!=null){
            filterChain.doFilter(requestWrapper,httpServletResponse);
      }else{
            filterChain.doFilter(httpServletRequest, httpServletResponse);
      }
    }
}
3.2.自界说HttpServletRequestWrapper(重点1,param赋值,body赋值,param pojo读取时赋值)

/**
* @Author: bright chen
*/
public class RequestWrapper extends HttpServletRequestWrapper {
    private String body;
    private Map<String, String[]> params = new HashMap<String, String[]>();

    public RequestWrapper(HttpServletRequest request) {
      super(request);
      StringBuilder stringBuilder = new StringBuilder();
      BufferedReader bufferedReader = null;
      InputStream inputStream = null;
      try {
            inputStream = request.getInputStream();
            if (inputStream != null) {
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                char[] charBuffer = new char;
                int bytesRead = -1;
                while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                  stringBuilder.append(charBuffer, 0, bytesRead);
                }
            } else {
                stringBuilder.append("");
            }
      } catch (IOException ex) {

      } finally {
            if (inputStream != null) {
                try {
                  inputStream.close();
                } catch (IOException e) {
                  e.printStackTrace();
                }
            }
            if (bufferedReader != null) {
                try {
                  bufferedReader.close();
                } catch (IOException e) {
                  e.printStackTrace();
                }
            }
      }
      body = stringBuilder.toString();
      this.params.putAll(request.getParameterMap());
    }


    @Override
    public Map<String, String[]> getParameterMap() {
      return params;
    }


    @Override
    public ServletInputStream getInputStream() throws IOException {
      final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
      ServletInputStream servletInputStream = new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {
            }

            @Override
            public int read() throws IOException {
                return byteArrayInputStream.read();
            }
      };
      return servletInputStream;
    }


    // 重载一个构造方法
    public RequestWrapper(HttpServletRequest request, Map<String, Object> extendParams, String body) {
      this(request);
      if (body != null && body.length() > 0) {
            setBody(body);
      }
      if (extendParams.size() > 0) {
            addAllParameters(extendParams);// 这里将扩展参数写入参数表
      }
    }


    @Override
    public BufferedReader getReader() throws IOException {
      return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }

    public String getBody() {
      return this.body;
    }

    // 赋值给body字段
    public void setBody(String body) {
      this.body = body;
    }


    @Override
    public String getParameter(String name) {// 重写getParameter,代表参数从当前类中的map获取
      String[] values = params.get(name);
      if (values == null || values.length == 0) {
            return null;
      }
      return values;
    }

    public String[] getParameterValues(String name) {// 同上
      return params.get(name);
    }


    /**
   * 参数为pojo类型时,会通过此方法获取所有的请求参数并进行遍历,对pojo属性赋值
   *
   * @return
   */
    @Override
    public Enumeration<String> getParameterNames() {// 同上
      ArrayList<String> list = list = new ArrayList<>();
      for (Map.Entry<String, String[]> entry : params.entrySet()) {
            list.add(entry.getKey());
      }
      return Collections.enumeration(list);
    }

    public void addAllParameters(Map<String, Object> otherParams) {// 增加多个参数
      for (Map.Entry<String, Object> entry : otherParams.entrySet()) {
            addParameter(entry.getKey(), entry.getValue());
      }
    }


    public void addParameter(String name, Object value) {// 增加参数
      if (value != null) {
            if (value instanceof String[]) {
                params.put(name, (String[]) value);
            } else if (value instanceof String) {
                params.put(name, new String[]{(String) value});
            } else {
                params.put(name, new String[]{String.valueOf(value)});
            }
      }
    }
}
3.3.自界说注解 @DecryptApi,后期在controller层带上这个注解则该controller层接口会被过滤器及拦截器进行解密处理。

/**
* 支持params和body的解密
* 支持
* @Author: bright chen
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.PARAMETER})
public @interface DecryptApi {
}
3.4. (核心代码) 拦截器 DecryptInterceptor,对带有@DecryptApi注解的接口数据进行解密处理后重新赋值,注意配置

拦截所有请求
/**
* @Author: bright chen
* api 解密拦截器
*/
public class DecryptInterceptor extends HandlerInterceptorAdapter {
    private static Logger logger = LogManager.getLogger(DecryptInterceptor.class);
    @Autowired
    private EncryptProperties encryptProperties;

    /**
   * Controller之前执行
   * preHandle:拦截于请求刚进入时,进行判断,需要boolean返回值,如果返回true将继续执行,如果返回false,将不进行执行。一般用于登录校验
   * 1.当preHandle方法返回false时,从当前拦截器往回执行所有拦截器的afterCompletion方法,再退出拦截器链。也就是说,请求不继续往下传了,直接沿着来的链往回跑。
   * 2.当preHandle方法全为true时,执行下一个拦截器,直到所有拦截器执行完。再运行被拦截的Controller。然后进入拦截器链,运行所有拦截器的postHandle方法,
   * 完后从最后一个拦截器往回执行所有拦截器的afterCompletion方法.
   */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
      try {


            if (handler instanceof HandlerMethod) {
                HandlerMethod method = (HandlerMethod) handler;
                // RequireLogin annotation = method.getMethodAnnotation(RequireLogin.class);
                if (method.hasMethodAnnotation(DecryptApi.class)) {
                  // 需要对数据进行加密解密
                  // 1.对application/json类型
                  String contentType = request.getContentType();
                  if (contentType == null && !"GET".equals(request.getMethod())) {
                        // 请求不通过,返回错误信息给客户端
                        responseResult(response, response.getWriter(), TResponse.FAIL("Decrypt failed"));
                        return false;
                  }
                  String requestBody = null;
                  boolean shouldEncrypt = false;

                  if ((contentType != null && StringUtils.substringMatch(contentType, 0,
                            MediaType.APPLICATION_FORM_URLENCODED_VALUE)) || "GET".equals(request.getMethod())) {
                        // 1.application/x-www-form-urlencoded 支持参数在body或者在param
                        shouldEncrypt = true;
                        requestBody = convertFormToString(request);
                        if (requestBody == null || "{}".equals(requestBody)) {
                            requestBody = URLDecoder.decode(convertInputStreamToString(request.getInputStream()),
                                    "UTF-8");
                            List<String> uriToList =
                                    Stream.of(requestBody.split("&")).map(elem -> new String(elem)).collect(Collectors.toList());

                            Map<String, String> uriToListToMap = new HashMap<>();


                            for (String individualElement : uriToList) {
                              if (individualElement.split("=") != null && !"".equals(individualElement.split("="))) {
                                    uriToListToMap.put(individualElement.split("="),
                                          individualElement.substring(individualElement.split("=").length() + 1));
                              }

                            }
                            requestBody = JSONObject.toJSONString(uriToListToMap);
                        }

                  } else if (StringUtils.substringMatch(contentType, 0, MediaType.APPLICATION_JSON_VALUE)) {
                        // application/json 支持加密参数在body
                        shouldEncrypt = true;
                        requestBody = convertInputStreamToString(request.getInputStream());

                  }

                  if (requestBody == null || "{}".equals(requestBody)||!shouldEncrypt) {
                        return true;
                  } else {

                        String result = decodeApi(JSON.parseObject(requestBody, StdRequestApi.class),
                              encryptProperties.getPrivateKey());
                        if (result == null) {
                            // 请求不通过,返回错误信息给客户端
                            responseResult(response, response.getWriter(), TResponse.FAIL("Decrypt failed"));
                            return false;
                        }
                        JSONObject jasonObject = JSONObject.parseObject(result);
                        Map map = (Map) jasonObject;
                        if (request instanceof RequestWrapper) {
                            RequestWrapper requestWrapper = (RequestWrapper) request;
                            requestWrapper.setBody(result);
                            requestWrapper.addAllParameters(map);
                            // requestWrapper = new RequestWrapper(request, map, result);
                            return true;
                        }
                  }
                } else {
                  String contentType = request.getContentType();
                  if (contentType != null && contentType.length() > 0 && StringUtils.substringMatch(contentType, 0,
                            MediaType.APPLICATION_FORM_URLENCODED_VALUE)) {
                        // 1.application/x-www-form-urlencoded 支持参数在body或者在param
                        String requestBody = convertFormToString(request);
                        if (requestBody == null || "{}".equals(requestBody)) {
                            // 把流数据放进param中,不解密
                            requestBody = URLDecoder.decode(convertInputStreamToString(request.getInputStream()),
                                    "UTF-8");
                            List<String> uriToList =
                                    Stream.of(requestBody.split("&")).map(elem -> new String(elem)).collect(Collectors.toList());

                            Map<String, Object> uriToListToMap = new HashMap<>();

                            for (String individualElement : uriToList) {
                              if (individualElement.split("=") != null && !"".equals(individualElement.split("="))) {
                                    uriToListToMap.put(individualElement.split("="),
                                          individualElement.substring(individualElement.split("=").length() + 1));
                              }
                            }
                            if (request instanceof RequestWrapper) {
                              RequestWrapper requestWrapper = (RequestWrapper) request;
                              requestWrapper.setBody(requestBody);
                              requestWrapper.addAllParameters(uriToListToMap);
                              return true;
                            }
                        }
                  }
                }

                return true;
            }
            return true;

      } catch (Exception e) {
            e.printStackTrace();
            logger.error(e.getMessage() + "异常地址:" + request.getServletPath());
            responseResult(response, response.getWriter(), TResponse.FAIL("Decrypt failed"));
            return false;
      }
    }


    /**
   * 返回信息给客户端
   *
   * @param response
   * @param tResponse
   */
    private void responseResult(HttpServletResponse response, TResponse tResponse) throws IOException {
      response.setContentType(HttpConstant.CONTENT_TYPE_JSON);
      String json = JSONObject.toJSONString(tResponse);
      PrintWriter out = response.getWriter();
      out.print(json);
      out.flush();
      out.close();
    }

    private String convertFormToString(HttpServletRequest request) {
      Map<String, String> result = new HashMap<>(8);
      Enumeration<String> parameterNames = request.getParameterNames();
      while (parameterNames.hasMoreElements()) {
            String name = parameterNames.nextElement();
            result.put(name, request.getParameter(name));
      }
      try {
            return JSON.toJSONString(result);
      } catch (Exception e) {
            throw new IllegalArgumentException(e);
      }
    }

    private String convertInputStreamToString(InputStream inputStream) throws IOException {
      return StreamUtils.copyToString(inputStream, Charset.forName("UTF-8"));
    }


    public String decodeApi(StdRequestApi stdRequestApi, String apiPrivateKey) {
      try {
            // 1.rsa解密
            // 2.AES验签
            // 3.AES解密
            return deData;
      } catch (Exception e) {
            e.printStackTrace();
            return null;
      }
    }

    /**
   * 返回信息给客户端
   *
   * @param response
   * @param out
   * @param tResponse
   */
    private void responseResult(HttpServletResponse response, PrintWriter out, TResponse tResponse) {
      response.setContentType(HttpConstant.CONTENT_TYPE_JSON);
      String json = JSONObject.toJSONString(tResponse);
      out.print(json);
      out.flush();
      out.close();
    }


}
3.5.公钥私钥配置读取

/**
* @Author: bright chen
*/
@Component
@ConfigurationProperties(prefix = "api")
public class EncryptProperties {
    private String privateKey;
    private String publicKey;

    public String getPrivateKey() {
      return privateKey;
    }

    public void setPrivateKey(String privateKey) {
      this.privateKey = privateKey;
    }

    public String getPublicKey() {
      return publicKey;
    }

    public void setPublicKey(String publicKey) {
      this.publicKey = publicKey;
    }
}
PS:本文编写于公司产物被黑客入侵后的处理方案之一,详细加密解密逻辑不在本文中报告
下一篇:接口安全防线注解加解密(二):加密请求异常后请求/error处理
末了

从时代发展的角度看,网络安全的知识是学不完的,而且以后要学的会更多,同砚们要摆正心态,既然选择入门网络安全,就不能仅仅只是入门水平而已,能力越强时机才越多。
因为入门学习阶段知识点比较杂,所以我讲得比较笼统,各人如果有不懂的地方可以找我咨询,我保证知无不言言无不尽,必要相干资料也可以找我要,我的网盘里一大堆资料都在吃灰呢。
干货主要有:
①1000+CTF历届题库(主流和经典的应该都有了)
②CTF技术文档(最全中文版)
③项目源码(四五十个有趣且经典的练手项目及源码)
④ CTF大赛、web安全、渗出测试方面的视频(适合小白学习)
⑤ 网络安全学习路线图(告别不入流的学习)
⑥ CTF/渗出测试工具镜像文件大全
⑦ 2023密码学/隐身术/PWN技术手册大全
如果你对网络安全入门感爱好,那么你必要的话可以点击这里
页: [1]
查看完整版本: 接口安全防线加解密:springboot 全局-指定接口解密(同时支持参数在body和