基于Springboot拦截器对请求参数统一解密

本文介绍了在Springboot应用中,如何通过全局拦截器处理前端发送过来经过AES加密的JSON数据,避免在控制器层大量冗余解密代码,以及处理HttpServletRequest流的重用问题。
摘要由CSDN通过智能技术生成

使用场景

前端发送过来的数据是经过加密的数据,我这里前端发送过来的数据格式如下所示:

{
"id":"7f9JPPdYpppQZSwB7QBSGQ==",
"name":"7f9JPPdYpppQZSwB7QBSGQhadkSjhaf=="
}

因为每一个API接口的请求数据是不一样,如果直接在控制器进行解密那么就会产生大量的冗余代码,这是我们不希望看到的,最好的办法就是使用Springboot的拦截器在请求到达控制器之前将加密的数据进行解密,再继续发送给控制器,之后控制器就能愉快的处理解密完成的数据了。

需求分析

因为 json 数据是属于请求体,只能通过读取 request 流进行获取加密前的数据,但是流只能读取一次,如果在拦截器读取流之后,后续的控制器就无法再次读取流,导致拿不到数据,所以这里我们需要使用 HttpServletRequestWrapper 将流进行拷贝,实现流的重复读取。
下面直接给出各部分相应的代码:

  1. 过滤器
package com.yiyun.filter;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Slf4j
@WebFilter(filterName = "myFilter", urlPatterns = {"/**"})
@Component
public class GlobalFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        //上传文件不做解密处理
        if ("multipart/form-data".equals(((HttpServletRequest) request).getHeader("Content-Type"))) {
            chain.doFilter(request, response);
        } else {
            MyHttpServletRequestWrapper MyHttpServletRequest = new MyHttpServletRequestWrapper((HttpServletRequest) request);
            chain.doFilter(MyHttpServletRequest, response);
        }
    }
}

注意:此处做了一个判断,如果是上传文件就不需要对参数进行解密,直接把原本的request继续往下传递就可以,不然上传文件的流会被损害,导致无法上传文件。

  1. 拦截器
package com.yiyun.interceptor;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.yiyun.filter.MyHttpServletRequestWrapper;
import com.yiyun.utils.AesUtil;
import com.yiyun.utils.ThreadLocalUtil;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.Map;


/**
 * @author 创梦流浪人
 * @version 1.0
 */
public class GlobalInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1.判断sign timestamp参数是否携带 app_id不用在这里判断,直接在相关的bean上面通过注解实现
        checkMustParameter(request);
        //2.根据URI 判断是否需要鉴权,比如文章设置了 VIP 可见、登录可见、游客可见等,
        // 那么如果是查询文章的 URI 就要做一个鉴权判断

        //3.参数解密
        decryptParameters(request);

        ThreadLocalUtil.set("你好");
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        ThreadLocalUtil.remove();
    }

    /**
     * 检查每个请求必须携带的参数,并且校验是否合法
     */
    private void checkMustParameter(HttpServletRequest request) {
        String sign = request.getParameter("sign");
        String timestamp = request.getParameter("timestamp");
        if (!StringUtils.hasText(sign) || !StringUtils.hasText(timestamp)) {
            throw new RuntimeException("签名获取时间戳参数缺少");
        }
    }

    /**
     * 解密参数
     */
    private void decryptParameters(HttpServletRequest request) throws IOException {
        ServletInputStream inputStream = request.getInputStream();
        DataInputStream dataInputStream = new DataInputStream(inputStream);
        byte[] bytes = dataInputStream.readAllBytes();

        ObjectMapper objectMapper = new ObjectMapper();
        Map<String, String> map = objectMapper.readValue(new String(bytes), Map.class);
        map.forEach((key, value) -> {
            try {
                map.put(key, AesUtil.decrypt(value));
            } catch (Exception e) {
                throw new RuntimeException("解密失败"+e);
            }
        });
        String s = objectMapper.writeValueAsString(map);
        ((MyHttpServletRequestWrapper) request).setBody(s.getBytes());
    }
}

注意:解密参数之后 ((MyHttpServletRequestWrapper) request).setBody(s.getBytes()) 将解密后的参数覆盖之前的流。

  1. HttpServletRequestWrapper
package com.yiyun.filter;

import cn.hutool.core.io.IoUtil;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper {
    private  byte[] body;

    public MyHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        request.setCharacterEncoding("UTF-8");
        body = IoUtil.readBytes(request.getInputStream());
    }

    public void setBody(byte[] body){
        this.body = body;
    }

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


    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream bis = new ByteArrayInputStream(body);

        return new ServletInputStream() {

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

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

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

            @Override
            public void setReadListener(ReadListener readListener) {

            }
        };
    }
}
  1. AES加密解密工具类
package com.yiyun.utils;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class AesUtil {

    private static final String ALGORITHM = "AES";
    private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding";
    private static final byte[] KEY = "1234567891234567".getBytes(StandardCharsets.UTF_8); // 16 bytes key for AES

    public static String encrypt(String data) throws Exception {
        SecretKeySpec keySpec = new SecretKeySpec(KEY, ALGORITHM);
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.ENCRYPT_MODE, keySpec);
        byte[] encrypted = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(encrypted);
    }

    public static String decrypt(String encryptedData) throws Exception {
        SecretKeySpec keySpec = new SecretKeySpec(KEY, ALGORITHM);
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.DECRYPT_MODE, keySpec);
        byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encryptedData));
        return new String(decrypted, StandardCharsets.UTF_8);
    }

    public static void main(String[] args) {
        try {
            String originalText = "121454";
            System.out.println("Original Text: " + originalText);

            String encryptedText = encrypt(originalText);
            System.out.println("Encrypted Text: " + encryptedText);

            String decryptedText = decrypt(encryptedText);
            System.out.println("Decrypted Text: " + decryptedText);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 23
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

创梦流浪人

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

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

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

打赏作者

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

抵扣说明:

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

余额充值