Information Security 29 min read

Designing Secure Third‑Party API Interfaces: Authentication, Signature, and Best Practices

This guide details a secure third‑party API design, covering API key generation, request signing with timestamps and nonces, permission division, CRUD endpoint definitions, unified response structures, and best‑practice security measures such as HTTPS, IP whitelisting, rate limiting, logging, and idempotency handling.

Top Architecture Tech Stack
Top Architecture Tech Stack
Top Architecture Tech Stack
Designing Secure Third‑Party API Interfaces: Authentication, Signature, and Best Practices

When providing third‑party interfaces, security concerns such as data tampering, replay attacks, and duplicate submissions must be addressed.

Design Overview

Generate a unique API key pair (Access Key/Secret Key) for each third‑party application, use the Access Key (AK) to identify the client and the Secret Key (SK) to sign requests.

AK: identifier; SK: secret used for signing and verification.

Authentication steps: the client creates a signature using AK, request parameters, a timestamp, and a nonce; the server validates the timestamp (e.g., within 60 s), checks nonce uniqueness (e.g., via Redis), and verifies the signature.

Permission Division

Define appId, appKey, appSecret, token, and their usage; appKey + appSecret can be paired to represent different permission levels (read‑only, read‑write, delete, etc.).

Signature Process

1. Collect all non‑empty parameters (including appId, timestamp, nonce) except the signature itself.

2. Sort parameters alphabetically and concatenate as key1value1key2value2… .

3. Append the secret key.

4. Compute the MD5 hash, convert to uppercase; this is the sign .

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");
        // 判断时间是否大于xx秒(防止重放攻击)
        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");
        }
        // 判断该用户的nonceStr参数是否已经在redis中(防止短时间内的重放攻击)
        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");
        }
        // 将本次用户请求的nonceStr参数存到redis中设置xx秒后自动删除
        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;
    }
    /** * 按照字母顺序进行升序排序
     * @param params 请求参数 。注意请求参数中不能包含key
     * @return 排序后结果
     */
    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();
    }
}

API Interface Design

Typical CRUD endpoints (list, create, update, delete) with HTTP methods GET, POST, PUT, DELETE, request parameters, and standard response codes (200, 201, 204).

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

Define a unified response format with fields code , message , and data , and an enum for status codes.

public enum CodeEnum {// 根据业务需求进行添加
    SUCCESS(200, "处理成功"),ERROR_PATH(404, "请求地址错误"),
    ERROR_SERVER(505, "服务器内部发生错误");
    private int code;
    private String message;
    CodeEnum(int code, String message) {
        this.code = code;
        this.message = message;
    }
    public int getCode() { return code; }
    public String getMessage() { return message; }
}
public class Result implements Serializable {
    private static final long serialVersionUID = 793034041048451317L;
    private int code;
    private String message;
    private Object data = null;
    public int getCode() { return code; }
    public void setCode(int code) { this.code = code; }
    public String getMessage() { return message; }
    public void setMessage(String message) { this.message = message; }
    public Object getData() { return data; }
    public Result fillCode(CodeEnum codeEnum) {
        this.setCode(codeEnum.getCode());
        this.setMessage(codeEnum.getMessage());
        return this;
    }
    public Result fillData(Object data) {
        this.setCode(CodeEnum.SUCCESS.getCode());
        this.setMessage(CodeEnum.SUCCESS.getMessage());
        this.data = data;
        return this;
    }
}

Additional security measures include HTTPS, IP white‑listing, rate limiting with Redis, request logging, data masking, idempotency handling via token or nonce, versioning, and standardized status codes.

Overall, the article provides a comprehensive guide for building secure, maintainable third‑party APIs.

backendJavaAuthenticationtimestampAPI securitySignaturenonce
Top Architecture Tech Stack
Written by

Top Architecture Tech Stack

Sharing Java and Python tech insights, with occasional practical development tool tips.

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.