Design and Security Practices for Third‑Party API Authentication and Signing
This article presents a comprehensive design scheme for third‑party APIs, covering permission segmentation, AK/SK generation, signature workflow and rules, secure API endpoint design, anti‑replay mechanisms, token handling, TLS encryption, database schema, and implementation examples in Java.
Design Overview
When exposing APIs to third‑party systems, data security must be considered, including tampering, staleness, and duplicate submissions. The solution uses Access Key/Secret Key (AK/SK) for authentication and sets callback URLs.
Permission Segmentation
Each application receives a unique appID and a pair of appKey (public) and appSecret (private). The appKey+appSecret pair can be used to generate tokens with fine‑grained permissions, allowing multiple appKey / appSecret pairs per appID for read‑only or read‑write access.
Signature Process
Clients include appKey , a timestamp, a nonce, and a signature in each request. The server validates the timestamp (e.g., within 60 seconds) and checks the nonce for uniqueness using Redis to prevent replay attacks.
Signature Rules
Assign appId and appSecret to callers.
Add a timeStamp (ms) to limit request validity.
Include a random nonce (≥10 characters) to avoid duplicate submissions.
Combine all parameters (excluding empty values and the signature itself) in alphabetical order, append the secret, compute the MD5 hash, and convert to uppercase to obtain sign .
API Interface Design
Typical CRUD endpoints are defined with clear URL, HTTP method, request parameters, and response format. Example:
GET /api/resources?page=<num>&limit=<num>
POST /api/resources { name, description }
PUT /api/resources/{resourceId} { name?, description? }
DELETE /api/resources/{resourceId}Security Considerations
Use HTTPS for data transmission.
Validate AK and signatures on the server side.
Encrypt sensitive data with TLS.
Anti‑Replay Measures
Requests carry timestamp and nonce . The server checks that the timestamp is recent and that the nonce has not been used within the allowed window, storing used nonces in Redis with an expiration.
Interceptor Implementation (Java)
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 {
String timestamp = request.getHeader("timestamp");
String nonceStr = request.getHeader("nonceStr");
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
params = new HashMap<>(16);
Enumeration
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×tamp=%s&nonceStr=%s&key=%s", this.sortQueryParamString(params), timestamp, nonceStr, key);
return SecureUtil.md5(qs).toLowerCase();
}
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();
}
}TLS Encryption Example (Java)
// Create SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(new FileInputStream("keystore.jks"), "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());
// send request, handle responseAK/SK Generation Scheme
Implement an API key management system that stores app_id , access_key , secret_key , validity periods, and enabled status in a database.
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 Enhancements
Use POST for all requests to avoid exposing parameters in URLs.
Configure IP white‑lists at the firewall level.
Apply per‑IP rate limiting with Redis.
Log each request for troubleshooting.
Mask sensitive fields using RSA encryption.
Ensure idempotency by using a globally unique nonce stored in Redis.
Version APIs (e.g., /v1/ , /v2/ ).
Standardize response format with code , message , and data fields.
Document APIs with Swagger or similar tools.
Signature Generation Steps
Collect all parameters (including appId , timeStamp , nonce ), remove empty values and the sign itself, then sort them alphabetically.
Concatenate each key and value without separators.
Append the secret key to the concatenated string.
Compute the MD5 hash of the final string and convert it to uppercase to obtain sign .
Token Concepts
A token (access token) identifies the caller and reduces the need to transmit username/password repeatedly. Tokens are typically UUIDs stored in Redis; the server validates their existence before processing a request.
Two token types are defined:
API Token : used for unauthenticated endpoints (login, registration).
USER Token : used for endpoints that require a logged‑in user.
When a user logs in, the server returns a token; subsequent requests include the token together with the usual signature to ensure both authentication and integrity.
Architect's Guide
Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.
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.