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.
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.
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.
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.
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.
