How to Secure OpenAPI with AppId/AppSecret and RSA Signatures – A Complete Walkthrough
This article explains how to use OpenAPI standards to standardize interfaces, generate unique AppId/AppSecret pairs, create RSA‑based signatures, and implement practical security measures such as timestamps, nonces, rate limiting, and data validation, all illustrated with full Java code examples.
OpenAPI specifications were created to standardize software interfaces, improve reusability, and enhance security; this article shows how to apply those standards in practice.
AppId and AppSecret
AppId serves as a globally unique identifier for a client, while AppSecret acts like a password. By pairing them, a signature is generated and encrypted; the client includes this signature in each request, and the server validates it to ensure data integrity and authenticity.
RSA Signature
Before using signatures, the article introduces asymmetric encryption (RSA) and hash algorithms (MD5, SHA‑256). It explains that RSA can sign a hash (e.g., SHA256withRSA) to provide non‑repudiation, and shows the underlying method used for the appSign value.
Usage Example
Client preparation – The client builds a business object, computes a SHA‑256 hash as sign, assembles parameters (appId, nonce, timestamp, sign) in alphabetical order, appends appSecret, and signs the concatenated string with the private RSA key to obtain appSign. The request header is then constructed with appId, nonce, sign, timestamp, and appSign, and the full request is serialized to JSON.
UserEntity userEntity = new UserEntity();
userEntity.setUserId("1");
userEntity.setPhone("13912345678");
String sign = getSHA256Str(JSONObject.toJSONString(userEntity));
// build map, sort keys, concatenate, append appSecret
StringBuilder sb = new StringBuilder();
for (String k : keyArray) {
if (data.get(k).trim().length() > 0)
sb.append(k).append("=").append(data.get(k).trim()).append("&");
}
sb.append("appSecret=").append(appSecret);
String appSign = sha256withRSASignature(privateKey, sb.toString());
Header header = Header.builder()
.appId(appId)
.nonce(nonce)
.sign(sign)
.timestamp(timestamp)
.appSign(appSign)
.build();
APIRequestEntity request = new APIRequestEntity();
request.setHeader(header);
request.setBody(userEntity);
String requestParam = JSONObject.toJSONString(request);Server verification – The server parses the JSON, recomputes the SHA‑256 hash of the body, compares it with the received sign, retrieves appSecret using appId, rebuilds the signed string, and verifies the RSA signature with the stored public key. If any check fails, an exception is thrown.
String sign = getSHA256Str(JSONObject.toJSONString(userEntity));
if (!sign.equals(header.getSign())) throw new Exception("Data signature error!");
String appSecret = getAppSecret(header.getAppId());
// rebuild string and verify
if (!rsaVerifySignature(sb.toString(), publicKey, header.getAppSign()))
throw new Exception("Public key verification error!");
System.out.println("【Provider】Verification passed!");Common Protection Measures
Timestamp – Requests include a timestamp and the server rejects any request older than five minutes.
long now = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
if ((now - Long.parseLong(timestamp)) / 1000 / 60 >= 5) throw new Exception("Request expired!");Nonce – A random nonce ensures each request can be used only once. The server stores used nonces in a cache (Guava in the example, Redis in production) with a five‑minute TTL; a repeated nonce triggers a rejection.
if (cache.getIfPresent(appId + "_" + nonce) != null) throw new Exception("Request invalid!");
cache.put(appId + "_" + nonce, "1");Access control – The API should enforce that an appId can only access resources within its permission scope.
Parameter validation – Using SpringBoot Validation annotations (e.g., @NotBlank, @DecimalMax, @Email) to enforce length, format, and range constraints on incoming data.
@NotBlank(message = "Name cannot be empty")
private String name;
@DecimalMin("5") @DecimalMax("10")
private BigDecimal amount;
@Email
private String email;Rate limiting – Prevents overload and abuse. Single‑node implementations can use Guava RateLimiter; distributed solutions include Redis or Alibaba’s open‑source Sentinel.
Sensitive data handling – Mask or encrypt personal identifiers such as ID numbers, phone numbers, and bank cards.
Whitelist/Blacklist – IP‑based controls restrict access to trusted callers (whitelist) or block known malicious sources (blacklist).
Additional API Design Considerations
Clear naming and description of each endpoint.
Support standard HTTP methods (GET, POST, PUT, DELETE) with well‑defined request/response formats.
Define comprehensive error codes and messages.
Provide documentation and sample payloads for developers.
Design for extensibility (e.g., versioning) while maintaining backward compatibility.
MD5 Applications
MD5 can be used for password hashing, but its deterministic nature makes it vulnerable to rainbow‑table attacks. The article demonstrates hashing the same password twice and obtaining identical results, then shows how an online lookup can recover the original password.
String pwd = "123456";
String s = DigestUtils.md5Hex(pwd);
System.out.println("First MD5: " + s);
String s1 = DigestUtils.md5Hex(pwd);
System.out.println("Second MD5: " + s1);To mitigate this, a salt is added before hashing, producing a unique digest that cannot be matched by pre‑computed tables.
String salt = "wylsalt";
String s = DigestUtils.md5Hex(salt + pwd);
System.out.println("Salted MD5: " + s);Digital Signatures
Beyond password hashing, hash functions are also used in digital signatures. The article includes a diagram (from Baidu Baike) illustrating the typical signing workflow.
Symmetric Encryption Algorithms
Discusses DES, 3DES, and AES. DES uses a 56‑bit key and operates on 64‑bit blocks; 3DES applies DES three times for stronger security at the cost of performance. AES is the modern standard.
ECB mode encrypts each block independently, which can reveal patterns; the article shows identical ciphertext for repeated plaintext.
String first = encryptWithECB("1234567812345678", key, "UTF-8");
String second = encryptWithECB("12345678123456781234567812345678", key, "UTF-8");
System.out.println("First encryption: " + first);
System.out.println("Second encryption: " + second);CBC mode introduces an initialization vector (IV) to chain blocks, preventing the pattern leakage of ECB but sacrificing parallelism.
Overall, the article provides a step‑by‑step guide, concrete code, and security reasoning for building robust, authenticated OpenAPI services.
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.
