How to Secure Third‑Party APIs with AK/SK, Signatures, Tokens and Anti‑Replay Measures
This article presents a comprehensive guide to designing secure third‑party APIs, covering the generation and management of Access Key/Secret Key pairs, signature creation, timestamp and nonce anti‑replay techniques, token handling, request throttling, IP whitelisting, idempotency, versioning, response standards, and practical code examples in Java and SQL.
Design Overview
Design scheme overview
Permission division
Signature process
Signature rules
API interface design
Security considerations
AK and SK generation scheme
Supplementary API design
When providing interfaces to third‑party systems, security issues such as data tampering, data expiration, and duplicate submissions must be considered.
In designing a third‑party interface call solution, both security and usability need to be addressed. Below is an overview of a design scheme, which includes using API keys ( Access Key/Secret Key) for authentication and setting callback URLs.
1. API Key Generation
AK (Access Key Id) identifies the application; SK (Secret Access Key) is used to encrypt and verify authentication strings and must be kept confidential.
Authentication is performed by encrypting the request using the Access Key Id and Secret Access Key to verify the sender's identity.
2. Interface Authentication
When calling an interface, the client must use the AK and request parameters to generate a signature, which is placed in the request header or parameters for identity verification.
3. Callback URL Setup
The third‑party application provides a callback URL to receive asynchronous notifications and results.
4. API Interface Design
Design details include URL, HTTP method, request parameters, and response format.
Security Considerations
Use HTTPS for data transmission to protect communication security.
Use AK and signatures for identity verification and request validation on the server side to prevent illegal and replay attacks.
Encrypt sensitive data during transmission, e.g., using TLS.
The above constitutes a simple design scheme and API interface example. Actual implementation details may vary based on project requirements, and error handling, exception handling, and logging should also be considered.
Prevent Replay Attacks
Replay attacks occur when an attacker captures a request and resends it. To prevent this, include a unique nonce (random string) and a timestamp in each request, and verify them on the server.
Generate a random, unique nonce (e.g., UUID).
Use a Unix timestamp (seconds) as the timestamp.
Store used nonces in a datastore (e.g., Redis) with an expiration time.
Reject requests with duplicate nonces or timestamps outside the allowed window (e.g., 60 seconds).
public class SignAuthInterceptor implements HandlerInterceptor {
private RedisTemplate<String, String> redisTemplate;
private String key;
public SignAuthInterceptor(RedisTemplate<String, String> redisTemplate, String key) {
this.redisTemplate = redisTemplate;
this.key = key;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// Get timestamp
String timestamp = request.getHeader("timestamp");
// Get nonce
String nonceStr = request.getHeader("nonceStr");
// Get signature
String signature = request.getHeader("signature");
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");
}
Boolean haveNonceStr = redisTemplate.hasKey(nonceStr);
if (StrUtil.isEmpty(nonceStr) || Objects.isNull(haveNonceStr) || haveNonceStr) {
throw new BusinessException("invalid nonceStr");
}
if (StrUtil.isEmpty(signature) || !Objects.equals(signature, this.signature(timestamp, nonceStr, request))) {
throw new BusinessException("invalid signature");
}
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<String, Object> params = new HashMap<>(16);
Enumeration<String> enumeration = request.getParameterNames();
while (enumeration.hasMoreElements()) {
String name = enumeration.nextElement();
String value = request.getParameter(name);
params.put(name, URLEncoder.encode(value, "UTF-8"));
}
String qs = String.format("%s\ttamp=%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 and concatenate as key=value&...
*/
private String sortQueryParamString(Map<String, Object> params) {
List<String> 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();
}
}TLS Encryption Example (Java)
// Create SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(new FileInputStream("keystore.jjs"), "password".toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, "password".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
URL url = new URL("https://api.example.com/endpoint");
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setSSLSocketFactory(sslContext.getSocketFactory());
// Continue with request/response handlingAPI Key Management Table (SQL)
CREATE TABLE api_credentials (
id INT AUTO_INCREMENT PRIMARY KEY,
app_id VARCHAR(255) NOT NULL,
access_key VARCHAR(255) NOT NULL,
secret_key VARCHAR(255) NOT NULL,
valid_from DATETIME NOT NULL,
valid_to DATETIME NOT NULL,
enabled TINYINT(1) NOT NULL DEFAULT 1,
allowed_endpoints VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);Additional API Design Details
All interfaces use POST for higher security.
Client IP whitelist to restrict access.
Per‑IP rate limiting using Redis (key = ip+endpoint, value = count, with expiration).
Request logging (e.g., via AOP).
Sensitive data masking/encryption (e.g., RSA).
Idempotency handling using a globally unique nonce stored in Redis.
Version control in URLs (e.g., /v1/list, /v2/list).
Standard HTTP status codes (200 success, 4xx client error, 5xx server error).
Unified response format with fields: code, message, data.
API documentation via Swagger2 or similar tools.
Signature Generation Steps
Collect all parameters (including appId, timestamp, nonce), exclude the sign parameter itself and any parameters with empty values, then sort them alphabetically by key.
Concatenate the sorted parameters as key1value1key2value2…keyXvalueX.
Append the secret key to the concatenated string.
Compute the MD5 hash of the final string, convert it to uppercase; this is the sign value.
Example:
appId=zs001&k1=v1&k2=v2&kX=vX&method=cancel&nonce=1234567890×tamp=1612691221000
=> appIdzs001k1v1k2v2kXvXmethodcancelnonce1234567890timestamp1612691221000
=> appIdzs001k1v1k2v2kXvXmethodcancelnonce1234567890timestamp1612691221000mysecret
=> MD5(...)=ABCDEF (uppercase)Token Concepts
A token (access token) identifies the caller and reduces the need to transmit username/password repeatedly. Tokens are usually UUIDs stored in Redis with associated user information. Validation flow:
Client logs in with credentials, server returns a token.
Client includes the token in subsequent requests.
Server checks token validity (exists and not expired) before processing.
Two token types:
API Token : for unauthenticated endpoints (e.g., login, registration). Obtained using appId, timestamp, and sign.
User Token : for endpoints requiring user login. Obtained using username and password.
For stateful endpoints, combine token with signature verification: the client includes both the token (as appId) and a signature generated with the secret key, preventing token hijacking and parameter tampering.
Additional notes: ensure consistent character encoding (e.g., UTF‑8) when computing signatures; use HTTPS for all communications; rotate keys periodically; enforce least‑privilege access for each appKey / appSecret pair.
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.
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.
