NetSuite OAuth1.0中InvalidSignature问题

本周闭关写代码,用Java通过TBA方式访问NetSuite REST Webservices。由于是手生,卡在InvalidSignature报错上,在这个问题上被卡了一整天。

 直到终于到来的Aha时刻。

在NetSuite中的样例代码是PHP的, 我平移到Java后,代码逻辑丝毫未变。反复核对代码后,发现代码没有任何的问题。按照NetSuite系统的指引,签名流程如下:

1. 构建Base String。这时,请注意字符串的格式,有三种格式。我采用的是REST Webservices,所以按照相应指引进行了构建。

NetSuite Applications Suite - The Signature for Web Services and RESTletshttps://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_1534941088.html#The-Signature-for-Web-Services-and-RESTlets

2. 构建Secret。

3. 用Secret对Base String进行加密,形成Signature。加密方法为HMAC-SHA256。

4. 把Signature结合其他字串,形成Authorization字串,赋给Header。

反复检查上述逻辑,但是一直报InvalidSignature错。相同的参数,在Postman中是毫无问题的。我们在对比了Header中的Authorization字符串后,发现只有最后的oauth_signature的值是有差别的。

 Signature的差别,只会有两种可能性,一是加密方法出错,二是Base String出错。在确定了HmacSHA256没有问题后,我们把问题聚焦在了Base String。

最后,虫子找到了!

原来在OAuth1.0的规范中,host必须是小写的。例如,123456-SB1必须格式化为123456-sb1。

 但是,在构建的Header中,Host是要大写的。这就是大坑所在。

以下代码调试通过,可参考。

//访问NetSuite REST Webservices,请注意Base String中Host的小写格式。
//Rick Mao 2023-6-26


import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.net.URLEncoder;
import java.util.*;

public class MainAppOAuth1okhttp3 {
    private static final String NETSUITE_ACCOUNT = "你的账户"; //对字母大写
    private static final String NETSUITE_CONSUMER_KEY = "你的consumer key";
    private static final String NETSUITE_CONSUMER_SECRET = "你的consumer secret";
    private static final String NETSUITE_TOKEN_ID = "你的token id";
    private static final String NETSUITE_TOKEN_SECRET = "你的token secret";
    // Generate the timestamp and nonce
    private static final String timestamp = Long.toString(System.currentTimeMillis() / 1000L);
    private static final String nonce = UUID.randomUUID().toString();

    public static void main(String[] args) throws Exception {
        // Create OkHttpClient with logging interceptor for debugging
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        OkHttpClient httpClient = new OkHttpClient.Builder()
                .addInterceptor(loggingInterceptor)
                .build();

        // Create the request URL
        HttpUrl requestUrl = HttpUrl.parse("https://" + NETSUITE_ACCOUNT + ".suitetalk.api.netsuite.com/services/rest/record/v1/customer");

        // Generate the Authorization header value
        String authorizationHeader = generateAuthorizationHeader(requestUrl.toString());

        // Create the request
        Request request = new Request.Builder()
                .url(requestUrl)
                .header("Authorization", authorizationHeader)
                .get()
                .build();

        // Execute the request
        try (Response response = httpClient.newCall(request).execute()) {
            // Process the response
            String responseBody = response.body().string();
            System.out.println(responseBody);
        }
    }

    private static String generateAuthorizationHeader(String url) throws Exception {


        // Generate the base string,这是Rest Webservice格式的,SOAP和Restlet则不同
        String baseString = baseStringConcat();

        // Generate the signature
        String signature = generateSignature(baseString, NETSUITE_CONSUMER_SECRET, NETSUITE_TOKEN_SECRET);

        // Construct the Authorization header value
        String AuthString = "OAuth " +
                "realm=\"" + NETSUITE_ACCOUNT + "\"," +
                "oauth_consumer_key=\"" + NETSUITE_CONSUMER_KEY + "\"," +
                "oauth_token=\"" + NETSUITE_TOKEN_ID + "\"," +
                "oauth_signature_method=\"HMAC-SHA256\"," +
                "oauth_timestamp=\"" + timestamp + "\"," +
                "oauth_nonce=\"" + nonce + "\"," +
                "oauth_version=\"1.0\"," +
                "oauth_signature=\"" + URLEncoder.encode(signature, StandardCharsets.UTF_8) + "\"";
        return AuthString;
    }

    private static String generateSignature(String baseString, String consumerSecret, String tokenSecret) throws NoSuchAlgorithmException, InvalidKeyException {

         String EMPTY_STRING = "";
         String CARRIAGE_RETURN = "\r\n";
            String key = URLEncoder.encode(consumerSecret, StandardCharsets.UTF_8) + "&" + URLEncoder.encode(tokenSecret, StandardCharsets.UTF_8);

            SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");

            Mac sha256Hmac = Mac.getInstance("HmacSHA256");

            sha256Hmac.init(secretKey);

            byte[] signatureBytes = sha256Hmac.doFinal(baseString.getBytes(StandardCharsets.UTF_8));

            String resultSignature = new String(java.util.Base64.getEncoder().encode(signatureBytes));

            return resultSignature.replace(CARRIAGE_RETURN, EMPTY_STRING);
         }



    public static String generateSignatureBaseString(String httpMethod, String url, Map<String, String> parameters) throws Exception {
        StringBuilder baseString = new StringBuilder();

        // URL-encode the components of the URL
        String encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8);

        // Sort and encode the parameters
        Map<String, String> sortedParameters = new HashMap<>(parameters);
        sortedParameters.entrySet().stream()
                .sorted(Map.Entry.comparingByKey())
                .forEachOrdered(entry -> {
                    try {
                        String key = URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8);
                        String value = URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8);
                        baseString.append(key).append("=").append(value).append("&");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });

        // Remove the trailing '&' character
        if (baseString.length() > 0) {
            baseString.setLength(baseString.length() - 1);
        }

        // Construct the signature base string
        String signatureBaseString = httpMethod.toUpperCase() + "&" + encodedUrl + "&" + URLEncoder.encode(baseString.toString(), "UTF-8");
        return signatureBaseString;
    }

    private static String baseStringConcat() throws Exception {
        String httpMethod = "GET";
        //NETSUITE_ACCOUNT 需要转为小写,否则服务端报InvalidSignature错。
        String url = "https://"+ NETSUITE_ACCOUNT.toLowerCase() + ".suitetalk.api.netsuite.com/services/rest/record/v1/customer";
        Map<String, String> parameters = new HashMap<>();
        parameters.put("oauth_consumer_key", NETSUITE_CONSUMER_KEY);
        parameters.put("oauth_nonce", nonce);
        parameters.put("oauth_signature_method", "HMAC-SHA256");
        parameters.put("oauth_timestamp", timestamp);
        parameters.put("oauth_token", NETSUITE_TOKEN_ID);
        parameters.put("oauth_version", "1.0");
        String signatureBaseString = generateSignatureBaseString(httpMethod, url, parameters);
        System.out.println(signatureBaseString);
        return signatureBaseString;


    }
}


如果有任何关于NetSuite的问题,欢迎来谈。我的邮箱:[email protected]

猜你喜欢

转载自blog.csdn.net/remottshanghai/article/details/131399801
今日推荐