Generating WeChat JS-SDK Signatures in Java

The WeChat JS-SDK requires a cryptographic signature to validate frontend requests. This process relies on two core resources: an OAuth access_token and a dynamic jsapi_ticket. Both tokens have expiration windows and rate limits, necessitating proper caching mechanisms in production environments.

1. Token Acquisition Workflow First, obtain your application credentials (AppID and AppSecret) from the WeChat Official Platform dashboard. Use these credentials to request a server-side access token: GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={APPID}&secret={APPSECRET}

Once authenticated, extract the access_token from the JSON response. Subsequently, request a JSAPI ticket using this tokan: GET https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={ACCESS_TOKEN}&type=jsapi

2. Signature Algorithm Implementation The signature is generated by constructing a raw query string from four parameters, sorting them lexicographically, concatenating them, and applying the SHA-1 hash algorithm. The required fields are:

  • jsapi_ticket: The valid ticket obtained previously.
  • noncestr: A random alphanumeric string (typically 16 characters).
  • timestamp: Unix epoch time in seconds.
  • url: The full URL of the current web page, strictly excluding the fragment identifier (#...).

3. Java Implementation Below is a refined implementation utilizing modern Java APIs for HTTP communication and cryptographic hashing.

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.security.MessageDigest;
import java.time.Instant;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import javax.json.JsonObject;
import javax.json.Json;
import java.nio.charset.StandardCharsets;

public class WeChatSignatureEngine {

    private static final HttpClient httpClient = HttpClient.newHttpClient();
    private static final String API_BASE = "https://api.weixin.qq.com/cgi-bin";

    // Step 1 & 2: Retrieve Access Token and JSAPI Ticket
    public static Map<String, String> fetchCredentials(String appId, String appSecret) throws Exception {
        HttpRequest tokenRequest = HttpRequest.newBuilder()
                .uri(URI.create(String.format("%s/token?grant_type=client_credential&appid=%s&secret=%s", 
                        API_BASE, appId, appSecret)))
                .GET()
                .build();
        
        HttpResponse<String> tokenResponse = httpClient.send(tokenRequest, HttpResponse.BodyHandlers.ofString());
        JsonObject tokenJson = parseJson(tokenResponse.body());
        String accessToken = tokenJson.getString("access_token");

        HttpRequest ticketRequest = HttpRequest.newBuilder()
                .uri(URI.create(String.format("%s/ticket/getticket?access_token=%s&type=jsapi", API_BASE, accessToken)))
                .GET()
                .build();

        HttpResponse<String> ticketResponse = httpClient.send(ticketRequest, HttpResponse.BodyHandlers.ofString());
        JsonObject ticketJson = parseJson(ticketResponse.body());
        String jsApiTicket = ticketJson.getString("ticket");

        Map<String, String> credentials = new LinkedHashMap<>();
        credentials.put("accessToken", accessToken);
        credentials.put("jsapiTicket", jsApiTicket);
        return credentials;
    }

    // Step 3: Generate Cryptographic Signature
    public static String calculateSignature(String jsapiTicket, String currentUrl) throws Exception {
        String nonce = UUID.randomUUID().toString().replace("-", "").substring(0, 16);
        long timestamp = Instant.now().getEpochSecond();

        // Build and sort parameters alphabetically by key
        Map<String, String> params = new LinkedHashMap<>();
        params.put("jsapi_ticket", jsapiTicket);
        params.put("noncestr", nonce);
        params.put("timestamp", String.valueOf(timestamp));
        params.put("url", currentUrl);

        StringBuilder rawString = new StringBuilder();
        params.entrySet().stream().sorted(Map.Entry.comparingByKey())
                .forEach(e -> rawString.append(e.getKey()).append("=").append(e.getValue()).append("&"));
        
        // Remove trailing ampersand and encode
        String sortedParams = rawString.substring(0, rawString.length() - 1);
        String encodedParam = java.net.URLEncoder.encode(sortedParams, StandardCharsets.UTF_8.name());

        // Apply SHA-1 Hash
        MessageDigest digest = MessageDigest.getInstance("SHA-1");
        byte[] hashBytes = digest.digest(encodedParam.getBytes(StandardCharsets.UTF_8));
        return bytesToHex(hashBytes);
    }

    // Helper Utilities
    private static String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) hexString.append('0');
            hexString.append(hex);
        }
        return hexString.toString();
    }

    private static JsonObject parseJson(String responseBody) {
        // Simplified JSON parsing logic for demonstration
        // In production, use Jackson or Gson
        return Json.parse(responseBody).asJsonObject(); 
    }
}

Key Implementation Details Parameter ordering must strictly follow alphabetical ascent. Any deviation breaks the hash verification on WeChat servers. The timestamp should be synchronized with server time to prevent replay attacks or validation failures due to drift. The target URL must match exactly what is registered in the domain whitelist within the Official Account configuration; relative paths or omitted domains will trigger a permission error. Caching both tokens locally with TTL monitoring is mandatory for high-throughput scenarios.

Tags: java WeChat JS-SDK cryptography API Integration web development

Posted on Fri, 19 Jun 2026 18:33:16 +0000 by mentalist