Information Security 31 min read

Designing Secure Third‑Party API Authentication with AK/SK, Signatures and Token Management

This article presents a comprehensive design for securing third‑party API calls by generating unique Access Key/Secret Key pairs, defining permission granularity, implementing signature generation with timestamps and nonces, handling token lifecycle, and providing concrete Java and SQL code examples for practical deployment.

Top Architect
Top Architect
Top Architect
Designing Secure Third‑Party API Authentication with AK/SK, Signatures and Token Management

When exposing APIs to third‑party systems, data integrity, freshness, and replay protection must be considered. The proposed solution uses an Access Key/Secret Key (AK/SK) pair for each client, a token for session management, and a signature algorithm that incorporates a timestamp and a random nonce.

Design Overview

Generate a unique AK/SK for each application; AK identifies the client, SK is kept secret for signing.

Clients create a signature by sorting all request parameters (excluding empty values and the signature itself), concatenating key+value strings, appending the SK, and applying MD5 (uppercase).

Include timestamp (valid for 60 seconds) and nonce (minimum 10 characters) to prevent replay attacks.

Server validates timestamp freshness, checks nonce uniqueness in Redis, and verifies the signature.

Permission Division

Each application has an appId (unique identifier). Permissions are expressed via appKey (public) and appSecret (private). Multiple appKey+appSecret pairs can be assigned to the same appId to achieve fine‑grained read/write control.

Signature Process

The request flow is:

Client sends appId , timestamp , nonce , and other parameters.

Client computes sign = MD5(sortedParams + SK).toUpperCase() and includes it in the request header.

Server checks timestamp, verifies nonce in Redis (to reject duplicates), recomputes the signature, and rejects the request if any check fails.

Implementation Example (Java Spring Interceptor)

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 = new ArrayList<>(params.keySet());
        Collections.sort(listKeys);
        StringBuilder content = new StringBuilder();
        for (String param : listKeys) {
            content.append(param).append("=").append(params.get(param).toString()).append("&");
        }
        return content.length() > 0 ? content.substring(0, content.length() - 1) : content.toString();
    }
}

TLS Encryption Example

// 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());

Database Table for API Credentials

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
);

Standard Response Wrapper

public class Result implements Serializable {
    private int code;
    private String message;
    private Object data = null;
    // getters, setters, and helper methods omitted for brevity
}

Additional best practices such as IP white‑listing, rate limiting with Redis, request logging, data masking, idempotency handling, API versioning, and unified response formats are also discussed to build a robust, production‑ready API platform.

backendJavaAuthenticationtokenAPI securitySignatureAK/SK
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

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.