Preventing API Parameter Tampering and Replay Attacks with Signature Verification in Java
The article explains how exposed API endpoints can be intercepted and altered, and presents practical security measures—including HTTPS, encrypted parameters, timestamp‑based signatures, and a Spring Boot filter implementation—to detect and block tampering and replay attacks in a Java backend.
When an API is exposed to the public network for third‑party service calls, attackers can capture the request, modify parameters, and resend it, leading to data theft or server attacks.
To mitigate these risks, several security mechanisms are recommended: use HTTPS to encrypt traffic, apply parameter encryption and request‑time limits, and employ signature verification based on a shared secret key.
Preventing Parameter Tampering
Transmit data over https to ensure encryption and integrity.
Implement backend validation such as parameter encryption and request timestamp checks; the article uses this approach as a case study.
Signature Verification Process
The frontend encrypts request parameters with a pre‑agreed secret key, generates a signature (sign1), and places it in the request headers.
The server receives the request, re‑signs the parameters in a filter using the same secret key to obtain sign2.
If sign1 equals sign2, the request is considered legitimate; otherwise, it is rejected as tampered.
Preventing Replay (Re‑posting) Attacks
Include a timestamp header in each request and sign it together with the parameters.
The server checks that the timestamp is within an acceptable window (e.g., 60 seconds); if it exceeds the limit, the signature is considered expired.
Altering the timestamp without the secret key invalidates the signature, preventing replay.
Other optional solutions include using a unique database primary key with optimistic locking or a anti‑replay token (UUID) stored in a hidden form field, and disabling the submit button with JS throttling.
Core Filter Implementation (Java)
@Component
@Slf4j
public class SignAuthFilter implements Filter {
@Autowired
private SignAuthProperties signAuthProperties;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("Initializing SignAuthFilter...");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// Skip URIs that do not require signing
String requestUri = httpRequest.getRequestURI();
for (String ignoreUri : signAuthProperties.getIgnoreUri()) {
if (requestUri.contains(ignoreUri)) {
log.info("URI " + requestUri + " does not require signature verification.");
chain.doFilter(request, response);
return;
}
}
// Retrieve signature and timestamp
String sign = httpRequest.getHeader("Sign");
String timestampStr = httpRequest.getHeader("Timestamp");
if (StringUtils.isEmpty(sign)) {
responseFail("Signature cannot be empty", response);
return;
}
if (StringUtils.isEmpty(timestampStr)) {
responseFail("Timestamp cannot be empty", response);
return;
}
// Replay time limit
long timestamp = Long.parseLong(timestampStr);
if (System.currentTimeMillis() - timestamp >= signAuthProperties.getTimeout() * 1000) {
responseFail("Signature has expired", response);
return;
}
// Verify signature
Map
parameterMap = httpRequest.getParameterMap();
if (SignUtils.verifySign(parameterMap, sign, timestamp)) {
chain.doFilter(httpRequest, response);
} else {
responseFail("Signature verification failed", response);
}
}
private void responseFail(String msg, ServletResponse response) throws IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
PrintWriter out = response.getWriter();
out.println(msg);
out.flush();
out.close();
}
@Override
public void destroy() {
log.info("Destroying SignAuthFilter...");
}
}The filter can be configured via application.yml :
sign:
# Signature timeout in seconds
timeout: 60
# URIs that do not require signing
ignoreUri:
- /swagger-ui.html
- /v2/api-docsAnd the corresponding properties class:
@Data
@ConfigurationProperties(prefix = "sign")
public class SignAuthProperties {
/** Signature timeout */
private Integer timeout;
/** URIs that can be accessed without a signature */
private List
ignoreUri;
}The actual signature verification logic (e.g., hashing algorithm) is omitted because it varies with the chosen encryption method; developers only need to agree on a common approach between front‑end and back‑end.
In summary, by combining HTTPS, encrypted parameters, timestamp‑based signatures, and a server‑side filter, API interfaces can be protected against tampering and replay attacks.
Architect
Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.
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.