使用 Salesforce Apex 中的 RESTlet 集成 NetSuite 时出现无效登录尝试错误

问题描述 投票:0回答:2

我正在努力将 NetSuite 与 Salesforce 连接起来,以便使用 RESTlet 脚本和用于 授权OAuth 1 在 NetSuite 中创建客户记录。虽然我可以使用 POSTMAN 创建记录,但 Apex 标注的尝试失败,并显示 403 禁止错误 和“无效登录尝试”消息。 NetSuite 登录审核跟踪指示“InvalidSignature。”

我尝试使用 REST Web 服务连接 NetSuite,然后使用 RESTlet 脚本与 Salesforce 连接。但这两种方法在 POSTMAN 中都能很好地工作,并且在从 Salesforce Apex 标注尝试时会在 Apex 中抛出 403 Forbidden、Invalid Login Attempt 错误。在NetSuite的登录审核跟踪中检查时,详细信息列显示InvalidSignature错误。 它应该从 Salesforce apex 标注成功进行身份验证,就像从 POSTMAN 进行身份验证一样。

我已经仔细检查了参数并使用了 HMAC-SHA256 签名。任何有关解决此问题的见解将不胜感激。另外,如果您之前已成功连接 NetSuite 与 Salesforce,我很想听听您的方法。

谢谢, 米纳尔

salesforce integration netsuite restlet netsuite-rest-api
2个回答
0
投票

我假设您正在代码中设置签名,对于正常请求使用类似的签名,然后使用 HMAC-SHA256。对于邮递员,对于下面的两种情况,它都会为您完成,因此您可能会感到困惑为什么邮递员可以工作,但您的代码却不能:

        data = $"oauth_consumer_key={config["consumerKey"]}" +
            $"&oauth_nonce={oauthNonce}" +
            $"&oauth_signature_method={config["signMethod"]}" +
            $"&oauth_timestamp={timestamp}&oauth_token={config["tokenId"]}" +
            $"&oauth_version={config["oauthVersion"]}";

当您点击 Restlet 的参数端点时...类似于: https://YOURACCOUNTID.restlets.api.netsuite.com/app/site/hosting/restlet.nl?scriptId=SCRIPTID&deployId=DEPLOYID 您需要将这些参数包含在签名生成中。它们也需要按字母顺序显示。所以上面的内容会变成:

        data = $"deploy={DEPLOYID}" +
            $"&oauth_consumer_key={config["consumerKey"]}" +
            $"&oauth_nonce={oauthNonce}" +
            $"&oauth_signature_method={config["signMethod"]}" +
            $"&oauth_timestamp={timestamp}&oauth_token={config["tokenId"]}" +
            $"&oauth_version={config["oauthVersion"]}&script={SCRIPTID}";

请注意,在构建签名值和密钥时,您仅传递不带参数的基本脚本 URL:

    var signatureValue = $"POST&Uri.EscapeDataString(https://YOURACCOUNTID.restlets.api.netsuite.com/app/site/hosting/restlet.nl)}&{Uri.EscapeDataString(data)}";
    var signatureKey = $"{Uri.EscapeDataString(config["consumerSecret"])}&{Uri.EscapeDataString(config["tokenSecret"])}";

但是您需要在 HttpRequestMessage 的 post uri 中发送整个 uri(带有查询参数)。

            var request = new HttpRequestMessage(method, https://YOURACCOUNTID.restlets.api.netsuite.com/app/site/hosting/restlet.nl?scriptId={SCRIPTID}&deployId={DEPLOYID})
            {
                Content = payload
            };
            foreach (var header in headers)
            {
                request.Headers.Add(header.Key, header.Value);
            }
            return await httpClient.SendAsync(request);

假设“headers”是一个字典,您从创建标题中获得


0
投票

这是我参考的代码。

这是标注的代码片段:

public class AuthNetSuite { private static final String NETSUITE_RESTLET_URL = 'https://(ACCOUNT_ID).http://restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=(SCRIPT_ID)&deploy=( DEPLOY_ID)';

私有静态最终字符串 CONSUMER_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';

私有静态最终字符串 CONSUMER_SECRET = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';

私有静态最终字符串 ACCESS_TOKEN = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx';

私有静态最终字符串 TOKEN_SECRET = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';

私有静态最终字符串 REALM = 'xxxxxxxxxxxx';公共静态 HttpResponse calloutToNet(String endPoint, String bodyString, String methodName) {

HttpRequest httpRequestObject = new HttpRequest();

httpRequestObject.setEndpoint(endPoint);

httpRequestObject.setMethod(methodName);

httpRequestObject.setHeader('Content-Type', 'application/json');

//httpRequestObject.setTimeout(120000); // Setting timeout to 2 minutes    if(bodyString != null) {

  httpRequestObject.setBody(bodyString);

}    String authHeader = OAuth1.getAuthorizationHeader('POST', NETSUITE_RESTLET_URL, ACCESS_TOKEN, TOKEN_SECRET, CONSUMER_KEY, CONSUMER_SECRET, REALM);

system.debug('authHeader '+authHeader);    httpRequestObject.setHeader('Authorization',authHeader);    Http http = new Http();

HttpResponse httpResponse = http.send(httpRequestObject);

System.debug('Status Code: '+httpResponse.getStatusCode()+', Status: '+httpResponse.getStatus()+', Body: '+httpResponse.getBody());    return httpResponse;

}

}

这是代码片段:签名构造

public static String getAuthorizationHeader(字符串方法,字符串url,字符串oauthToken,字符串oauthTokenSecret,字符串consumerKey,字符串consumerSecret,字符串领域){

String oauthNonce = generateNonce();

String oauthTimestamp = String.valueOf(System.currentTimeMillis() / 1000);

system.debug('oauthTimestamp '+oauthTimestamp);

// Construct signature base string

String signatureBaseString = constructSignatureBaseString(method, url, oauthNonce, oauthTimestamp, oauthToken, consumerKey, realm);

// Construct signing key

String signingKey = consumerSecret + '&' + oauthTokenSecret;

// Generate signature

Blob hmac = Crypto.generateMac('HmacSHA256', Blob.valueOf(signatureBaseString), Blob.valueOf(signingKey));

String signature = EncodingUtil.base64Encode(hmac);

// Construct authorization header

String authHeader = 'OAuth ';

authHeader += 'oauth_consumer_key="' + consumerKey + '",';

authHeader += 'oauth_nonce="' + oauthNonce + '",';

authHeader += 'realm="' + realm + '",';

authHeader += 'oauth_signature="' + EncodingUtil.urlEncode(signature, 'UTF-8') + '",';

authHeader += 'oauth_signature_method="HMAC-SHA256",';

authHeader += 'oauth_timestamp="' + oauthTimestamp + '",';

authHeader += 'oauth_token="' + oauthToken + '",';

authHeader += 'oauth_version="1.0"';

return authHeader;

} 私有静态字符串generateNonce() {

return EncodingUtil.convertToHex(Crypto.generateAesKey(128)).substring(0, 32);

}私有静态字符串constructSignatureBaseString(字符串方法,字符串url,字符串oauthNonce,字符串oauthTimestamp,字符串oauthToken,字符串consumerKey,字符串领域){

method = method.toUpperCase();

url = EncodingUtil.urlEncode(url, 'UTF-8');    String signatureParams = 'oauth_consumer_key=' + consumerKey +

  '&oauth_nonce=' + oauthNonce +

  '&oauth_signature_method=HMAC-SHA256' +

  '&oauth_timestamp=' + oauthTimestamp +

  '&oauth_token=' + oauthToken +

  '&oauth_version=1.0';    if (!String.isBlank(realm)) {

  signatureParams += '&realm=' + realm;

}    return method + '&' + url + '&' + EncodingUtil.urlEncode(signatureParams, 'UTF-8');

}

© www.soinside.com 2019 - 2024. All rights reserved.