W3BGUY W3BGUY - 1 month ago 10
Java Question

Issues Connecting to NetSuite Via TBA

We were in progress of converting our SSO integration to TBA (vs a username and password). Since the recent 2016.2 upgrade the java code has completely failed to work (note, this did work before the upgrade). The current issue is that we receive either a 403 or a bad timestamp error. Working with NetSutie, I'm not seeing any correlation to when these change.

I'm far from a Java expert, so I'm hoping someone here can assist and tell me what is wrong with this code (I was able to get it working using the NetSuite PHP toolkit, though).

Below is the basic code that I'm trying (most was received from our Java developer, whom I'm trying to fix this for), slightly tweaked by myself using ideas from NetSuite.

public class Main{
public static void main(String[]args){
String compID="compID";
String consumerKey="consumerKey";
String consumerSecret="consumerSecret";
String tokenId="tokenId";
String tokenSecret="tokenSecret";
String restletURL="https://rest.sandbox.netsuite.com/app/site/hosting/restlet.nl?script=123&deploy=2";

Token token=new Token(tokenId,tokenSecret);
String nonce=RandomStringUtils.randomNumeric(9);
long ms=new java.util.Date().getTime();
ms=(long)Math.floor(ms/1000);
String time=ms+"";

String baseString=compID+"&"+consumerKey+"&"+tokenId+"&"+nonce+"&"+time;
String key=consumerSecret+"&"+tokenSecret;
byte[] bytes=key.getBytes();
SecretKeySpec secretkey=new SecretKeySpec(bytes,"HmacSHA1");
Mac keymac=null;
try{
keymac=Mac.getInstance("HmacSHA1");
keymac.init(secretkey);
}catch(NoSuchAlgorithmException e){
e.printStackTrace();
}catch(InvalidKeyException e){
e.printStackTrace();
}
byte[] hash=keymac.doFinal(baseString.getBytes());
String result=new String(Base64.encodeBase64(hash,false));
String oauth_timestamp = Instant.now().toEpochMilli()+"";

String headerAuthorization="OAuth realm=\""+compID+"\", oauth_consumer_key=\""+consumerKey+"\", oauth_token=\""+token+"\", oauth_nonce=\""+nonce+"\", oauth_timestamp=\""+oauth_timestamp+"\", oauth_signature_method=\"HMAC-SHA1\", oauth_version=\"1.0\", oauth_signature=\""+result+"\"";

System.out.println(headerAuthorization);

HttpClient httpclient=HttpClientBuilder.create().build();

HttpGet request=new HttpGet(restletURL);
request.setHeader(HttpHeaders.AUTHORIZATION,headerAuthorization);

String response="";
try{
//Handle the response from NetSuite
ResponseHandler<String> responseHandler=new ResponseHandler<String>(){
@Override
public String handleResponse(final HttpResponse response)throws ClientProtocolException,IOException{
int status=response.getStatusLine().getStatusCode();

//If the call is successful
if(status>=200 && status<300){
HttpEntity entity=response.getEntity();
return entity!=null?EntityUtils.toString(entity):null;
}else{
throw new ClientProtocolException("Unexpected response status: "+status);
}
}
};
response=httpclient.execute(request,responseHandler);
System.out.println(response);
}catch(Exception e){
e.printStackTrace();
}
}
}

Answer

We finally got this working. Turned out that in NetSuite changed the way their API works (according to what they told me, at least). To get this running, Scribe 1.3.7 is needed (Maven Repo). Then you need two Java files described below:

DummyService.java

import org.scribe.builder.api.DefaultApi10a;
import org.scribe.model.Token;

public class DummyService extends DefaultApi10a{
  @Override
  public String getAccessTokenEndpoint() {
    // TODO Auto-generated method stub
    return null;
  }
  @Override
  public String getAuthorizationUrl(Token arg0) {
    // TODO Auto-generated method stub
    return null;
  }
  @Override
  public String getRequestTokenEndpoint() {
    // TODO Auto-generated method stub
    return null;
  }
}

TokenAuth.java

import org.scribe.builder.ServiceBuilder;
import org.scribe.model.OAuthRequest;
import org.scribe.model.Response;
import org.scribe.model.SignatureType;
import org.scribe.model.Token;
import org.scribe.model.Verb;
import org.scribe.oauth.OAuthService;

public class TokenAuth{
  private static final String TOKEN_ID="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
  private static final String TOKEN_SECRET="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
  private static final String CONSUMER_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
  private static final String CONSUMER_SECRET="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
  private static final String REALM="123456";
  private static final String CONTENT_TYPE="content-type";
  private static final String APP_JSON="application/json";

  private static final String REST_URL="https://rest.sandbox.netsuite.com/app/site/hosting/restlet.nl?script=xxx&deploy=x";
  private static final String JSON_PAYLOD="{}";

  private static OAuthService service=getService();
  private static Token accessToken=getToken();
  public static void main(String[]args){
    Response responseGet=callWithHttpGet();
    System.out.println(responseGet.getBody());  
    Response responsePost=callWithHttpPost();
    System.out.println(responsePost.getBody());
  }
  private static Response callWithHttpGet(){
    OAuthRequest request=new OAuthRequest(Verb.GET,REST_URL);
    request.setRealm(REALM);
    service.signRequest(accessToken,request);
    return request.send();
  }
  private static Response callWithHttpPost(){
    OAuthRequest request=new OAuthRequest(Verb.POST,REST_URL);
    request.setRealm(REALM);
    request.addHeader(CONTENT_TYPE,APP_JSON);
    request.addPayload(JSON_PAYLOD);
    service.signRequest(accessToken,request);
    return request.send();
  }
  private static Token getToken(){
    return new Token(TOKEN_ID, TOKEN_SECRET);
  }
  private static OAuthService getService(){
    return new ServiceBuilder().provider(DummyService.class).apiKey(CONSUMER_KEY).apiSecret(CONSUMER_SECRET).signatureType(SignatureType.Header).build();
  }
}

*Note: This data has now been updated (except the requirement of Scribe 1.3.7) in the SuiteAnswers article, as well (42167 - Java > RESTlet Authentication using Token (Token-Based Authentication))