NetSuite OAuth1.0中InvalidSignature问题

news2024/11/18 15:28:30

本周闭关写代码,用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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/687236.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【云原生丶Docker】虚拟化技术简介

什么是虚拟化技术&#xff1f; Docker 是一款基于容器虚拟化技术构建的软件&#xff0c;那到底什么虚拟化技术呢&#xff1f;在学习 Docker 之前&#xff0c;先简单了解下虚拟化技术。 虚拟化是云原生的实现基础&#xff0c;它能够帮助我们更加有效地利用物理计算机硬件。 虚…

clip-interrogator本地部署

clip-interrogator本地部署 1. 克隆源码2. 创建虚拟环境及配置3. 下载远程相关文件3.1 下载Salesforce相关文件失败3.2 下载cache相关文件失败3.3 解决库不完整问题 4. 测试代码4.1 脚本一4.2 脚本二4.3 测试run_cli.py文件4.4 测试run_gradio.py文件 源码链接&#xff1a;http…

d3dcompiler_47.dll缺失怎么办?d3dcompiler_47.dll缺失修复方法(详解)

d3dcompiler_47.dll在Windows操作系统和 DirectX应用程序中使用此 DLL 文件,包括游戏&#xff0c;图形处理软件&#xff0c;CAD软件和其他3D应用程序中。如果电脑提示“由于找不到d3dcompiler_47.dll&#xff0c;无法继续执行此代码”&#xff0c;“d3dcompiler_47.dll缺失”&a…

Docker卷与持久化数据

数据主要分为两类&#xff0c;持久化的与非持久化的。 两者都很重要&#xff0c;并且Docker均有对应的支持方式。每个Docker容器都有自己的非持久化存储。非持久化存储自动创建&#xff0c;从属于容器&#xff0c;生命周期与容器相同。这意味着删除容器也会删除全部非持久化数…

网络维护岗位个人求职简历

网络维护岗位个人求职简历1 姓名&#xff1a; 国籍&#xff1a;中国 目前所在地&#xff1a;广州民族&#xff1a;汉族 户口所在地&#xff1a;湖南身材&#xff1a;170cm64kg 婚姻状况&#xff1a;未婚年龄&#xff1a;28 求职意向及工作经历 应聘职位&#xff1a; 工作年限&a…

day14_面向对象的三大特征之一(继承)

继承的概述 Java是对现实社会的模拟&#xff0c;现实中有儿子可以继承父亲的财产&#xff0c;因此有了富二代的。 java中多个类中存在相同属性和行为时&#xff0c;将这些内容抽取到单独一个类中&#xff0c;那么多个类中无需再定义这些属性和行为&#xff0c;只需要和抽取出来…

【Java-SpringBoot+Vue+MySql】项目开发杂记

目录 1、关闭eslint 2、 新建路由 3、安装jQuery依赖-在Vue使用JQuery语法 4、MySQL———数据全部清除&#xff0c;自增归零 5、前后端数据传递——增加功能 6、使用element-ui获取当前表格中的数据——删除功能 1、关闭eslint 2、 新建路由 {path: /user,component: Lay…

LLM - Hugging Face 工程 BERT base model (uncased) 配置

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://blog.csdn.net/caroline_wendy/article/details/131400428 BERT是一个在大量英文数据上以自监督的方式预训练的变换器模型。这意味着它只是在原始文本上进行预训练&#xff0c;没有人以…

RabbitMQ实现延迟消息,RabbitMQ使用死信队列实现延迟消息,RabbitMQ延时队列插件

文章目录 一、什么是延迟消息1、队列的属性2、消息的属性3、什么是死信4、使用死信队列的缺点5、延时消息插件 二、JavaAPI利用死信队列实现RabbitMQ延迟消息1、代码实现2、基本流程 三、JavaAPI利用插件实现RabbitMQ延迟消息1、代码实现2、基本原理 四、Springboot利用死信队列…

5分钟自建可视化平台,在线拖拽组件也太方便了!

一、前言 我们在完成 C 端用户的业务需求时&#xff0c;发现大量电商推广页面的逻辑复杂度和交互程度都比较低&#xff0c;却要消耗大量的开发资源。同时在后台管理系统的开发过程中&#xff0c;我们也发现一些相同套路的页面在不同场景下需要重复开发&#xff0c;即使我们封装…

如何提升企业采购绩效?提高采购绩效的方法

采购是任何企业的一个重要方面&#xff0c;它在确保企业平稳高效运行方面发挥着重要作用。作为一名采购专业人员&#xff0c;你有责任确保你的企业以正确的质量、数量和价格获得所需的货物和服务。 本文将讨论一些最佳做法&#xff0c;这些做法可以帮助你提高采购专业人员的绩…

【032】C++高级开发之多态技术详解(虚函数最全讲解)

C的多态技术&#xff08;虚函数&#xff09;详解 引言一、多态的概念二、虚函数2.1、父类指针保存子类空间地址 带来的问题2.2、虚函数的定义2.3、虚函数的动态绑定机制2.4、重载、重定义、重写的区别 三、纯虚函数3.1、纯虚函数的定义方式3.2、纯虚函数的案例&#xff1a;饮品…

mvnd 安装和 idea配置mvnd

一、mvnd 安装 1、mvnd 下载地址 https://github.com/apache/maven-mvnd 2、安装 解压并配置环境变量到path 打开CMD终端&#xff0c;输入 mvnd -v 看到如下信息提示则安装成功 默认使用的是内置的maven&#xff0c;配置修改&#xff0c;兼容本地的maven 打开 Maven-mvn…

基于Java医院医患管理系统设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a; ✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精…

如何测试webservice接口

看过网上很多对Web Service的解释&#xff0c;有些文章写得通俗易懂&#xff0c;但是缺少深度解读&#xff1b;有的文章图文并茂&#xff0c;但是没有现成案例&#xff1b;有的文章非常详细&#xff0c;但是没有直观感受。 于是&#xff0c;我想从测试一个web service接口的角…

【复习《剑指Offer》6-12题】【每天40分钟,我们一起用50天刷完 (剑指Offer)】第七天 7/50

专注 效率 记忆 预习 笔记 复习 做题 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&#xff09;   文章字体风格&#xff1a; 红色文字表示&#…

围“桌”详谈|本周六直播,一起聊聊「发版」那些事儿!

&#x1f30d; 你想要深入了解 ShardingSphere 、与业内技术大咖「面对面」探讨数据库领域的前沿趋势吗&#xff1f;那就一定不能错过由 ShardingSphere 社区出品、SphereEx 公司赞助的全球软件工程师必看的直播节目——《对谈时刻》&#xff01;每期节目&#xff0c;我们都会邀…

VRRP缺省配置

一、VRRP缺省配置 表&#xff1a;VRRP参数缺省值 二、VRRP场景作用 表&#xff1a;VRRP场景作用 三、VRRP配置缺省参数汇总 1&#xff09;配置VRRP备份组最大数 缺省情况下&#xff0c;设备只支持配置256个VRRP备份组&#xff08;VRRP4和VRRP6备份组的总和&#xff09; 当需…

计算机原理二:磁盘和内存速度差多少?

前言 作为程序员&#xff0c;我们不深究硬件&#xff0c;无需深入了解硬件的各个电路信号等细节&#xff0c;只了解存储器的基本相关知识。 存储器的层次结构 寄存器 计算机的一种硬件组件&#xff0c;用于存储和快速访问处理器的数据&#xff0c;位于CPU内部的小型存储器单…

火拼折叠屏:国产手机的杀手锏还是遮羞布?

刚过去的618&#xff0c;手机市场一反常态。 过去&#xff0c;国产安卓旗舰上演“大跳水”&#xff0c;苹果价格坚挺&#xff1b;现在&#xff0c;安卓旗舰优惠力度小&#xff0c;苹果却大降价&#xff0c;iPhone 14 Pro在各平台的优惠力度达到上千元。 IDC中国研究经理郭天翔…