Global API Encryption with @ControllerAdvice: 30-Min Spring Boot Implementation & Benchmarks
This tutorial demonstrates how to implement global API encryption in Spring Boot using @ControllerAdvice and ResponseBodyAdvice with AES, covering step-by-step code, three critical pitfalls to avoid, and performance benchmarks showing AES adds only ~6ms overhead per request.
Background: Why Interface Encryption Matters
The Kuaishou security incident highlighted how AI-driven automated attacks can bypass authentication and exploit vulnerabilities at scale. For backend developers, APIs are the core data-exchange channel; transmitting sensitive data (phone numbers, passwords, order details) in plaintext is equivalent to handing attackers a ready-to-use key. Encryption ensures data confidentiality and integrity during transit.
Solution Overview: @ControllerAdvice + ResponseBodyAdvice
The combination of @ControllerAdvice and ResponseBodyAdvice provides a lightweight, non-invasive way to encrypt all responses globally without modifying any controller code. @ControllerAdvice defines the scope (e.g., basePackages = "com.example.controller"), while ResponseBodyAdvice intercepts the response body before serialization to apply encryption.
Step-by-Step Implementation
1. Core Annotations
@Component
@ControllerAdvice(basePackages = "com.example.controller")
@Slf4j
public class EncodeResponseBodyAdvice implements ResponseBodyAdvice<Object> { ... } @Component: Registers the class as a Spring bean so the advice is activated. @ControllerAdvice: Restricts the advice to controllers under the specified package, avoiding unnecessary encryption of public endpoints. @Slf4j: Provides a logger for debugging encryption/decryption issues.
2. The supports Method: Encryption Switch
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true; // "one-size-fits-all" – encrypt every response in scope
}Returning true encrypts all responses in the advised package. The article notes this is simple but may add overhead to public endpoints. Advanced usage: check for a custom @NeedEncrypt annotation on the method to encrypt selectively.
3. Core Logic in beforeBodyWrite
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
// 1. Log original data for troubleshooting
log.info("【原始数据】准备加密: {}", body);
// 2. Normalize to unified ResultEntity
ResultEntity result = JSON.parseObject(JSON.toJSONString(body), ResultEntity.class);
// 3. Skip encryption for null data
if (ObjectUtil.isNull(result) || ObjectUtil.isNull(result.getData())) {
return result.ok();
}
// 4. Encrypt data field, destroy plaintext
String encryptedData = AesUtils.encrypt(result.getData().toString(), getSecretKey());
result.setData(null);
result.setEncrypt_data(encryptedData);
return result;
}The method logs the raw response, converts any return type into a standard ResultEntity (containing code, msg, data, encrypt_data), skips null payloads, encrypts the data field with AES, nulls out the plaintext, and returns the wrapper with only encrypt_data populated.
4. Example Transformation
Plaintext response: {"code":200,"msg":"成功","data":"用户密码123"} Encrypted response: {"code":200,"msg":"成功","encrypt_data":"x9#kL2!pQ7*zR3"} Interceptors see only ciphertext.
Three Critical Pitfalls
Pitfall 1: Hardcoding the Secret Key
// ❌ Wrong
String key = "test@2025";
// ✅ Correct
@Value("${aes.secret-key}")
private String secretKey;Keys must live in configuration files (e.g., application.yml) for rotation and to avoid leaking secrets in version control.
Pitfall 2: Non-Standard ResultEntity
The wrapper class must have exactly these fields with getters/setters:
public class ResultEntity {
private Integer code;
private String msg;
private Object data;
private String encrypt_data; // encrypted payload
}Pitfall 3: Forgetting Frontend Coordination
Encryption is bidirectional. The frontend must know:
Algorithm (AES)
Secret key (from config)
Decryption logic
Otherwise the client cannot decode responses, leading to wasted debugging time.
Performance Benchmarks (Real-World Tests)
No encryption: ~3 ms baseline
AES encryption (single call): ~6 ms – negligible impact
RSA encryption (single call): ~45 ms – acceptable for low-frequency calls
Full-interface blanket encryption: +30% latency – needs optimization (use selective encryption via supports or custom annotation)
Conclusion: AES overhead is virtually unnoticeable; RSA is heavier but usable for specific high-security endpoints. Blanket encryption should be avoided for performance-sensitive public APIs.
Summary & Homework
@ControllerAdvicescopes the encryption; ResponseBodyAdvice performs it. Together they deliver global, zero-intrusion encryption. The article ends with a challenge: implement selective encryption using a custom annotation (e.g., @NeedEncrypt) combined with @ControllerAdvice so that only annotated endpoints are encrypted, leaving public endpoints untouched.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Engineer Tang's Implementation Study Notes
Old Tang | Pseudo-full-stack tech player: writes code without going bald, manages projects without going crazy, delivers without staying up late. No formal tutorials here, only a "slacking-off" practical manual that cures system pain points. Focuses on Shell, SQL, Java, and delivery, turning complexity into simplicity and making efficiency fun.
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.
