Common API Security Practices: Token, Timestamp, Signature, and Replay Prevention in Java

This article explains practical API security techniques—including access tokens, timestamps, cryptographic signatures, and anti‑replay measures—illustrated with Java Spring Boot code, Redis storage, custom annotations, and ThreadLocal usage to protect data exchange between services.

Top Architect
Top Architect
Top Architect
Common API Security Practices: Token, Timestamp, Signature, and Replay Prevention in Java

In real‑world systems, interacting with third‑party services requires protecting data during transmission. Beyond HTTPS, a set of conventions such as access tokens, timestamps, nonces, and signatures can mitigate theft, tampering, and replay attacks.

Token Overview : An access token (usually a UUID) identifies the caller. The client first requests a token by sending appId, timestamp, and a sign (MD5 of timestamp+appId+key). The server validates the signature, generates a token, stores it in Redis, and returns it with an expiration time.

Timestamp : The current millisecond timestamp is sent with each request. The server checks that the difference between the server time and the provided timestamp is within an allowed window (e.g., 5 minutes) to limit the window for replay attacks.

Signature (sign) : All non‑empty request parameters (excluding sign) are sorted alphabetically, concatenated as key=value&, then the token, timestamp, nonce, and a secret key are appended. The resulting string is MD5‑hashed to produce sign. The server recomputes the hash and rejects the request if the signatures differ.

Preventing Duplicate Submissions : A custom annotation @NotRepeatSubmit marks methods that must not be called repeatedly within a short period. The interceptor stores the sign in Redis with the same expiration as the request; subsequent identical requests are blocked.

Usage Flow :

Client obtains appId and key from the provider.

Client calls /api/token/api_token with appId, timestamp, and sign to receive an API token.

For user‑specific operations, the client logs in with username/password to receive a user token.

All subsequent API calls include token, timestamp, nonce, and sign.

Key Code Snippets :

Dependency configuration:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Redis configuration:

@Configuration
public class RedisConfiguration {
    @Bean
    public JedisConnectionFactory jedisConnectionFactory(){
        return new JedisConnectionFactory();
    }
    @Bean
    public RedisTemplate<String, String> redisTemplate(){
        RedisTemplate<String, String> redisTemplate = new StringRedisTemplate();
        redisTemplate.setConnectionFactory(jedisConnectionFactory());
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

Token controller (simplified):

@RestController
@RequestMapping("/api/token")
public class TokenController {
    @Autowired
    private RedisTemplate redisTemplate;

    @PostMapping("/api_token")
    public ApiResponse<AccessToken> apiToken(String appId,
        @RequestHeader("timestamp") String timestamp,
        @RequestHeader("sign") String sign){
        // validate parameters, timestamp window, signature, then generate token
        AccessToken token = saveToken(0, new AppInfo("1","12345678954556"), null);
        return ApiResponse.success(token);
    }

    @NotRepeatSubmit(5000)
    @PostMapping("user_token")
    public ApiResponse<UserInfo> userToken(String username, String password){
        // validate user, generate user token
        AccessToken token = saveToken(1, new AppInfo("1","12345678954556"), new UserInfo(username,"pwdHash","salt"));
        UserInfo userInfo = new UserInfo(username,"pwdHash","salt");
        userInfo.setAccessToken(token);
        return ApiResponse.success(userInfo);
    }
    // token generation and storage logic omitted for brevity
}

Interceptor that checks token existence, timestamp, and signature, and enforces @NotRepeatSubmit:

@Component
public class TokenInterceptor extends HandlerInterceptorAdapter {
    @Autowired
    private RedisTemplate redisTemplate;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("token");
        String timestamp = request.getHeader("timestamp");
        String nonce = request.getHeader("nonce");
        String sign = request.getHeader("sign");
        Assert.isTrue(!StringUtils.isEmpty(token) && !StringUtils.isEmpty(timestamp) && !StringUtils.isEmpty(sign), "参数错误");
        // check timestamp window
        // fetch token info from Redis and verify existence
        // recompute signature and compare
        // handle @NotRepeatSubmit to prevent duplicate submissions
        return super.preHandle(request, response, handler);
    }
}

Utility classes such as MD5Util, the custom annotation @NotRepeatSubmit, data models ( AccessToken, AppInfo, TokenInfo, UserInfo), and helper classes ( ApiUtil, ApiResponse) provide the full implementation for the described security workflow.

Finally, the article mentions using ThreadLocal to store user context after token validation, allowing any layer (controller, service, DAO) to retrieve the current user without passing parameters explicitly.

In summary, combining token storage in Redis, timestamp validation, cryptographic signatures, nonce usage, and anti‑replay/duplicate‑submission checks creates a robust API security layer suitable for production systems.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

javaredisSpring Boottoken authenticationsignatureprevent replay
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

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.