Location>code7788 >text

Implementation of parsing of HTTP request signature verification based on Spring Boot

Popularity:99 ℃/2025-04-07 11:04:59

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

  1. Multi-protocol support: Completely cover common request types such as GET, POST (JSON/Form-Data)
  2. Aging control: Request time window for 5 minutes validity period
  3. Multiple verification: Public key verification + signature verification + timestamp triple protection mechanism
  4. Safe filtering: Automatically exclude signature parameters to participate in the verification and calculation
  5. 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

  1. 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
  2. Exception handling enhancement
try {
     // Sign-in verification logic
 } catch (NumberFormatException e) {
     ("Timestamp format exception: {}", timestampStr);
     throw new InvalidTimestampException();
 } catch (SignatureException e) {
     ("Signature verification failed: {}", ());
 }
  1. Performance optimization
    • Add public key caching mechanism (RedisCache)
    • Use connection pool to manage database queries
  2. Security enhancement
    • Add a playback attack counter
    • Support dynamic time window configuration
    • Add blacklist IP mechanism

Things to note

  1. Time synchronization: Ensure the time synchronization of NTP services
  2. Key Management: It is recommended to adopt a key rotation mechanism
  3. Empty parameter processing: Need to clarify the processing strategy of empty strings and null
  4. Coding consistency:Use UTF-8 character set uniformly
  5. 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 ();
     }
 }