How to Secure API Transmission with Spring Boot, Vue, and AES/DES Encryption
This guide explains how to protect API data exchange by implementing HTTPS, JWT, rate limiting, MAC, and symmetric encryption (AES/DES) in a Spring Boot backend, while using Vue and Axios on the frontend, including custom annotations, request/response advice, and crypto‑js utilities for end‑to‑end encryption.
Environment: Spring Boot 2.5.12 + Vue 2 + Axios
Overview
Secure transmission of API data is essential to prevent interception, tampering, or leakage. The following techniques and best practices can be used to strengthen API security:
Use HTTPS instead of HTTP to encrypt communication with SSL/TLS.
Validate HTTPS requests by checking the certificate issuer and validity period.
Verify API keys for legitimacy by checking their identifiers, validity, and permissions.
Use JSON Web Tokens (JWT) for authentication and authorization.
Limit API request rate and concurrency to prevent abuse and denial‑of‑service attacks.
Use Message Authentication Code (MAC) to ensure message integrity and prevent replay attacks.
Encrypt sensitive data such as passwords and personal information using symmetric or asymmetric encryption.
Set appropriate HTTP headers to mitigate XSS and other vulnerabilities (e.g., X‑XSS‑Protection).
Implement access control (RBAC or ABAC) based on user identity and permissions.
Regularly update and patch APIs and related systems to fix known security flaws.
In Spring we implement request decryption by extending RequestBodyAdviceAdapter and response encryption by implementing ResponseBodyAdvice . The following sections detail the implementation.
Encryption/Decryption Interface
<code>public interface SecretProcess {
/**
* Data encryption
*/
String encrypt(String data);
/**
* Data decryption
*/
String decrypt(String data);
/**
* Encryption algorithm format: algorithm[/mode/padding]
*/
String getAlgorithm();
public static class Hex {
private static final char[] HEX = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
public static byte[] decode(CharSequence s) {
int nChars = s.length();
if (nChars % 2 != 0) {
throw new IllegalArgumentException("16进制数据错误");
}
byte[] result = new byte[nChars / 2];
for (int i = 0; i < nChars; i += 2) {
int msb = Character.digit(s.charAt(i), 16);
int lsb = Character.digit(s.charAt(i + 1), 16);
if (msb < 0 || lsb < 0) {
throw new IllegalArgumentException("Detected a Non-hex character at " + (i + 1) + " or " + (i + 2) + " position");
}
result[i / 2] = (byte) ((msb << 4) | lsb);
}
return result;
}
public static String encode(byte[] buf) {
StringBuilder sb = new StringBuilder();
for (byte b : buf) {
sb.append(HEX[(b & 0xF0) >>> 4]).append(HEX[b & 0x0F]);
}
return sb.toString();
}
}
}
</code>Abstract Implementation
<code>public abstract class AbstractSecretProcess implements SecretProcess {
@Resource
private SecretProperties props;
@Override
public String decrypt(String data) {
try {
Cipher cipher = Cipher.getInstance(getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, keySpec());
byte[] decryptBytes = cipher.doFinal(Hex.decode(data));
return new String(decryptBytes);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public String encrypt(String data) {
try {
Cipher cipher = Cipher.getInstance(getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, keySpec());
return Hex.encode(cipher.doFinal(data.getBytes(Charset.forName("UTF-8"))));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Generate key material based on the secret key and algorithm (supports AES, DES).
*/
public Key getKeySpec(String algorithm) {
if (algorithm == null || algorithm.trim().isEmpty()) {
return null;
}
String secretKey = props.getKey();
switch (algorithm.toUpperCase()) {
case "AES":
return new SecretKeySpec(secretKey.getBytes(), "AES");
case "DES":
try {
DESKeySpec desKeySpec = new DESKeySpec(secretKey.getBytes());
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("DES");
return secretKeyFactory.generateSecret(desKeySpec);
} catch (Exception e) {
throw new RuntimeException(e);
}
default:
return null;
}
}
/**
* Subclass must provide the concrete key specification.
*/
public abstract Key keySpec();
}
</code>AES Algorithm Implementation
<code>public class AESAlgorithm extends AbstractSecretProcess {
@Override
public String getAlgorithm() {
return "AES/ECB/PKCS5Padding";
}
@Override
public Key keySpec() {
return this.getKeySpec("AES");
}
}
</code>Configuration
<code>@Configuration
public class SecretConfig {
@Bean
@ConditionalOnMissingBean(SecretProcess.class)
public SecretProcess secretProcess() {
return new AESAlgorithm();
}
@Component
@ConfigurationProperties(prefix = "secret")
public static class SecretProperties {
private Boolean enabled;
private String key;
public Boolean getEnabled() { return enabled; }
public void setEnabled(Boolean enabled) { this.enabled = enabled; }
public String getKey() { return key; }
public void setKey(String key) { this.key = key; }
}
}
</code>YAML configuration:
<code>secret:
key: aaaabbbbccccdddd # secret key
enabled: true # enable encryption/decryption
</code>Custom Annotation
<code>@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Mapping
@Documented
public @interface SIProtection {}
</code>Request Decryption Advice
<code>@ControllerAdvice
@ConditionalOnProperty(name = "secret.enabled", havingValue = "true")
public class DecryptRequestBodyAdvice extends RequestBodyAdviceAdapter {
@Resource
private SecretProcess secretProcess;
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return methodParameter.getMethod().isAnnotationPresent(SIProtection.class) ||
methodParameter.getMethod().getDeclaringClass().isAnnotationPresent(SIProtection.class);
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
String body = secretProcess.decrypt(inToString(inputMessage.getBody()));
return new HttpInputMessage() {
@Override
public HttpHeaders getHeaders() { return inputMessage.getHeaders(); }
@Override
public InputStream getBody() throws IOException { return new ByteArrayInputStream(body.getBytes()); }
};
}
private String inToString(InputStream is) {
byte[] buf = new byte[10 * 1024];
int len;
StringBuilder sb = new StringBuilder();
try {
while ((len = is.read(buf)) != -1) {
sb.append(new String(buf, 0, len));
}
return sb.toString();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
</code>Response Encryption Advice
<code>@ControllerAdvice
@ConditionalOnProperty(name = "secret.enabled", havingValue = "true")
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> {
@Resource
private SecretProcess secretProcess;
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return returnType.getMethod().isAnnotationPresent(SIProtection.class) ||
returnType.getMethod().getDeclaringClass().isAnnotationPresent(SIProtection.class);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
if (body == null) {
return body;
}
try {
String jsonStr = new ObjectMapper().writeValueAsString(body);
return secretProcess.encrypt(jsonStr);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
</code>Controller Example
<code>@PostMapping("/save")
@SIProtection
public R save(@RequestBody Users users) {
return R.success(usersService.save(users));
}
@RestController
@RequestMapping("/users")
@SIProtection
public class UsersController {
// all methods in this controller will be encrypted/decrypted
}
</code>Frontend Encryption Utilities (crypto‑js)
<code>/**
* Encrypt data
*/
function encrypt(data) {
let key = CryptoJS.enc.Utf8.parse(Consts.Secret.key);
if (typeof data === 'object') {
data = JSON.stringify(data);
}
let plainText = CryptoJS.enc.Utf8.parse(data);
let secretText = CryptoJS.AES.encrypt(plainText, key, {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7}).ciphertext.toString();
return secretText;
}
/**
* Decrypt data
*/
function decrypt(data) {
let key = CryptoJS.enc.Utf8.parse(Consts.Secret.key);
let secretText = CryptoJS.enc.Hex.parse(data);
let encryptedBase64Str = CryptoJS.enc.Base64.stringify(secretText);
let result = CryptoJS.AES.decrypt(encryptedBase64Str, key, {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7}).toString(CryptoJS.enc.Utf8);
return JSON.parse(result);
}
</code>Frontend Configuration
<code>let Consts = {
Secret: {
key: 'aaaabbbbccccdddd', // 16‑byte key, must match backend
urls: ['/users/save']
}
};
export default Consts;
</code>Axios Interceptors for Encryption/Decryption
<code>axios.interceptors.request.use(config => {
let uri = config.url;
if (uri.includes('?')) {
uri = uri.substring(0, uri.indexOf('?'));
}
if (window.cfg.enableSecret === '1' && config.data &&
(Consts.Secret.urls.includes('*') || Consts.Secret.urls.includes(uri))) {
config.data = Utils.Secret.encrypt(config.data);
}
return config;
}, error => {
return Promise.reject(error);
});
axios.interceptors.response.use(response => {
let uri = response.config.url;
if (uri.includes('?')) {
uri = uri.substring(0, uri.indexOf('?'));
}
if (window.cfg.enableSecret === '1' && response.data &&
(Consts.Secret.urls.includes('*') || Consts.Secret.urls.includes(uri))) {
let data = Utils.Secret.decrypt(response.data);
if (data) {
response.data = data;
}
}
return response;
}, error => {
return Promise.reject(error);
});
</code>Below are screenshots showing encrypted request payloads and encrypted responses.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
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.