springboot处理数据解密

16 篇文章 0 订阅
15 篇文章 0 订阅

问题描述

原有前端数据传输是通过明文传输的,现处于安全性考虑,需要对敏感信息加密(将加密后的数据存储到一个新的加密字段,后端通过解密这个字段获取数据)

解决方案

通过注解实现

思路

在需要进行解密的方法,然后通过切面前置处理(进行数据解密)

demo 实现

定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Decrypt {
}
定义请求 model
@Data
public class Person {

    private String name;
    private String address;
    private Integer age;

    /**
     * 加密传输数据的字段
     */
    private String encryptData;

}
定义controller
@Slf4j
@RestController
public class DecryptController {

    /**
     * 测试      http://localhost:8080/annotation
     *     全参
     *     {
     *     "encryptData":"eyJhZGRyZXNzIjoi5rW35bqV5Lik5LiH6YeMIiwiYWdlIjoxOCwibmFtZSI6IuWlleaZqCJ9"
     *     }
     *
     *     缺 age 字段
     *     {
     *     "encryptData":"eyJhZGRyZXNzIjoi5rW35bqV5Lik5LiH6YeMIiwibmFtZSI6IuWlleaZqCJ9"
     *     }
     */
    @RequestMapping("/annotation")
    @Decrypt
    public String annotationDecrypt(@RequestBody @Valid Person person){
        log.info("通过注解解密数据");
        return JSON.toJSONString(person);
    }


    @RequestMapping("/filter")
    public String filterDecrypt(@RequestBody @Valid Person person){
        log.info("通过过滤器和拦截器解密数据");
        return JSON.toJSONString(person);
    }

}
定义 Aspect
@Aspect
@Slf4j
@Component
public class DecryptAspect {

    @Pointcut("@annotation(com.yichen.decryptdemo.annotation.Decrypt)")
    public void decryptPointCut() {
    }

    @Before("decryptPointCut()")
    public void decrypt(JoinPoint joinPoint){
        //  base64 测试
        // 这里我单纯的模拟,用 Base64 加解密,一般都是通过对称加密或非对称加密实现
        // 这里简化了,我直接转换了,一般需要做判断,我的实现是定义一个接口,接口有定义一个获取加密字段的方法。
        Object[] args = joinPoint.getArgs();
        log.info("请求入参 {}",JSON.toJSONString(args));
        try {
            Person person = (Person)args[0];
            String decryptString = new String(Base64.getDecoder().decode(person.getEncryptData().getBytes(StandardCharsets.UTF_8)),"UTF-8");
            JSONObject jsonObject = JSON.parseObject(decryptString);
            // 这里简化了,可以用反射。
            person.setName(String.valueOf(jsonObject.get("name")));
            person.setAddress(String.valueOf(jsonObject.get("address")));
            person.setAge(Integer.valueOf(String.valueOf(jsonObject.get("age"))));
        }
        catch (Exception e){
            log.error("解密出错 {}",e.getMessage(),e);
        }
    }
}
请求测试

这里我下掉了annotationDecrypt() 方法中的注解 @Valid
在这里插入图片描述

缺陷

该实现存在一个问题,即原来入参如果使用了如@NotEmpty()等字段校验注解时,会直接被校验住并抛出错误,被全局异常处理器拦截然后返给前端。这里即使给我们自定的切面最高优先级@Order(Ordered.HIGHEST_PRECEDENCE)也是不起作用的,因为@Valid的处理时机先于我们自定义的Aspect

无age参数请求

{
“encryptData”:“eyJhZGRyZXNzIjoi5rW35bqV5Lik5LiH6YeMIiwibmFtZSI6IuWlleaZqCJ9”
}

在这里插入图片描述

全参请求

{
“encryptData”:“eyJhZGRyZXNzIjoi5rW35bqV5Lik5LiH6YeMIiwiYWdlIjoxOCwibmFtZSI6IuWlleaZqCJ9”
}

在这里插入图片描述

这种情况下,只能无奈的把校验下掉,手动进行参数判断,略痛苦。

通过过滤器和拦截器实现

前置条件

DecryptAspect@Component注了

思路

通过自定义filter包装setvletRequest,通过interceptor进行数据解密
不单独使用interceptor的原因是因为,interceptor只能进行读取请求数据(request.getInputStream()),但是无法反写。

demo 实现

filter定义
@Slf4j
public class DecryptFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 为什么使用自定义类包装 request,因为 request 一旦读取后,无法在此读取,也不能添加入参,这里包装后重写 getInputStream() 方法即可克服以上功能
        DecryptServletRequestWrapper requestWrapper = null;
        try {
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            requestWrapper = new DecryptServletRequestWrapper(httpServletRequest);
        }
        catch (Exception e){
            log.error("DecryptServletRequestWrapper error {}",e.getMessage(),e);
        }
        chain.doFilter((Objects.isNull(requestWrapper) ? request : requestWrapper),response);
    }
}
interceptor定义
@Slf4j
public class DecryptInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception  {
        if (request instanceof DecryptServletRequestWrapper){
            DecryptServletRequestWrapper requestWrapper = (DecryptServletRequestWrapper) request;
            Map<String, Object> requestBody = requestWrapper.getRequestBody();
            // 这里可以处理解密逻辑,我这里直接赋值了,就不解密了
            requestBody.put("name","yichen");
            requestBody.put("address","海底两万里");
            // 对比结果
//            requestBody.put("age",18);
            DecryptServletRequestWrapper.printMap(requestBody);
        }
        return true;
    }
}
request wrapper
@Slf4j
public class DecryptServletRequestWrapper extends HttpServletRequestWrapper {

    private Map<String,Object> requestBody;


    /**
     * Constructs a request object wrapping the given request.
     *
     * @param request The request to wrap
     * @throws IllegalArgumentException if the request is null
     */
    public DecryptServletRequestWrapper(HttpServletRequest request) {
        super(request);
        // 这里手动赋值,可以是 解密数据
        requestBody = new HashMap<>(16);
        JSONObject params = getJsonParam(request);
        requestBody.putAll(params);
        printMap(requestBody);
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        ByteArrayInputStream inputStream = new ByteArrayInputStream(JSON.toJSONString(requestBody).getBytes(StandardCharsets.UTF_8));
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

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

            @Override
            public void setReadListener(ReadListener listener) {

            }

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

    public Map<String, Object> getRequestBody() {
        return requestBody;
    }

    public void setRequestBody(Map<String, Object> requestBody) {
        this.requestBody = requestBody;
    }

    /**
     * 获取 入参
     * @param request 请求
     * @return 入参结果集
     */
    public static JSONObject getJsonParam(HttpServletRequest request) {
        JSONObject jsonParam = null;
        try {
            // 获取输入流
            BufferedReader streamReader = new BufferedReader(new InputStreamReader(request.getInputStream(), "UTF-8"));

            // 写入数据到Stringbuilder
            StringBuilder sb = new StringBuilder();
            String line = null;
            while ((line = streamReader.readLine()) != null) {
                sb.append(line);
            }
            jsonParam = JSONObject.parseObject(sb.toString());
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        log.info("getJsonParam => {}", JSON.toJSONString(jsonParam));
        return jsonParam;
    }

    public static void printMap(Map<String,Object>  params){
        params.forEach((key, value) -> log.info("key =>  {}, value => {}", key, value));
    }

}
解密配置
@Configuration
public class DecryptConfiguration implements WebMvcConfigurer {


    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        DecryptInterceptor interceptor = new DecryptInterceptor();
        registry.addInterceptor(interceptor);
    }

    @Bean
    public FilterRegistrationBean<?> decryptDataRegistrationBean(){
        DecryptFilter filter = new DecryptFilter();
        FilterRegistrationBean<DecryptFilter> bean = new FilterRegistrationBean<>();
        bean.setName("decrypt-data");
        bean.setFilter(filter);
        bean.addUrlPatterns("/*");
        bean.setOrder(Ordered.LOWEST_PRECEDENCE);
        return bean;
    }
}
测试

这里我通过在DecryptInterceptor手动模拟解密来比对

传全参
@Slf4j
public class DecryptInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception  {
        if (request instanceof DecryptServletRequestWrapper){
            DecryptServletRequestWrapper requestWrapper = (DecryptServletRequestWrapper) request;
            Map<String, Object> requestBody = requestWrapper.getRequestBody();
            // 这里可以处理解密逻辑,我这里直接赋值了,就不解密了
            requestBody.put("name","yichen");
            requestBody.put("address","海底两万里");
            // 对比结果
            requestBody.put("age",18);
            DecryptServletRequestWrapper.printMap(requestBody);
        }
        return true;
    }
}

在这里插入图片描述

少传age字段
@Slf4j
public class DecryptInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception  {
        if (request instanceof DecryptServletRequestWrapper){
            DecryptServletRequestWrapper requestWrapper = (DecryptServletRequestWrapper) request;
            Map<String, Object> requestBody = requestWrapper.getRequestBody();
            // 这里可以处理解密逻辑,我这里直接赋值了,就不解密了
            requestBody.put("name","yichen");
            requestBody.put("address","海底两万里");
            // 对比结果
//            requestBody.put("age",18);
            DecryptServletRequestWrapper.printMap(requestBody);
        }
        return true;
    }
}

在这里插入图片描述

结论

这种方式实现可以完全适配原有的代码逻辑,字段校验也不存在冲突。

参考

springboot HandlerIntercepter拦截器实现修改request body数据

心得

代码demo
1、加密方式最好统一,别不同端不同的加密方式。。。。
2、请求入参最好规范化,别大多数用了泛型适配如 RequestData,而某几个接口突然入参一个 @RequestParam("mobile")String mobile,这很难受。
3、尽量代码复用,像这种加密,有一种偷懒的方式是在每个要加密的接口前置处理(一段代码到处拷贝。改起来也有点痛苦)

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值