在本篇文章中,我将描述Java中的TBA/OAuth集成,因为我注意到很多人在网上问有关此问题的问题。
前言
在我当前的项目中,我们决定将我们的订单流程与NetSuite API(简称NS)集成。
https://7930879-sb1.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=396&deploy=2
在客户在我们的系统中完成订购流程后,我们需要在NS中创建一个自定义实体。然而,我们需要集成的API使用的安全协议不受现代Spring版本的支持。为了节省签署标头的时间,我寻找了一个可以帮助我的Java库。
然而,这个库最终消耗了我比我预期的时间更多的时间(6个小时而不是2个小时)...
Library
我选择了OAuth Signpost,因为它提供了我所需的一切。然而,它实现的一个设计点并不明显。今天,我想强调一下这一点,以便在类似的情况下节省你的时间。
这是我使用当前库获得签名请求的算法:
- 使用从NS获取的CONSUMER_KEY和CONSUMER_SECRET创建一个消费者(用于签署我们的请求)
OAuthConsumer consumer =
new CommonsHttpOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET);
注意:CommonsHttpOAuthConsumer构造函数中创建的默认OAuthMessageSigner是HmacSha1MessageSigner,但我们需要HmacSha256MessageSigner。
- 设置用于消息签名的OAuth令牌和令牌密钥。
consumer.setTokenWithSecret(ACCESS_TOKEN, ACCESS_SECRET);
- 用HmacSha256MessageSigner替换HmacSha1MessageSigner。
HmacSha256MessageSigner messageSigner = new HmacSha256MessageSigner();
consumer.setMessageSigner(messageSigner);
- 创建我们将在POST请求期间使用的一组标头。
HttpParameters params = new HttpParameters();
params.put("oauth_timestamp", String.valueOf(System.currentTimeMillis() / 1000L));
params.put("oauth_signature_method", "HMAC-SHA256");
params.put("oauth_nonce", generateNonce());
params.put("oauth_version", "1.0");
params.put("realm", USER);
consumer.setAdditionalParameters(params);
- 创建并签署我们的请求。
HttpPost httpPost = new HttpPost(NETSUITE_BASE_URL);
httpPost.setEntity(requestEntity);
consumer.sign(httpPost);
- 通过httpClient执行HTTP调用(在我的情况下,客户端来自org.apache.http.impl.client包)。
httpClient.execute(httpPost);
问题
当一切准备就绪时,我开始测试并意识到NS返回了403错误代码,而不是我通过Postman收到的预期的200。
长话短说(记住,我想节省你的时间),问题的根本原因在我的第三步中:
HmacSha256MessageSigner messageSigner = new HmacSha256MessageSigner();
consumer.setMessageSigner(messageSigner);
当调用setMessageSigner时,新消息签名器只传递了CONSUMER_SECRET而没有传递ACCESS_SECRET,这对我来说不太合理,因为我已经在OAuthConsumer本身的级别上提供了ACCESS_SECRET(在内部,它直接将此键设置为默认消息签名器):
consumer.setTokenWithSecret(ACCESS_TOKEN, ACCESS_SECRET);
解决方案
为了使它正常工作,我稍微修改了算法:
OAuthConsumer consumer = new CommonsHttpOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET);
consumer.setTokenWithSecret(ACCESS_TOKEN, ACCESS_SECRET);
HmacSha256MessageSigner messageSigner = new HmacSha256MessageSigner();
messageSigner.setTokenSecret(ACCESS_SECRET); // important part, or change ordering
consumer.setMessageSigner(messageSigner);
consumer.setSigningStrategy(new AuthorizationHeaderSigningStrategy());
现在一切都像预期的那样工作了!
从库设计决策中汲取教训,使我们的软件更好
setTokenWithSecret()方法试图通过设置具有自己状态的逻辑独立组件的属性来简化客户端体验。因此,它引入了顺序要求:
- 创建一个
OAuthConsumer
。 - 设置所需的
MessageSigner
。 - 调用
setTokenWithSecret
方法。 为了避免这种情况,唯一需要做的就是使创建具有无效算法不变量的对象变得不可能:
new HmacSha256MessageSigner(String consumerSecret, String tokenSecret) //we shouldn't be able to create an object with not valid algorithm invariants
setTokenWithSecret()方法没有意义,应该删除,因为默认的MessageSigner是通过Consumer的构造函数创建的。用于正确运行签名器的参数应该传递到创建此签名器的位置,从而保持请求签名算法的有效不变量。
另一个设计问题是consumerSecret属性当前在OAuthConsumer和MessageSigner中重复,导致状态封装的破裂和独立对象的耦合(setTokenWithSecret()方法是一个很好的例子)。
结论
我要感谢OAuth Signpost贡献者,他们简化了想要快速集成OAuth授权的开发人员的生活。
通过示例描述不幸的设计决策是有趣的。
记住,错误可以帮助我们改进。不要害怕犯错误。只要不忘记分析它们。
GitHub Repo: https://github.com/AlexandrKostiuk/netsuite-java-integration
译自:https://medium.com/@olexandr.kostiuk/java-to-netsuite-api-integration-oauth-sha-256-ee4af8b772dd
评论(0)