Implementing Request and Response Encryption in Spring MVC with ControllerAdvice
This article presents a step‑by‑step guide on securing Spring MVC interfaces by encrypting and decrypting both GET and POST requests using symmetric AES, custom ControllerAdvice components, and Jackson configuration to ensure consistent JSON serialization across mobile, H5, and backend services.
The author introduces an interface security requirement that mandates minimal impact on existing business logic, symmetric AES encryption for Android, iOS, and H5 clients, separate keys for H5, and support for both GET and POST endpoints.
Data models are defined using Lombok: a @Data public class User { Integer id; String name; UserType userType = UserType.COMMON; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime registerTime; } and an enum @Getter @JsonFormat(shape = JsonFormat.Shape.OBJECT) public enum UserType { VIP("VIP用户"), COMMON("普通用户"); private String code; private String type; UserType(String type){ this.code = name(); this.type = type; } } .
A simple controller demonstrates a user‑list endpoint: @RestController @RequestMapping({"/user","/secret/user"}) public class UserController { @RequestMapping("/list") ResponseEntity > listUser(){ /* create sample user and return */ } } .
For request decryption, @ControllerAdvice @Order(Ordered.HIGHEST_PRECEDENCE) @Slf4j public class SecretRequestAdvice extends RequestBodyAdviceAdapter { @Override public boolean supports(...) { return true; } @Override public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, ...) throws IOException { String httpBody; if (Boolean.TRUE.equals(SecretFilter.secretThreadLocal.get())) { httpBody = decryptBody(inputMessage); } else { httpBody = StreamUtils.copyToString(inputMessage.getBody(), Charset.defaultCharset()); } return new SecretHttpMessage(new ByteArrayInputStream(httpBody.getBytes()), inputMessage.getHeaders()); } private String decryptBody(HttpInputMessage inputMessage) throws IOException { /* signature verification and AES decryption */ } } .
Response encryption is handled by @ControllerAdvice public class SecretResponseAdvice implements ResponseBodyAdvice { @Autowired private ObjectMapper objectMapper; @Override public boolean supports(MethodParameter methodParameter, Class aClass) { return true; } @Override public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest request, ServerHttpResponse response) { Boolean respSecret = SecretFilter.secretThreadLocal.get(); String secretKey = SecretFilter.clientPrivateKeyThreadLocal.get(); SecretFilter.secretThreadLocal.remove(); SecretFilter.clientPrivateKeyThreadLocal.remove(); if (respSecret != null && respSecret && o instanceof ResponseBasic) { try { String dataStr = objectMapper.writeValueAsString(o); String data = EncryptUtils.aesEncrypt(dataStr, secretKey); long timestamp = System.currentTimeMillis() / 1000; int salt = EncryptUtils.genSalt(); String newSignature = Md5Utils.genSignature(timestamp + "" + salt + "" + data + secretKey); return SecretResponseBasic.success(data, timestamp, salt, newSignature); } catch (Exception e) { /* log and return error */ } } return o; } } .
Testing reveals serialization mismatches for the enum and LocalDateTime fields; the author switches from FastJSON to Jackson and configures custom serializers: ObjectMapper mapper = new Jackson2ObjectMapperBuilder() .serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))) .deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))) .build(); . After replacing JSON.toJSONString(o) with mapper.writeValueAsString(o) , the encrypted response matches the original unencrypted format.
The article concludes with notes on handling GET request encryption, cross‑origin issues, and invites further discussion.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.