在Java或Groovy中获取有效的oauth_signature所需的最少代码是什么?

19

我正在测试一个Rest OAuth实现。我的测试工具将发送HTTP请求,但我需要准备Authorization头。

我需要什么:我想要一个有效的Authorization头

我拥有什么:除了oauth_signature之外的所有头信息,我还拥有两个秘钥,即token_secret和consumer_secret,以及access_token。所以问题实际上就是如何对此请求进行签名。我该怎么做?

总结:我只需要填写RESTful服务的Authorization头中的oauth_signature部分。 我该怎么做?

基本上:

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;

我的问题是我没有oauth_signature部分,也不知道如何获取它。请帮忙解决?

3个回答

24

这里是我为Flickr OAuth编写的代码。注意:我参考了SignPost的一些逻辑。生成签名真的非常棘手...好的。这只是一个生成“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));
            }
        }
    }

}

这是我在两天的挣扎中的救星。谢谢。 - Luiz Feijão Veronesi
太棒了!帮助了我。 - Rohit Mandiwal
1
有时服务器会将回调方法视为额外参数并抛出签名不匹配的错误。删除回调对我有用。我正在测试WordPress OAuth1.0a插件。 - Prakashm88
1
实际上,关于你所问的"不要问我为什么需要添加一个&符号"的答案可以在这里找到:https://oauth1.wp-api.org/docs/basics/Signing.html :秘密由客户端秘钥和令牌秘钥组成。当你没有令牌秘钥时,你需要client_secret&,而当你拥有两者时,你需要client_secret&token_secret。 - Myoch

20

Twitter oAuth:如果有人需要生成oAuth签名和标头以连接到Twitter API,这里是代码。这需要至少Java 8NO 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) + "";
    }

}

使用Spring RestTemplate获取Twitter用户详细信息的示例用法:
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());

完整的代码和可工作演示,请前往Twitter-Play


3
太棒了。这正是我在寻找的。 - ahsan_cse2004
1
谢谢你,你救了我的一整天。为什么不写一篇博客文章,让更多的人受益呢? - mahfuj asif
1
对我有用。非常感谢 :) - Matt Welke
1
我已经尽力在我的代码库中给你信用(https://imgur.com/a/itU5eHR)。你应该考虑将其放入一个仓库并连接到类似JitPack(https://jitpack.io/)或GitHub Packages的东西,以便人们可以将其用作工件。这太棒了。 - Matt Welke
1
@MattWelke,我很高兴这个答案对你有帮助。我同意你或任何人使用该代码,并接受任何许可证。 - Smile
显示剩余10条评论

1
对于使用JIRA的OAuth 1.0a。我能够从上面的Twitter示例中受益,除了在JIRA的情况下,我必须使用RSA-SHA1而不是HMACSHA1对生成的SignatureBaseString进行数字签名。

以下是使用RSA-SHA1进行签名的片段。这是使用Java 8的Groovy,没有使用任何外部库:

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);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接