Overview
In distributed system interaction, the security of API interfaces is crucial. This article will deeply analyze the HTTP request signature verification mechanism implemented based on Spring Boot. This solution supports GET/POST and other request methods, providing timeliness verification and data integrity guarantee. The following is an analysis of the technical key points of the core implementation.
Functional Features
- Multi-protocol support: Completely cover common request types such as GET, POST (JSON/Form-Data)
- Aging control: Request time window for 5 minutes validity period
- Multiple verification: Public key verification + signature verification + timestamp triple protection mechanism
- Safe filtering: Automatically exclude signature parameters to participate in the verification and calculation
- Encoding compatible: Automatically handle URL special character encoding issues
Core implementation analysis
1. Main verification process
public boolean verifySignature(HttpServletRequest request,
HttpServletResponse response,
GridAccountUser accountUser) {
// Get the public key configuration
StateGridAccount gridAccount = stateGridService
.getStateGridAccountById(());
// Request type routing
if((())) {
// Handle JSON/Form-Data types
} else if((())) {
// Process GET parameters
}
// Return the verification result uniformly
}
2. Key technical points
2.1 Timestamp Verification
private Boolean verifyTimestamp(String timestampStr) {
long timestamp = (timestampStr);
long currentTime = ();
return (currentTime - timestamp) <= 300_000; // 5 minutes valid
}
- Prevent replay attacks
- Requires time synchronization on the client server side
- Error window configurable suggestions
2.2 Request body processing
JSON request processing:
String requestStr = getRequestBody(request);
JSONObject jsonObject = (requestStr);
("sign"); // Filter signature parameters
Form-Data processing:
Map<String, Object> formData = (request, "");
("sign");
GET request processing:
Map<String, String[]> queryParams = ();
signature = (" ", "+"); // Handle URL encoding
2.3 Signature verification
(
uri, // Request path
(), // Filtered parameters
(timestamp),
signature,
publicKey
);
Optimization suggestions
-
Parameter serialization optimization
- Currently, JSONUtil using Fastjson and Hutool is used simultaneously. It is recommended to unify the JSON processing library.
- Jackson is recommended for standardized processing
- Exception handling enhancement
try {
// Sign-in verification logic
} catch (NumberFormatException e) {
("Timestamp format exception: {}", timestampStr);
throw new InvalidTimestampException();
} catch (SignatureException e) {
("Signature verification failed: {}", ());
}
-
Performance optimization
- Add public key caching mechanism (RedisCache)
- Use connection pool to manage database queries
-
Security enhancement
- Add a playback attack counter
- Support dynamic time window configuration
- Add blacklist IP mechanism
Things to note
- Time synchronization: Ensure the time synchronization of NTP services
- Key Management: It is recommended to adopt a key rotation mechanism
- Empty parameter processing: Need to clarify the processing strategy of empty strings and null
- Coding consistency:Use UTF-8 character set uniformly
- Log desensitization: sensitive parameters need to be filtered
Summarize
This implementation solution provides basic guarantees for API interface security, and in actual production environment, the following functions can be expanded according to business needs:
- Increased flow frequency limit control
- Implement two-way certificate verification
- Supports multiple hashing algorithms
- Add OpenAPI specification support
- Integrated API Management Platform
By continuously optimizing the verification process and improving the monitoring mechanism, a safe and reliable API gateway system can be effectively built.
Attached complete code
Production key private key tool class
package ;
import ;
import ;
import ;
import .Base64;
import ;
public class KeyGenExample {
private static final String ALGORITHM = "RSA";
public static Map<String, String> generateKey() throws NoSuchAlgorithmException {
// 1. Selection Algorithm (RSA/EC/DSA)
KeyPairGenerator keyGen = (ALGORITHM);
(2048); // Key length
// 2. Generate key pair
KeyPair keyPair = ();
// 3. Obtain the public and private key (Base64 encoding printing)
byte[] privateKeyBytes = ().getEncoded();
byte[] publicKeyBytes = ().getEncoded();
String privateKey = ().encodeToString(privateKeyBytes);
String publicKey = ().encodeToString(publicKeyBytes);
Map<String, String> map = ();
("private_key", privateKey);
("public_key", publicKey);
return map;
}
public static void main(String[] args) {
try {
Map<String, String> stringStringMap = generateKey();
(stringStringMap);
} catch (NoSuchAlgorithmException e) {
();
}
}
}
Generate signature and verification methods
package ;
import ;
import ;
import .;
import ;
import ;
import .*;
import .PKCS8EncodedKeySpec;
import .X509EncodedKeySpec;
import .Base64;
import ;
import ;
/**
* <b>System:</b>NCC<br/>
* <b>Title:</b><br/>
* <b>Description:</b>Add description information<br/>
* <b>@author: </b>zhouxiaomin_a<br/>
* <b>@date:</b>2018/6/21 17:00<br/>
* <b>@version:</b> 1.0.0.0<br/>
* <b>Copyright (c) 2017 ASPire Tech.</b>
*/
public class SignUtil {
private static final String CHARSET = "UTF-8";
private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
private static final String KEY_ALGORITHM = "RSA";
// Method of generating signatures by passing in private keys, data and timestamps
public static String generateSignature(String url, String data, long timestamp, String privateKeyStr) throws Exception {
// Merge data and timestamp into a string
String dataWithTimestamp = url + "|" + data + "|" + timestamp;
// Generate private key through incoming private key string
PrivateKey privateKey = getPrivateKeyFromString(privateKeyStr);
// Create a signed object, using the SHA256withRSA algorithm
Signature signature = (SIGNATURE_ALGORITHM);
(privateKey);
// Update data
((StandardCharsets.UTF_8));
// Generate a signature
byte[] signedData = ();
// Convert signature to Base64 encoded string
return ().encodeToString(signedData);
}
// Method to generate private key object by passing in private key string
public static PrivateKey getPrivateKeyFromString(String privateKeyStr) throws Exception {
// Decode Base64-encoded private key string into a byte array
byte[] decodedKey = ().decode(privateKeyStr);
// Use KeyFactory to generate a private key object
KeyFactory keyFactory = (KEY_ALGORITHM);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
return (keySpec);
}
// Method to verify signature
public static boolean verifySignature(String url, String data, Long timestamp, String signatureStr, String publicKeyStr) throws Exception {
if ((url) || (data) || null == timestamp
|| (signatureStr) || (publicKeyStr)) {
return false;
}
// Merge data and timestamp into a string
String dataWithTimestamp = url + "|" + data + "|" + timestamp;
// Generate public key through incoming public key string
PublicKey publicKey = getPublicKeyFromString(publicKeyStr);
// Create a signed object, using the SHA256withRSA algorithm
Signature signature = (SIGNATURE_ALGORITHM);
(publicKey);
// Update data
((StandardCharsets.UTF_8));
// Convert Base64-encoded signature string to byte array
byte[] signatureBytes = ().decode(signatureStr);
// Verify the signature
return (signatureBytes);
}
// Method to generate public key object by passing in public key string
public static PublicKey getPublicKeyFromString(String publicKeyStr) throws Exception {
// Decode Base64-encoded public key string into a byte array
byte[] decodedKey = ().decode(publicKeyStr);
// Use KeyFactory to generate public key objects
KeyFactory keyFactory = (KEY_ALGORITHM);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedKey);
return (keySpec);
}
// Test code
public static void main(String[] args) throws Exception {
// Get Base64 encoded string for public and private keys
String privateKeyStr = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC+3ZTH/VCeWaesY4muy7fOvqLLTX07QpfJVeG1KZKSCjeciih3VRQfMn1PsxL0elMUFIfRKGOtjS7o0+V3UqAwaJCozyxEMPw3OGKQ0bb2dCId/TCnjWkFpSOj1UKct/LdOT/CZ07Bu+8DCynjzHSxwA Te2DYWZp8fHf9FAuYjPuXhkeFEkf+/TG5ObyDE4VqzHiTb4XmjkJCuhk98H5CLOCrWb+NiyHpbp/fPF865QzEG1n9/pyFKyMDsBgSxmx0iXDheR6ue/wotxnDvJgGqaG00kD88r/oU2M3xo581a9sF+v6fzNCldtF+MHJJVd4OhFcscahBySEhIUY9TZo/AgMBAAECggEAI h6TP7MBa+VEC5WZocUqHQvIJ0a5adQMNUIkgJGncXLhIRszg62SVMdeTlaJP2n0mwTWiKXLN9Wiup1SimObXjv7DCpI1AHbvHVYbWIH7oOxK6I8xd8KFKfCOMHhUAm0ISbgRnzYP9q8LdObj+zXOYVFeZ62AIgkzte6b9hGUqtWv4yGL3VTiki5TvzHpRPJoeEYHEk+zGhd Cn9dr7GY+3mm3EfnNL69s78jWAdkib8fHQsgunOB4NiA4Gnn58Aphzw/SYio6mS5ieRXz4h9ng77UI4agkuU6QhYMdt+s+7YTs4Z6+iXnE3f0zHZJT/UA1GItcxnYuznl2X+vKWS3QKBgQD3L/lUfh5R2N40rB16cE4W75betT59TIjEW+/g5VY4WhZZXEmIi7HWcxoAYXeT p1/Ls2KrKWyAs5ys89lTmasJh2M8afS04mUTCtOO2/bsnUXc0g7P8M913X2fWME+uIVLXBbG2+0xfXGgLCtsgZro5IkDqrHO09SX8s02jOAbJQKBgQDFq5HkGsxJZzS7anFoaBnXxUK590bkIt4eIyOs5Z7AldppWIaT5uCR9TAq3N29rvxBPBwiRKIsghleTHtkvccImDE Cu6ztVubn1oaudvNnHPbUyi5rfxg+dAKUoLxX3nzniSYupyN94QDq+q/OPiTBopyAXxRmNfn8x3YGvOi0kwKBgQCv3D/E7x1fGa2tR66JR5EnHDn4JHZa6rJ7EPWuyTr4SI+R7+iY7toNOkKLdsx+DhxHbk6Ke6QoRKD5I1vA8JkQ5HOjrbZdYpyKWa99+dzJJnNn0UKcijT vJC+VyK1jlB+xJ8lEnX85MIhAbmxOfD7b5ovcQfrSrT6ZBDMf1kYyyQKBgHQq32NRyGr/B0N5S8rTGxTubceCphvezfCiMA4lKAYAS0qL5xM2pRXCJZubD4mxM7hWziXpdfF4R9ZeVkofKcBISM1VZExbPPpU3fPcHjGkGP93Do7IM4RIg1e7mtR9AaTEujbCrR4GRJbT2s v3Q3y0xwq+Veu3nwHKaveMv6mXAoGBAM0trI9b/k7oHS8z9FfETTeiXxac3puumRcGt3HmCISPmXxc4RWMVbMPUWSzm2KFdWtKEYMMxvSZcEG90rxM/Mh3Uhdj8Pdz2iwAnCghpwzAtp60N2yKxJyq80gRFywTlNp70VxDxYCFZ9Dugjzg7NT2JbhOdSjoZDP9FXg+konq";
String publicKeyStr = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvt2Ux/1QnlmnrGOJrsu3zr6iy019O0KXyVXhtSmSkgo3nIood1UUHzJ9T7MS9HpTFBSH0ShjrY0u6NPld1KgMGiQqM8sRDD8NzhikNG29nQiHf0wp41pBaUjo9VCnLfy3Tk/wmdOwbvvAwsp48x0scAE 3tg2FmafHx3/RQLmIz7l4ZHhRJH/v0xuTm8gxOFasx4k2+F5o5CQroZPfB+Qizgq1m/jYsh6W6f3zxfOuUMxBtZ/f6chSsjA7AYEsZsdIlw4Xkernv8KLcZw7yYBqmhtNJA/PK/6FNjN8aOfNWvbBfr+n8zQpXbRfjBySVXeDoRXLHGoQckhISFGPU2aPwIDAQAB";
String url = "/api/rest/demo/queryAlarmInfo";
// Data and timestamp to be signed
String json = "{\"SPECIALTY\":8,\"NETWORKTYPE\":801,\"VENDORNAME\":\"Huawei\",\"NETYPE\":\"801\",\"ALARMTITLE\":\"\",\"ORG_EVENT_ID\":\"\"\"}";
Map map = (json, );
String data = ();
(data);
long timestamp = (); // Current timestamp
(timestamp);
// Generate a signature
String signature = generateSignature(url, data, timestamp, privateKeyStr);
("Generated Signature: " + signature);
// Verify the signature
boolean isVerified = (url, data, timestamp, signature, publicKeyStr);
("Signature Verified: " + isVerified);
("---------------------------------------------------------");
String url2 = "/api/rest/demo/getUser";
Map<String, Object> params = new HashMap<>();
("idcard", "123456");
long timestamp1 = (); // Current timestamp
(timestamp1);
String data2 = ();
(data2);
// Generate a signature
String signature1 = generateSignature(url2, data2, timestamp1, privateKeyStr);
("Generated Signature1: " + signature1);
// Verify the signature
boolean isVerified1 = (url2, data2, timestamp1, signature1, publicKeyStr);
("Signature isVerified1: " + isVerified1);
("---------------------------------------------------------");
String url3 = "/api/rest/demo/getIdCard";
Map<String, Object> params1 = new HashMap<>();
("name", "Li Si");
("age", "27");
String data3 = ();
(data3);
long timestamp2 = (); // Current timestamp
(timestamp2);
// Generate a signature
String signature2 = generateSignature(url3, data3, timestamp2, privateKeyStr);
("Generated signature2: " + signature2);
// Verify the signature
boolean isVerified2 = (url3, data3, timestamp2, signature2, publicKeyStr);
("Signature isVerified2: " + isVerified2);
}
}
Custom interceptor
package ;
import ;
import ;
import ;
import ;
import .slf4j.Slf4j;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
@Slf4j
@Component
public class SignatureInterceptor implements HandlerInterceptor {
private final TokenService tokenService;
private final VerifySignatureService verifySignatureService;
@Autowired
public SignatureInterceptor(TokenService tokenService, VerifySignatureService verifySignatureService) {
= tokenService;
= verifySignatureService;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String clientType = ("clientType");
if ((clientType)) {
GridAccountUser accountUser = (request);
// Custom signature verification logic
boolean verified = (request, response, accountUser);
if (!verified) {
("Lottery verification failed");
(());
(MediaType.APPLICATION_JSON_VALUE);
(StandardCharsets.UTF_8.name());
// Build error response
String errorMessage = "{\"error\": \"Signature is invalid.\"}";
().write(errorMessage);
return false;
}
}
return true;
}
}
Sign-in verification implementation class
package ;
import ;
import .;
import .;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import .slf4j.Slf4j;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import .*;
@Slf4j
@Service
public class VerifySignatureService {
@Autowired
private IStateGridAccountService stateGridService;
public boolean verifySignature(HttpServletRequest request, HttpServletResponse response, GridAccountUser accountUser) {
//Query the public key
StateGridAccount gridAccount = (());
if (gridAccount == null || (())) {
("Account [{}] public key not configured", ());
return false;
}
String publicKey = ();
String uri = ();
// Determine the current request method
if (().equals(())) {
// Determine whether it is a JSON format request
if (MediaType.APPLICATION_JSON_VALUE.equals(())) {
// Get the content of the request body
String requestStr = getRequestBody(request);
("Request body content: {}", requestStr);
if ((requestStr)) {
return false;
}
JSONObject jsonObject = (requestStr);
Map map = (requestStr, );
("sign");
("timestamp");
String timestamp = ("timestamp");
String signature = ("sign");
// Verify timestamp
Boolean verifed = verifTimestamp(timestamp);
if (!verifed) {
return false;
}
try {
boolean isValid = (uri, (), (timestamp), signature, publicKey);
return isValid;
} catch (Exception e) {
(());
return false;
}
} else if (() != null && ().startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)) {
// Process form data format
Map<String, Object> formData = (request, "");
("Form Data: {}", formData);
String timestamp = (String) ("timestamp");
String signature = (String) ("sign");
("sign");
("timestamp");
// Verify timestamp
Boolean verifed = verifTimestamp(timestamp);
if (!verifed) {
return false;
}
try {
boolean isValid = (uri, (), (timestamp), signature, publicKey);
return isValid;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
} else if (().equals(())) {
// Processing of GET requests, obtain query parameters for sign-in verification
Map<String, String[]> queryParams = ();
if (queryParams == null) {
return false;
}
Map<String, String> filteredParams = new HashMap<>();
((key, values) -> {
// Filter out the "sign" and "timestamp" parameters
if (!"sign".equals(key) && !"timestamp".equals(key)) {
(key, > 0 ? values[0] : "");
}
});
String timestamp = ("timestamp");
String signature = ("sign");
if (timestamp == null || signature == null) {
return false;
}
// Verify timestamp
Boolean verifed = verifTimestamp(timestamp);
if (!verifed) {
return false;
}
//+ symbols are usually converted to a space (%20), because in URL encoding, the + symbol represents a space
signature = (" ", "+");
boolean isValid = false;
try {
isValid = (uri, (), (timestamp), signature, publicKey);
if (!isValid) {
return false; // Return false indicates that the signature verification failed
}
return true; // Return true means the signature verification is successful
} catch (Exception e) {
(());
return false;
}
}
return false; // Return false by default, if the request method does not support it
}
/**
* @throws
* @MethodName verifTimestamp
* @author zhouzihao
* @param: timestampStr
* @DateTime March 25, 2025, 0025 06:13 pm
* @return:
* @description: Check timestamp: Ensure that the client and server time are synchronized, the error is within 5 minutes.
*/
private Boolean verifTimestamp(String timestampStr) {
long timestamp = (timestampStr);
long currentTime = ();
if ((currentTime - timestamp) > 300000) { // 5 minutes
("Lottery verification failed: timestamp expired");
return false;
}
return true;
}
/**
* Read the content of the request body
*/
private static String getRequestBody(HttpServletRequest request) {
InputStream in = null;
StringBuffer sb = null;
try {
in = ();
BufferedReader br = new BufferedReader(new InputStreamReader(in,
("UTF-8")));
sb = new StringBuffer("");
String temp;
while ((temp = ()) != null) {
(temp);
}
if (in != null) {
();
}
if (br != null) {
();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return ();
}
}