Information Security 8 min read

Preventing API Parameter Tampering and Replay Attacks Using Timestamp and Nonce

This article explains how timestamp and nonce mechanisms can be combined to protect API endpoints from parameter tampering and replay attacks, illustrating the approach with a Java Spring interceptor that stores nonces in Redis and validates signatures on each request.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Preventing API Parameter Tampering and Replay Attacks Using Timestamp and Nonce

In modern front‑back separated architectures, APIs exposed to external clients must be secured against parameter tampering and replay attacks. The article first defines API parameter tampering as malicious modification of request parameters captured via packet sniffing, and API replay attacks as the resubmission of previously captured requests.

To mitigate these threats, a common solution is to use a combination of a timestamp and a nonce (a one‑time random string). The timestamp ensures that a request is processed only within a short time window (e.g., 60 seconds), while the nonce guarantees that each request is unique and cannot be replayed.

The timestamp is added to every HTTP request and signed together with other parameters. The server checks that the timestamp is within the allowed window; if it is older, the request is rejected as invalid.

The nonce is stored in a server‑side collection (e.g., Redis). When a request arrives, the server verifies that the nonce has not been seen before; if it exists, the request is considered a replay and is rejected. After successful validation, the nonce is stored with an expiration time to prevent the collection from growing indefinitely.

Below is a Java SignAuthInterceptor implementation for Spring that demonstrates how to enforce these checks using Redis:

public class SignAuthInterceptor implements HandlerInterceptor {

    private RedisTemplate
redisTemplate;
    private String key;

    public SignAuthInterceptor(RedisTemplate
redisTemplate, String key) {
        this.redisTemplate = redisTemplate;
        this.key = key;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // Get timestamp, nonce and signature from headers
        String timestamp = request.getHeader("timestamp");
        String nonceStr = request.getHeader("nonceStr");
        String signature = request.getHeader("signature");

        // Validate timestamp (prevent replay after 60s)
        long NONCE_STR_TIMEOUT_SECONDS = 60L;
        if (StrUtil.isEmpty(timestamp) || DateUtil.between(DateUtil.date(Long.parseLong(timestamp) * 1000), DateUtil.date(), DateUnit.SECOND) > NONCE_STR_TIMEOUT_SECONDS) {
            throw new BusinessException("invalid  timestamp");
        }

        // Check nonce existence in Redis (prevent short‑term replay)
        Boolean haveNonceStr = redisTemplate.hasKey(nonceStr);
        if (StrUtil.isEmpty(nonceStr) || Objects.isNull(haveNonceStr) || haveNonceStr) {
            throw new BusinessException("invalid nonceStr");
        }

        // Verify signature
        if (StrUtil.isEmpty(signature) || !Objects.equals(signature, this.signature(timestamp, nonceStr, request))) {
            throw new BusinessException("invalid signature");
        }

        // Store nonce in Redis with expiration
        redisTemplate.opsForValue().set(nonceStr, nonceStr, NONCE_STR_TIMEOUT_SECONDS, TimeUnit.SECONDS);
        return true;
    }

    private String signature(String timestamp, String nonceStr, HttpServletRequest request) throws UnsupportedEncodingException {
        Map
params = new HashMap<>(16);
        Enumeration
enumeration = request.getParameterNames();
        if (enumeration.hasMoreElements()) {
            String name = enumeration.nextElement();
            String value = request.getParameter(name);
            params.put(name, URLEncoder.encode(value, CommonConstants.UTF_8));
        }
        String qs = String.format("%s×tamp=%s&nonceStr=%s&key=%s", this.sortQueryParamString(params), timestamp, nonceStr, key);
        log.info("qs:{}", qs);
        String sign = SecureUtil.md5(qs).toLowerCase();
        log.info("sign:{}", sign);
        return sign;
    }

    /**
     * Sort query parameters alphabetically.
     */
    private String sortQueryParamString(Map
params) {
        List
listKeys = Lists.newArrayList(params.keySet());
        Collections.sort(listKeys);
        StrBuilder content = StrBuilder.create();
        for (String param : listKeys) {
            content.append(param).append("=").append(params.get(param).toString()).append("&");
        }
        if (content.length() > 0) {
            return content.subString(0, content.length() - 1);
        }
        return content.toString();
    }
}

The article concludes that security is a permanent concern for any internet application—whether a fresh‑produce mini‑program or a mobile app—and that robust API protection is a prerequisite for building reliable systems.

JavaRedistimestampAPI securityReplay Attacknonce
Code Ape Tech Column
Written by

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

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.