本周闭关写代码,用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的问题,欢迎来谈。我的邮箱:rick.mao@truston.group