Absolute minimum code to get a valid oauth_signature populated in Java or Groovy?
Asked Answered
G

3

20

SO I am testing a Rest OAuth implementation. My testing tool will send the HTTP Request, but I need to prepare the Authorization header.

What I need: I want a valid Authorization Header

What I have: All the headers except the oauth_signature I also have the 2 secrets, the token_secret and the consumer_secret. I also posses the access_token. So It really boils down to, having to sign this request. How do I do that?

Summary: I simply need to populate the oauth_signature portion of the Authorization header for a RESTful service. How do I do it?

Basically:

oAuthHeader="OAuth";
oAuthHeader=oAuthHeader+" oauth_signature_method="+oauth_signature_method;
oAuthHeader=oAuthHeader+",oauth_version="+oauth_version;
oAuthHeader=oAuthHeader+",oauth_nonce="+oauth_nonce;
oAuthHeader=oAuthHeader+",oauth_timestamp="+oauth_timestamp;
oAuthHeader=oAuthHeader+",oauth_consumer_key="+oauth_consumer_key;
oAuthHeader=oAuthHeader+",oauth_token="+oauth_token;
oAuthHeader=oAuthHeader+",oauth_signature="+**oauth_signature**;

Authorization = oAuthHeader;

My problem is I do not have the oauth_signature portion of it. And I do not know how to get it. Help please?

Gregorygregrory answered 20/9, 2010 at 23:29 Comment(0)
L
24

Here is my code for Flickr OAuth. NOTICE: I REFERED some logic from SignPost. It is really very tricky to generate it signature.... OK. This is just an example for generate the "oauth_signature"

package oauthflickr;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIUtils;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;

/**
 * a simple program to get flickr token and token secret.
 * 
 * @author Mark Zang
 * 
 */
public class OAuthForFlickr {

    private static String key = "_________________________";
    private static String secret = "___________";

    private static final String HMAC_SHA1 = "HmacSHA1";

    private static final String ENC = "UTF-8";

    private static Base64 base64 = new Base64();

    /**
     * 
     * @param url
     *            the url for "request_token" URLEncoded.
     * @param params
     *            parameters string, URLEncoded.
     * @return
     * @throws UnsupportedEncodingException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     */
    private static String getSignature(String url, String params)
            throws UnsupportedEncodingException, NoSuchAlgorithmException,
            InvalidKeyException {
        /**
         * base has three parts, they are connected by "&": 1) protocol 2) URL
         * (need to be URLEncoded) 3) Parameter List (need to be URLEncoded).
         */
        StringBuilder base = new StringBuilder();
        base.append("GET&");
        base.append(url);
        base.append("&");
        base.append(params);
        System.out.println("Stirng for oauth_signature generation:" + base);
        // yea, don't ask me why, it is needed to append a "&" to the end of
        // secret key.
        byte[] keyBytes = (secret + "&").getBytes(ENC);

        SecretKey key = new SecretKeySpec(keyBytes, HMAC_SHA1);

        Mac mac = Mac.getInstance(HMAC_SHA1);
        mac.init(key);

        // encode it, base64 it, change it to string and return.
        return new String(base64.encode(mac.doFinal(base.toString().getBytes(
                ENC))), ENC).trim();
    }

    /**
     * @param args
     * @throws IOException
     * @throws ClientProtocolException
     * @throws URISyntaxException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     */
    public static void main(String[] args) throws ClientProtocolException,
            IOException, URISyntaxException, InvalidKeyException,
            NoSuchAlgorithmException {

        HttpClient httpclient = new DefaultHttpClient();
        List<NameValuePair> qparams = new ArrayList<NameValuePair>();
        // These params should ordered in key
        qparams.add(new BasicNameValuePair("oauth_callback", "oob"));
        qparams.add(new BasicNameValuePair("oauth_consumer_key", key));
        qparams.add(new BasicNameValuePair("oauth_nonce", ""
                + (int) (Math.random() * 100000000)));
        qparams.add(new BasicNameValuePair("oauth_signature_method",
                "HMAC-SHA1"));
        qparams.add(new BasicNameValuePair("oauth_timestamp", ""
                + (System.currentTimeMillis() / 1000)));
        qparams.add(new BasicNameValuePair("oauth_version", "1.0"));

        // generate the oauth_signature
        String signature = getSignature(URLEncoder.encode(
                "http://www.flickr.com/services/oauth/request_token", ENC),
                URLEncoder.encode(URLEncodedUtils.format(qparams, ENC), ENC));

        // add it to params list
        qparams.add(new BasicNameValuePair("oauth_signature", signature));

        // generate URI which lead to access_token and token_secret.
        URI uri = URIUtils.createURI("http", "www.flickr.com", -1,
                "/services/oauth/request_token",
                URLEncodedUtils.format(qparams, ENC), null);

        System.out.println("Get Token and Token Secrect from:"
                + uri.toString());

        HttpGet httpget = new HttpGet(uri);
        // output the response content.
        System.out.println("oken and Token Secrect:");

        HttpResponse response = httpclient.execute(httpget);
        HttpEntity entity = response.getEntity();
        if (entity != null) {
            InputStream instream = entity.getContent();
            int len;
            byte[] tmp = new byte[2048];
            while ((len = instream.read(tmp)) != -1) {
                System.out.println(new String(tmp, 0, len, ENC));
            }
        }
    }

}
Lettering answered 20/9, 2010 at 23:29 Comment(4)
This was my salvation for a 2-day struggle. Thank you.Home
fantastic! helped meSivas
Sometime the server treats the callback method as an extra param and throws a Signature mismatch error. Removing the callback worked for me. I was testing the WordPress OAuth1.0a Plugin.Slue
Nope, in fact, the answer to your "don't ask me why it needs appending a &" can be found here: oauth1.wp-api.org/docs/basics/Signing.html : the secret is made of the client secret and the token secret. When you don't have a token secret, then you need the client_secret&, and when you have both, you need client_secret&token_secret.Streetman
B
21

For Twitter oAuth: In case anyone needs to generate oAuth signature and header to connect to Twitter API, here is the code. This requires minimum of Java 8 and NO 3rd party library.

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

/**
 * Class to generate Oauth 1.0a header for Twitter
 *
 */
public class TwitterOauthHeaderGenerator {

    private String consumerKey;
    private String consumerSecret;
    private String signatureMethod;
    private String token;
    private String tokenSecret;
    private String version;

    public TwitterOauthHeaderGenerator(String consumerKey, String consumerSecret, String token, String tokenSecret) {
        this.consumerKey = consumerKey;
        this.consumerSecret = consumerSecret;
        this.token = token;
        this.tokenSecret = tokenSecret;
        this.signatureMethod = "HMAC-SHA1";
        this.version = "1.0";
    }

    private static final String oauth_consumer_key = "oauth_consumer_key";
    private static final String oauth_token = "oauth_token";
    private static final String oauth_signature_method = "oauth_signature_method";
    private static final String oauth_timestamp = "oauth_timestamp";
    private static final String oauth_nonce = "oauth_nonce";
    private static final String oauth_version = "oauth_version";
    private static final String oauth_signature = "oauth_signature";
    private static final String HMAC_SHA1 = "HmacSHA1";

    /**
     * Generates oAuth 1.0a header which can be passed as Authorization header
     * 
     * @param httpMethod
     * @param url
     * @param requestParams
     * @return
     */
    public String generateHeader(String httpMethod, String url, Map<String, String> requestParams) {
        StringBuilder base = new StringBuilder();
        String nonce = getNonce();
        String timestamp = getTimestamp();
        String baseSignatureString = generateSignatureBaseString(httpMethod, url, requestParams, nonce, timestamp);
        String signature = encryptUsingHmacSHA1(baseSignatureString);
        base.append("OAuth ");
        append(base, oauth_consumer_key, consumerKey);
        append(base, oauth_token, token);
        append(base, oauth_signature_method, signatureMethod);
        append(base, oauth_timestamp, timestamp);
        append(base, oauth_nonce, nonce);
        append(base, oauth_version, version);
        append(base, oauth_signature, signature);
        base.deleteCharAt(base.length() - 1);
        System.out.println("header : " + base.toString());
        return base.toString();
    }

    /**
     * Generate base string to generate the oauth_signature
     * 
     * @param httpMethod
     * @param url
     * @param requestParams
     * @return
     */
    private String generateSignatureBaseString(String httpMethod, String url, Map<String, String> requestParams, String nonce, String timestamp) {
        Map<String, String> params = new HashMap<>();
        requestParams.entrySet().forEach(entry -> {
            put(params, entry.getKey(), entry.getValue());
        });
        put(params, oauth_consumer_key, consumerKey);
        put(params, oauth_nonce, nonce);
        put(params, oauth_signature_method, signatureMethod);
        put(params, oauth_timestamp, timestamp);
        put(params, oauth_token, token);
        put(params, oauth_version, version);
        Map<String, String> sortedParams = params.entrySet().stream().sorted(Map.Entry.comparingByKey())
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (oldValue, newValue) -> oldValue, LinkedHashMap::new));
        StringBuilder base = new StringBuilder();
        sortedParams.entrySet().forEach(entry -> {
            base.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
        });
        base.deleteCharAt(base.length() - 1);
        String baseString = httpMethod.toUpperCase() + "&" + encode(url) + "&" + encode(base.toString());
        return baseString;
    }

    private String encryptUsingHmacSHA1(String input) {
        String secret = new StringBuilder().append(encode(consumerSecret)).append("&").append(encode(tokenSecret)).toString();
        byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
        SecretKey key = new SecretKeySpec(keyBytes, HMAC_SHA1);
        Mac mac;
        try {
            mac = Mac.getInstance(HMAC_SHA1);
            mac.init(key);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
            return null;
        }
        byte[] signatureBytes = mac.doFinal(input.getBytes(StandardCharsets.UTF_8));
        return new String(Base64.getEncoder().encode(signatureBytes));
    }

    /**
     * Percentage encode String as per RFC 3986, Section 2.1
     * 
     * @param value
     * @return
     */
    private String encode(String value) {
        String encoded = "";
        try {
            encoded = URLEncoder.encode(value, "UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
        }
        String sb = "";
        char focus;
        for (int i = 0; i < encoded.length(); i++) {
            focus = encoded.charAt(i);
            if (focus == '*') {
                sb += "%2A";
            } else if (focus == '+') {
                sb += "%20";
            } else if (focus == '%' && i + 1 < encoded.length() && encoded.charAt(i + 1) == '7' && encoded.charAt(i + 2) == 'E') {
                sb += '~';
                i += 2;
            } else {
                sb += focus;
            }
        }
        return sb.toString();
    }

    private void put(Map<String, String> map, String key, String value) {
        map.put(encode(key), encode(value));
    }

    private void append(StringBuilder builder, String key, String value) {
        builder.append(encode(key)).append("=\"").append(encode(value)).append("\",");
    }

    private String getNonce() {
        int leftLimit = 48; // numeral '0'
        int rightLimit = 122; // letter 'z'
        int targetStringLength = 10;
        Random random = new Random();

        String generatedString = random.ints(leftLimit, rightLimit + 1).filter(i -> (i <= 57 || i >= 65) && (i <= 90 || i >= 97)).limit(targetStringLength)
                .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString();
        return generatedString;

    }

    private String getTimestamp() {
        return Math.round((new Date()).getTime() / 1000.0) + "";
    }

}

Sample usage using Spring RestTemplate to get details of a Twitter user:

TwitterOauthHeaderGenerator generator = new TwitterOauthHeaderGenerator("consumerKey", "consumerSecret", "token", "tokenSecret");
Map<String, String> requestParams = new HashMap<>();
requestParams.put("usernames", "some_handle");
String header = generator.generateHeader("GET", "https://api.twitter.com/labs/1/users", requestParams);
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", header);
HttpEntity<String> httpEntity = new HttpEntity<String>("body", headers);
ResponseEntity<SomeModel> someModelEntity= restTemplate.exchange("https://api.twitter.com/labs/1/users?usernames=some_handle",
                HttpMethod.GET, httpEntity, SomeModel.class);
System.out.println(someModelEntity.getBody());

Complete code and working demo available at Twitter-Play

Badgett answered 16/1, 2020 at 8:41 Comment(15)
Awesome. This was exactly what I was looking forKannan
Thanks man you saved my whole day. Why don't you write a blog post about this so that more people can be benefitedTownes
Any idea how to do this with NodeJS?Philpot
@MohamedDaher I have not tried with NodeJS but you can give it a try using steps mentioned at twitter-guide and ask a question here if you face any issueBadgett
I am trying this. I now know i have to sort the string but i am missing what would the token secret because i am not using it in postman #64767901Philpot
@Badgett I followed the same for our application's LTI URL. But I am getting signature mismatch error.Calpe
@Calpe you can check complete code at github.com/smilep/twitter-play and compare with your codeBadgett
Worked for me. tyvm :)Descent
Did my best to give you credit in my own code base (imgur.com/a/itU5eHR). You should consider putting that in a repo and hooking it up to something like JitPack (jitpack.io) or GitHub Packages so people can use it as an artifact. It's brilliant.Descent
Ended up making a public repo with that code (github.com/mattwelke/packt-book-bot). I used Apache 2.0 License in my code. Let me know if you have an issue with this. Or, could you consider editing your answer to indicate what license you'd like to use or if you'd like to donate that code sample to the public domain?Descent
@MattWelke I am glad this answer helped you. I am fine with you or anyone else using the code with any license.Badgett
Seems not to work when you add a realmWilkinson
@G.Ciardini I didn't understand your comment. Could you please ask a new question with your code and error and share the question link?Badgett
@Badgett this work as everything else for me for users/me request but for follow is not working is give me UnotorizedHerpetology
Thank you, it took allot of searching and wasted time before I found this code. It appears that very few external libraries are working any more and of the ones that do, they are not production ready code. This is a very nice solution.Keller
U
1

For OAuth 1.0a with JIRA. I was able to benefit from the Twitter example above, except that in case of JIRA I had to digitally sign the generatedSignatureBaseString with RSA-SHA1 instead of HMACSHA1.

Below are the snippets to do the signing with RSA-SHA1. This is in Groovy and uses Java 8 without any external libraries:

import java.security.SignatureException;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.Signature;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;


//-------- generate RSA-SHA1 signature from request data
def encryptUsingRSASHA1(String data, String key) throws 
java.security.SignatureException
{
String result
try {
    // get an rsa_sha1 key from the raw key bytes
    //SHA1withRSA
    Signature signature = Signature.getInstance("SHA1withRSA");
    signature.initSign(getPrivateKey(key));
    signature.update(data.getBytes("UTF-8"));
    byte[] rawRSAsigned = signature.sign();
    result= rawRSAsigned.encodeBase64()
    
} catch (Exception e) {
    throw new SignatureException("Failed to generate Signature : " + 
e.getMessage());
}
return result
} 

//get PrivateKey from key string
def getPrivateKey(String privateKey) throws NoSuchAlgorithmException, 
InvalidKeySpecException {
    String privateKeyPEM = privateKey.replace("-----BEGIN PRIVATE KEY-----\n", 
"").replace("-----END PRIVATE KEY-----", "").replaceAll("\n","");

    byte[] privateKeyBytes = privateKeyPEM.decodeBase64();
    PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
    KeyFactory kf = KeyFactory.getInstance("RSA");
    return kf.generatePrivate(keySpec);
}
Unbelief answered 9/8, 2021 at 12:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.