接口安全防线加解密: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[128];
- 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[0];
- }
- 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("=")[0] != null && !"".equals(individualElement.split("=")[0])) {
- uriToListToMap.put(individualElement.split("=")[0],
- individualElement.substring(individualElement.split("=")[0].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("=")[0] != null && !"".equals(individualElement.split("=")[0])) {
- uriToListToMap.put(individualElement.split("=")[0],
- individualElement.substring(individualElement.split("=")[0].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技术手册大全
如果你对网络安全入门感爱好,那么你必要的话可以点击这里 |