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

接口安全防线加解密: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:本文编写于公司产品被黑客入侵后的处理方案之一,具体加密解密逻辑不在本文中讲述

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
SpringBoot中可以使用加密算法,如AES、DES等,对接口数据进行加解密处理。下面给出一个简单的例子。 1.添加依赖 在pom.xml文件中添加以下依赖: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>2.5.5</version> </dependency> ``` 2.配置文件 在application.properties文件中添加以下配置: ``` # 加密密钥 encryption.key=1234567890abcdef ``` 3.自定义加解密工具类 定义一个加解密工具类,如下: ``` @Component public class EncryptionUtils { @Value("${encryption.key}") private String key; /** * 加密 * * @param data 待加密数据 * @return 加密后数据 */ public String encrypt(String data) { try { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), "AES"); IvParameterSpec ivParameterSpec = new IvParameterSpec(key.getBytes()); cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec); byte[] encryptedData = cipher.doFinal(data.getBytes("UTF-8")); return Base64.getEncoder().encodeToString(encryptedData); } catch (Exception e) { throw new RuntimeException("加密失败", e); } } /** * 解密 * * @param data 待解密数据 * @return 解密后数据 */ public String decrypt(String data) { try { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), "AES"); IvParameterSpec ivParameterSpec = new IvParameterSpec(key.getBytes()); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec); byte[] decryptedData = cipher.doFinal(Base64.getDecoder().decode(data)); return new String(decryptedData, "UTF-8"); } catch (Exception e) { throw new RuntimeException("解密失败", e); } } } ``` 4.使用加解密工具类 在Controller中使用加解密工具类,如下: ``` @RestController @RequestMapping("/api") public class ApiController { @Autowired private EncryptionUtils encryptionUtils; @PostMapping("/encrypt") public String encrypt(@RequestBody String data) { return encryptionUtils.encrypt(data); } @PostMapping("/decrypt") public String decrypt(@RequestBody String data) { return encryptionUtils.decrypt(data); } } ``` 5.测试接口 使用Postman测试接口,如下: - 请求:POST http://localhost:8080/api/encrypt Body:{"name": "张三", "age": 18} - 响应:f1gYflQ2Kj7TzJJbTnA7vQ== - 请求:POST http://localhost:8080/api/decrypt Body:f1gYflQ2Kj7TzJJbTnA7vQ== - 响应:{"name": "张三", "age": 18} 以上就是SpringBoot实现接口数据的加解密的简单示例,可以根据实际需求进行修改和完善。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值