SpringBoot参数加解密

SpringBoot参数加解密

概述

  • 有时候,为了接口安全,防止接口数据被拦截抓取,我们需要对请求,响应参数进行加密。接口org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice能对请求参数进行前置处理,而org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice对响应结果数据进行后置处理。因此这两个接口能很好的植入我们的加解密代码段,统一进行全局操作,避免重复编码。

实现流程

1.添加拦截注解

  • 主要为了标识加解密规则
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface SafetyProcess {
    /**
     * 请求参数是否解密
     */
    boolean decode() default true;
    /**
     * 响应结果是否加密
     */
    boolean encode() default true;
}

2.添加配置application.properties

# 注意:本项目使用ase算法,密钥长度固定为16字节
safety.secret=1234567890abcdef

3.实现前置解密处理逻辑

@ControllerAdvice
public class MyRequestBodyAdvice implements RequestBodyAdvice {

    @Value("${safety.secret}")
    private String secret;

    @Override
    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        SafetyProcess process = methodParameter.getMethodAnnotation(SafetyProcess.class);
        //如果带有安全注解且标记为解密,测进行解密操作
        return null != process && process.decode();
    }

    /**
     * 约定,请求方解密算法:
     * 1.获取源字符串
     * 2.进行base64解码
     * 3.进行ase解密
     * 4.封装可重复读的请求流对象
     * @param httpInputMessage
     * @param methodParameter
     * @param type
     * @param aClass
     * @return
     * @throws IOException
     */
    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
        HttpHeaders headers = httpInputMessage.getHeaders();
        String bodyStr = StreamUtils.copyToString(httpInputMessage.getBody(), Charset.forName("utf-8"));
        //base64解码
        byte[] bytes = Base64.getDecoder().decode(bodyStr);
        //aes解密
        byte[] body = SecureUtil.aes(secret.getBytes()).decrypt(bytes);
        return new MyHttpInputMessage(headers, body);
    }

    @Override
    public Object afterBodyRead(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return o;
    }

    @Override
    public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return o;
    }

    /**
     * 自定义消息体,因为org.springframework.http.HttpInputMessage#getBody()只能调一次,所以要重新封装一个可重复读的消息体
     */
    @AllArgsConstructor
    public static class MyHttpInputMessage implements HttpInputMessage {

        private HttpHeaders headers;

        private byte[] body;

        @Override
        public InputStream getBody() throws IOException {
            return new ByteArrayInputStream(body);
        }

        @Override
        public HttpHeaders getHeaders() {
            return headers;
        }
    }

}

4.实现后置加密处理逻辑

@ControllerAdvice
@Slf4j
public class MyResponseBodyAdvice implements ResponseBodyAdvice {

    @Value("${safety.secret}")
    private String secret;

    @Override
    public boolean supports(MethodParameter methodParameter, Class aClass) {
        SafetyProcess process = methodParameter.getMethodAnnotation(SafetyProcess.class);
        //如果带有安全注解且标记为加密,测进行加密操作
        return null != process && process.encode();
    }

    /**
     * 约定,响应方加密算法:
     * 1.对源数据转换成json字符串
     * 2.然后调用aes加密
     * 3.将加密后的数据进行base64编码
     * @param o
     * @param methodParameter
     * @param mediaType
     * @param aClass
     * @param serverHttpRequest
     * @param serverHttpResponse
     * @return
     */
    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        Object data = null;
        //如果是rest接口统一封装返回对象
        if (o instanceof Response) {
            Response res = (Response) o;
            //如果返回成功
            if (res.isOk()) {
                data = res.getData();
            } else {
                return o;
            }
        } else {
            data = o;
        }
        if (null != data) {
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                String bodyStr = objectMapper.writeValueAsString(o);
                //aes加密
                byte[] bytes = SecureUtil.aes(secret.getBytes()).encrypt(bodyStr);
                //base64编码返回
                return Response.success(Base64.getEncoder().encodeToString(bytes));
            } catch (JsonProcessingException e) {
                log.error("数据加密异常", e);
            }
        }
        return o;
    }
}

5.测试代码

  • 请求对象数据体
@Data
@Accessors(chain = true)
public class DemoReqDTO implements Serializable {
    private static final long serialVersionUID = 1019466745376831818L;
    private Integer a;
    private String b;
}
  • 响应对象数据体
@Data
@Accessors(chain = true)
public class DemoRespDTO implements Serializable {
    private static final long serialVersionUID = 1019466745376831818L;
    private Integer c;
    private String d;
}
  • 统一封装数据体
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Response<T> implements Serializable {

    private static final long serialVersionUID = 4921114729569667431L;

    //状态码,200为成功,其它为失败
    private Integer code;

    //消息提示
    private String message;

    //数据对象
    private T data;

    //成功状态码
    public static final int SUCCESS = 200;

    //失败状态码
    public static final int ERROR = 1000;

    public static <R> Response<R> success(R data) {
        return new Response<>(SUCCESS, "success", data);
    }

    public static <R> Response<R> error(String msg) {
        return new Response<>(ERROR, msg, null);
    }

    @JsonIgnore
    public boolean isOk() {
        return null != getCode() && SUCCESS == getCode();
    }

}
  • 测试代码
@RestController
public class DemoController {

    /**
     * 请求参数解密,响应结果加密
     * @param reqDTO
     * @return
     */
    @SafetyProcess
    @PostMapping(value = "/test")
    public Response<DemoRespDTO> test(@RequestBody DemoReqDTO reqDTO) {
        DemoRespDTO respDTO = new DemoRespDTO().setC(3).setD("4");
        return Response.success(respDTO);
    }

    /**
     * 请求参数解密
     * @param reqDTO
     * @return
     */
    @SafetyProcess(encode = false)
    @PostMapping(value = "/test2")
    public Response<DemoRespDTO> test2(@RequestBody DemoReqDTO reqDTO) {
        DemoRespDTO respDTO = new DemoRespDTO().setC(3).setD("4");
        return Response.success(respDTO);
    }

    /**
     * 响应结果加密
     * @param reqDTO
     * @return
     */
    @SafetyProcess(decode = false)
    @PostMapping(value = "/test3")
    public Response<DemoRespDTO> test3(@RequestBody DemoReqDTO reqDTO) {
        DemoRespDTO respDTO = new DemoRespDTO().setC(3).setD("4");
        return Response.success(respDTO);
    }

    /**
     * 不进行加解密
     * @param reqDTO
     * @return
     */
    @SafetyProcess(decode = false, encode = false)
    @PostMapping(value = "/test4")
    public Response<DemoRespDTO> test4(@RequestBody DemoReqDTO reqDTO) {
        DemoRespDTO respDTO = new DemoRespDTO().setC(3).setD("4");
        return Response.success(respDTO);
    }

    public static void main(String[] args) throws JsonProcessingException {
        DemoReqDTO reqDTO = new DemoReqDTO().setA(1).setB("2");
        System.out.println("源字符串:" + new ObjectMapper().writeValueAsString(reqDTO));
        //加密
        String str = encode(reqDTO);
        System.out.println("加密后:" + str);

        //解密
        String res = decode(str);
        System.out.println("解密后:" + res);
    }

    private static String encode(Object obj) {
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            String bodyStr = objectMapper.writeValueAsString(obj);
            //aes加密
            byte[] bytes = SecureUtil.aes("1234567890abcdef".getBytes()).encrypt(bodyStr);
            //base64编码返回
            return Base64.getEncoder().encodeToString(bytes);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return null;
    }

    private static String decode(String str) {
        byte[] bytes = Base64.getDecoder().decode(str);
        //aes解密
        byte[] body = SecureUtil.aes("1234567890abcdef".getBytes()).decrypt(bytes);
        return new String(body, StandardCharsets.UTF_8);
    }
}

6.测试效果

  • 发送请求
curl -X POST \
  http://localhost:8080/test \
  -H 'Content-Type: application/json' \
  -d 'MGi5m7Q0ghkXf+3K/I80qQ=='
  • 响应结果
{
    "code": 200,
    "message": "success",
    "data": "GHjhCNV5FnFGoO7ITlXjF2lM8lNDX6IX1/wMPNFWF6qIEfenO6LM0G4KP9hbuevFoFjFqKzDDJ8Z0af+TVG1Ww=="
}
  • 码云 https://gitee.com/hweiyu/spring-boot-encrypt.git
  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
Spring Boot中,可以使用RequestBodyAdvice和ResponseBodyAdvice来实现接口的加密和解密逻辑。这两个接口可以在方法中使用@RequestBody和@ResponseBody注解时生效。具体的实现步骤如下: 1. 创建一个自动配置类,比如AppConfig,用于配置加密和解密的相关参数。在该类中,可以使用@Bean注解创建一个AES对象,用于进行加密和解密操作。可以参考\[3\]中的示例代码。 2. 在自动配置类中,可以使用@Configuration注解标记该类为配置类,并使用@Resource注解注入CryptConfig对象,该对象用于获取加密和解密的相关配置参数。 3. 在加密和解密的逻辑中,可以使用AES对象进行加密和解密操作。可以根据具体的需求选择合适的加密模式、填充方式、密钥和向量等参数。 4. 在接口方法中,使用@RequestBody注解标记需要加密的请求数据,在方法中进行解密操作。然后处理相应的业务逻辑,并使用@ResponseBody注解标记需要加密的返回数据,在返回之前进行加密操作。 通过以上步骤,可以将加密和解密的逻辑提取出来,使接口方法只关注业务逻辑的处理。具体的实现可以参考\[2\]中的项目结构示例代码。 总结起来,使用Spring Boot的RequestBodyAdvice和ResponseBodyAdvice可以很方便地实现接口的加密和解密逻辑,提高代码的可维护性和安全性。 #### 引用[.reference_title] - *1* [springboot中如何优雅的对接口数据进行加密解密](https://blog.csdn.net/xinghui_liu/article/details/121208804)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [SpringBoot 接口加密解密,新姿势!](https://blog.csdn.net/qq_42914528/article/details/128168527)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值