# spring-boot-encrypt **Repository Path**: cqcnihao/spring-boot-encrypt ## Basic Information - **Project Name**: spring-boot-encrypt - **Description**: spring-boot请求参数,响应结果加解密 - **Primary Language**: Java - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 4 - **Created**: 2024-01-21 - **Last Updated**: 2024-01-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # SpringBoot参数加解密 ## 概述 * 有时候,为了接口安全,防止接口数据被拦截抓取,我们需要对请求,响应参数进行加密。接口`org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice`能对请求参数进行前置处理,而`org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice`对响应结果数据进行后置处理。因此这两个接口能很好的植入我们的加解密代码段,统一进行全局操作,避免重复编码。 ## 实现流程 ### 1.添加拦截注解 * 主要为了标识加解密规则 ```java @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface SafetyProcess { /** * 请求参数是否解密 */ boolean decode() default true; /** * 响应结果是否加密 */ boolean encode() default true; } ``` ### 2.添加配置application.properties ```properties # 注意:本项目使用ase算法,密钥长度固定为16字节 safety.secret=1234567890abcdef ``` ### 3.实现前置解密处理逻辑 ```java @ControllerAdvice public class MyRequestBodyAdvice implements RequestBodyAdvice { @Value("${safety.secret}") private String secret; @Override public boolean supports(MethodParameter methodParameter, Type type, Class> 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> 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> aClass) { return o; } @Override public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class> 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.实现后置加密处理逻辑 ```java @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.测试代码 * 请求对象数据体 ```java @Data @Accessors(chain = true) public class DemoReqDTO implements Serializable { private static final long serialVersionUID = 1019466745376831818L; private Integer a; private String b; } ``` * 响应对象数据体 ```java @Data @Accessors(chain = true) public class DemoRespDTO implements Serializable { private static final long serialVersionUID = 1019466745376831818L; private Integer c; private String d; } ``` * 统一封装数据体 ```java @Data @NoArgsConstructor @AllArgsConstructor public class Response 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 Response success(R data) { return new Response<>(SUCCESS, "success", data); } public static Response error(String msg) { return new Response<>(ERROR, msg, null); } @JsonIgnore public boolean isOk() { return null != getCode() && SUCCESS == getCode(); } } ``` * 测试代码 ```java @RestController public class DemoController { /** * 请求参数解密,响应结果加密 * @param reqDTO * @return */ @SafetyProcess @PostMapping(value = "/test") public Response 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 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 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 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 curl -X POST \ http://localhost:8080/test \ -H 'Content-Type: application/json' \ -d 'MGi5m7Q0ghkXf+3K/I80qQ==' ``` * 响应结果 ```json { "code": 200, "message": "success", "data": "GHjhCNV5FnFGoO7ITlXjF2lM8lNDX6IX1/wMPNFWF6qIEfenO6LM0G4KP9hbuevFoFjFqKzDDJ8Z0af+TVG1Ww==" } ```