在 Java 中实现 OAuth

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2964392/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 23:43:35  来源:igfitidea点击:

Implement OAuth in Java

javatwitteroauth

提问by f4lco

I made an an attempt to implement OAuth for my programming idea in Java, but I failed miserably. I don't know why, but my code doesn't work. Every time I run my program, an IOException is thrown with the reason "java.io.IOException: Server returned HTTP response code: 401" (401 means Unauthorized). I had a close look at the docs, but I really don't understand why it doesn't work. My OAuth provider I wanted to use is twitter, where I've registered my app, too.
Thanks in advance
phineas

我曾尝试用 Java 为我的编程想法实现 OAuth,但我失败了。我不知道为什么,但我的代码不起作用。每次运行我的程序时,都会抛出一个 IOException,原因是“java.io.IOException:服务器返回 HTTP 响应代码:401”(401 表示未经授权)。我仔细查看了文档,但我真的不明白为什么它不起作用。我想使用的 OAuth 提供程序是 twitter,我也在那里注册了我的应用程序。
提前
致谢


OAuth docs
Twitter API wiki
Class Base64Coder


OAuth 文档
Twitter API wiki
类 Base64Coder

import java.io.InputStreamReader;
import java.io.BufferedReader; 
import java.io.OutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLEncoder;
import java.net.URLConnection;
import java.net.MalformedURLException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeyException;

public class Request {
    public static String read(String url) {
        StringBuffer buffer = new StringBuffer();
        try {
        /** 
         * get the time - note: value below zero 
         * the millisecond value is used for oauth_nonce later on
         */
            int millis = (int) System.currentTimeMillis() * -1;
            int time = (int) millis / 1000;

            /**
             * Listing of all parameters necessary to retrieve a token
             * (sorted lexicographically as demanded)
             */
             String[][] data = {
                {"oauth_callback", "SOME_URL"},
                {"oauth_consumer_key", "MY_CONSUMER_KEY"},
                {"oauth_nonce",  String.valueOf(millis)},
                {"oauth_signature", ""},
                {"oauth_signature_method", "HMAC-SHA1"},
                {"oauth_timestamp", String.valueOf(time)},
                {"oauth_version", "1.0"}
            };

            /**
             * Generation of the signature base string
             */
            String signature_base_string = 
                "POST&"+URLEncoder.encode(url, "UTF-8")+"&";
            for(int i = 0; i < data.length; i++) {
                // ignore the empty oauth_signature field
                if(i != 3) {
                signature_base_string +=
                    URLEncoder.encode(data[i][0], "UTF-8") + "%3D" +
                    URLEncoder.encode(data[i][1], "UTF-8") + "%26";
                }
            }
            // cut the last appended %26 
            signature_base_string = signature_base_string.substring(0,
                signature_base_string.length()-3);

            /**
             * Sign the request
             */
            Mac m = Mac.getInstance("HmacSHA1");
            m.init(new SecretKeySpec("CONSUMER_SECRET".getBytes(), "HmacSHA1"));
            m.update(signature_base_string.getBytes());
            byte[] res = m.doFinal();
            String sig = String.valueOf(Base64Coder.encode(res));
            data[3][1] = sig;

           /**
            * Create the header for the request
            */
           String header = "OAuth ";
           for(String[] item : data) {
                header += item[0]+"=\""+item[1]+"\", ";
           }
           // cut off last appended comma
           header = header.substring(0, header.length()-2);

           System.out.println("Signature Base String: "+signature_base_string);
           System.out.println("Authorization Header: "+header);
           System.out.println("Signature: "+sig);

           String charset = "UTF-8";
           URLConnection connection = new URL(url).openConnection();
           connection.setDoInput(true);
           connection.setDoOutput(true);
           connection.setRequestProperty("Accept-Charset", charset);
           connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=" + charset);
           connection.setRequestProperty("Authorization", header);
           connection.setRequestProperty("User-Agent", "XXXX");
           OutputStream output = connection.getOutputStream();
           output.write(header.getBytes(charset));

           BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));

           String read;
           while((read = reader.readLine()) != null) {
               buffer.append(read);
           }
        }
        catch(Exception e) {
            e.printStackTrace();
        }

       return buffer.toString();
   }

   public static void main(String[] args) {
       System.out.println(Request.read("http://api.twitter.com/oauth/request_token"));
   }
}

采纳答案by Sheng Chien

Try remove the parameter "oauth_callback". It worked for me. The application I was working on is a web application.

尝试删除参数“oauth_callback”。它对我有用。我正在开发的应用程序是一个 Web 应用程序。

回答by Joel Bodega

I know this is an old question but it seems it takes a bit of digging around to finally get a proper answer. This seems to be one of the top links in google too. Pages at dev.twitter.com lead to almost nowhere also. So here it goes. Look herefor a code that properly handles it. It uses HttpCore, but it can be achieved with standard libraries.

我知道这是一个老问题,但似乎需要进行一些挖掘才能最终得到正确的答案。这似乎也是谷歌的顶级链接之一。dev.twitter.com 上的页面也几乎无处可去。所以就到这里了。在此处查找正确处理它的代码。它使用HttpCore,但可以使用标准库来实现。

Making the story short.

使故事简短。

  1. URLEncoder library in 1.6 (most likely then in Android too) doesn't conform to the RFC standard Twitter API requires. The code here seems to be countering the problem partially. Take a look at the source I linked to find the method handling it appropriately.
  2. While creating the base string parameter you are encoding each key value pair. It conforms to the guide at the twitter page. It is easier to append the encoded url and the whole created parameter string (key/value pairs)
  3. While creating the signature you are creating it against a keyspec object with the CONSUMER_KEY appended with "&" sign in request token method of the Twitter API.
  4. As far as for this code. For request token it doesn't seem to be important at all.

    connection.setRequestProperty("Accept-Charset", charset);  
    connection.setRequestProperty("Content-Type", "application/x-www-formurlencoded;charset="+ charset);  
    connection.setRequestProperty("User-Agent", "XXXX");  
    

    This one though, does connection.setRequestMethod(method);

  1. 1.6 中的 URLEncoder 库(最有可能在 Android 中也是如此)不符合 Twitter API 要求的 RFC 标准。这里的代码似乎部分地解决了这个问题。看看我链接的来源,找到适当处理它的方法。
  2. 在创建基本字符串参数时,您正在对每个键值对进行编码。它符合 Twitter 页面上的指南。附加编码的 url 和整个创建的参数字符串(键/值对)更容易
  3. 在创建签名时,您是针对 keyspec 对象创建它的,其中 CONSUMER_KEY 附加了“&” Twitter API 的登录请求令牌方法。
  4. 至于这个代码。对于请求令牌,它似乎根本不重要。

    connection.setRequestProperty("Accept-Charset", charset);  
    connection.setRequestProperty("Content-Type", "application/x-www-formurlencoded;charset="+ charset);  
    connection.setRequestProperty("User-Agent", "XXXX");  
    

    这个虽然,确实 connection.setRequestMethod(method);