Information Security 29 min read

Designing Secure Third‑Party API Authentication with Access Keys, Signatures, and Token Management

This article presents a comprehensive design for securing third‑party API calls by using Access Key/Secret Key pairs, timestamp and nonce based signatures, token generation, permission granularity, TLS encryption, and practical Java code examples to prevent replay and tampering attacks.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Designing Secure Third‑Party API Authentication with Access Keys, Signatures, and Token Management

Design Overview

When exposing interfaces to third‑party systems, data integrity, freshness, and idempotency must be guaranteed. The proposed solution uses an Access Key/Secret Key pair for authentication, a callback URL for asynchronous notifications, and a detailed permission model.

Permission Division

Each application is identified by an appId . The appKey (public) and appSecret (private) form a pair that can be associated with multiple permissions, enabling fine‑grained access control such as read‑only or read‑write rights.

Signature Process

Requests must include a timeStamp (valid for a short window, e.g., 60 seconds) and a random nonce (at least 10 characters). The server stores used nonces in Redis to reject duplicates, thereby preventing replay attacks.

Signature Rules

Generate a unique appId and secret appSecret for each client.

Append the current timeStamp to the request.

Add a nonce to ensure uniqueness.

Compute a signature sign over all parameters (excluding empty values and the signature itself) sorted alphabetically, concatenate the secret, and apply MD5 (uppercase).

API Interface Design

Typical CRUD endpoints are illustrated:

GET /api/resources – list resources (optional page and limit ).

POST /api/resources – create a resource (requires name ).

PUT /api/resources/{resourceId} – update a resource.

DELETE /api/resources/{resourceId} – delete a resource.

All endpoints should use POST for higher security, enforce IP white‑listing, rate limiting, logging, data masking, idempotency keys, versioning, and a unified response format ( code , message , data ).

Security Considerations

Use HTTPS/TLS for transport encryption, validate appKey / appSecret signatures on every request, and store secrets securely. Implement interceptors to check timestamps, nonces, and signatures before processing the request.

Code Example – Signature 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 {
        // Get timestamp, nonce, and signature from headers
        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();
        if (enumeration.hasMoreElements()) {
            String name = enumeration.nextElement();
            String value = request.getParameter(name);
            params.put(name, URLEncoder.encode(value, CommonConstants.UTF_8));
        }
        String qs = String.format("%s×tamp=%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;
    }
    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

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());
// set other request parameters, send request, handle response

AK/SK Generation and Management

A dedicated credential service creates unique access_key (AK) and secret_key (SK) for each client, stores them in a database with validity periods, and optionally encrypts the SK. Clients obtain their keys via a secure portal or API.

Token and Signature Validation

After login, the server issues a UUID accessToken (token) stored in Redis. Subsequent requests must include the token together with the signed parameters (AK, timestamp, nonce). The interceptor validates token existence, timestamp freshness, nonce uniqueness, and signature correctness before allowing the request.

Additional Best Practices

Use IP white‑listing and per‑IP rate limiting (Redis counters).

Log all API calls for audit and troubleshooting.

Mask sensitive fields (e.g., order numbers) using RSA encryption.

Ensure idempotency by requiring a unique request identifier stored in Redis.

Version APIs (e.g., /v1/ , /v2/ ) and document them with Swagger.

backendtokenAPI securitySignatureaccess key
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

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.