2013-04-23 64 views
1

我想讓Oauth使用Twitter Streaming API工作,但我得到了401未授權的錯誤。我發現了一個爲Oauth身份驗證提供代碼的博客,但我不得不調整它來編譯它。下面是我在我的主要方法至今:Twitter流API和Oauth拉標籤

 HttpWebRequest webRequest = null; 
     HttpWebResponse webResponse = null; 
     StreamReader responseStream = null; 

     String twitterUser = Properties.Settings.Default.TwitterAccount; 
     String twitterPassword = Properties.Settings.Default.TwitterPassword; 
     String twitterHashtags = Properties.Settings.Default.TwitterHashtags; 

     String twitterConsumerKey = Properties.Settings.Default.TwitterConsumerKey; 
     String twitterConsumerSecret = Properties.Settings.Default.TwitterConsumerSecret; 
     String twitterAccessToken = Properties.Settings.Default.TwitterAccessTokens; 
     String twitterAccessTokenSecret = Properties.Settings.Default.TwitterAccessTokenSecret; 

     Int32 wait = 250; 

     while (true) //Auto retry on error! 
     { 
      try 
      { 
       var postData = "track=" + HttpUtility.UrlEncode(twitterHashtags); 
       var requestContentBuffer = Encoding.UTF8.GetBytes(postData); 

       webRequest = (HttpWebRequest)WebRequest.Create(new Uri("https://stream.twitter.com/1.1/statuses/filter.json")); 
       webRequest.Method = "POST"; 
       webRequest.ContentLength = requestContentBuffer.Length; 
       webRequest.ContentType = "application/x-www-form-urlencoded"; 

       webRequest.UserAgent = "OAuthTwitterStream"; 
       webRequest.Headers["Accept-Encoding"] = "deflate, gzip"; 
       webRequest.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; 

       OAuthRequestAuthorizer _requestAuthorizer = new OAuthRequestAuthorizer(twitterConsumerKey, twitterConsumerSecret, twitterAccessToken, twitterAccessTokenSecret); 

       _requestAuthorizer.Authorize(webRequest, postData); 

       using (var dataStream = webRequest.GetRequestStream()) 
       { 
        dataStream.Write(requestContentBuffer, 0, requestContentBuffer.Length); 
       } 

       webResponse = (HttpWebResponse)webRequest.GetResponse(); 
       responseStream = new StreamReader(webResponse.GetResponseStream()); 

       while (true) 
       { 
        string jsonText = responseStream.ReadLine(); 

        dynamic o = JsonConvert.DeserializeObject(jsonText); 

        if (o.disconnect == null) //Sometimes you get a disconnect response, we don't want our clients to see this. 
        { 
         //do something with tweet object 
        } 
       } 

      } 
      catch (WebException ee) 
      { 
       if (ee.Status == WebExceptionStatus.ProtocolError) 
       { 
        if (wait < 10000) 
         wait = 10000; 
        else 
        { 
         if (wait < 240000) 
          wait = wait * 2; 
        } 
       } 

       Console.Error.WriteLine(ee.Message); 

      } 
      catch (Exception ee) 
      { 
       Console.Error.WriteLine(ee.Message); 
      } 
      finally 
      { 
       if (webRequest != null) 
       { 
        webRequest.Abort(); 
       } 
       if (responseStream != null) 
       { 
        responseStream.Close(); 
        responseStream = null; 
       } 

       if (webResponse != null) 
       { 
        webResponse.Close(); 
        webResponse = null; 
       } 
       Console.WriteLine("Waiting: " + wait); 
       Thread.Sleep(wait); 
      } 
     } 

而這裏的Oauth類:

using System; 
using System.Security.Cryptography; 
using System.Collections.Generic; 
using System.Text; 
using System.Web; 

namespace OAuth 
{ 
    public class OAuthBase 
    { 

     /// <summary> 
     /// Provides a predefined set of algorithms that are supported officially by the protocol 
     /// </summary> 
     public enum SignatureTypes 
     { 
      HMACSHA1, 
      PLAINTEXT, 
      RSASHA1 
     } 

     /// <summary> 
     /// Provides an internal structure to sort the query parameter 
     /// </summary> 
     protected class QueryParameter 
     { 
      private string name = null; 
      private string value = null; 

      public QueryParameter(string name, string value) 
      { 
       this.name = name; 
       this.value = value; 
      } 

      public string Name 
      { 
       get { return name; } 
      } 

      public string Value 
      { 
       get { return value; } 
      } 
     } 

     /// <summary> 
     /// Comparer class used to perform the sorting of the query parameters 
     /// </summary> 
     protected class QueryParameterComparer : IComparer<QueryParameter> 
     { 

      #region IComparer<QueryParameter> Members 

      public int Compare(QueryParameter x, QueryParameter y) 
      { 
       if (x.Name == y.Name) 
       { 
        return string.Compare(x.Value, y.Value); 
       } 
       else 
       { 
        return string.Compare(x.Name, y.Name); 
       } 
      } 

      #endregion 
     } 

     protected const string OAuthVersion = "1.0"; 
     protected const string OAuthParameterPrefix = "oauth_"; 

     // 
     // List of know and used oauth parameters' names 
     //   
     protected const string OAuthConsumerKeyKey = "oauth_consumer_key"; 
     protected const string OAuthCallbackKey = "oauth_callback"; 
     protected const string OAuthVersionKey = "oauth_version"; 
     protected const string OAuthSignatureMethodKey = "oauth_signature_method"; 
     protected const string OAuthSignatureKey = "oauth_signature"; 
     protected const string OAuthTimestampKey = "oauth_timestamp"; 
     protected const string OAuthNonceKey = "oauth_nonce"; 
     protected const string OAuthTokenKey = "oauth_token"; 
     protected const string OAuthTokenSecretKey = "oauth_token_secret"; 

     protected const string HMACSHA1SignatureType = "HMAC-SHA1"; 
     protected const string PlainTextSignatureType = "PLAINTEXT"; 
     protected const string RSASHA1SignatureType = "RSA-SHA1"; 

     protected Random random = new Random(); 

     protected string unreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_.~"; 

     /// <summary> 
     /// Helper function to compute a hash value 
     /// </summary> 
     /// <param name="hashAlgorithm">The hashing algoirhtm used. If that algorithm needs some initialization, like HMAC and its derivatives, they should be initialized prior to passing it to this function</param> 
     /// <param name="data">The data to hash</param> 
     /// <returns>a Base64 string of the hash value</returns> 
     private string ComputeHash(HashAlgorithm hashAlgorithm, string data) 
     { 
      if (hashAlgorithm == null) 
      { 
       throw new ArgumentNullException("hashAlgorithm"); 
      } 

      if (string.IsNullOrEmpty(data)) 
      { 
       throw new ArgumentNullException("data"); 
      } 

      byte[] dataBuffer = System.Text.Encoding.ASCII.GetBytes(data); 
      byte[] hashBytes = hashAlgorithm.ComputeHash(dataBuffer); 

      return Convert.ToBase64String(hashBytes); 
     } 

     /// <summary> 
     /// Internal function to cut out all non oauth query string parameters (all parameters not begining with "oauth_") 
     /// </summary> 
     /// <param name="parameters">The query string part of the Url</param> 
     /// <returns>A list of QueryParameter each containing the parameter name and value</returns> 
     private List<QueryParameter> GetQueryParameters(string parameters) 
     { 
      if (parameters.StartsWith("?")) 
      { 
       parameters = parameters.Remove(0, 1); 
      } 

      List<QueryParameter> result = new List<QueryParameter>(); 

      if (!string.IsNullOrEmpty(parameters)) 
      { 
       string[] p = parameters.Split('&'); 
       foreach (string s in p) 
       { 
        if (!string.IsNullOrEmpty(s) && !s.StartsWith(OAuthParameterPrefix)) 
        { 
         if (s.IndexOf('=') > -1) 
         { 
          string[] temp = s.Split('='); 
          result.Add(new QueryParameter(temp[0], temp[1])); 
         } 
         else 
         { 
          result.Add(new QueryParameter(s, string.Empty)); 
         } 
        } 
       } 
      } 

      return result; 
     } 

     /// <summary> 
     /// This is a different Url Encode implementation since the default .NET one outputs the percent encoding in lower case. 
     /// While this is not a problem with the percent encoding spec, it is used in upper case throughout OAuth 
     /// </summary> 
     /// <param name="value">The value to Url encode</param> 
     /// <returns>Returns a Url encoded string</returns> 
     protected string UrlEncode(string value) 
     { 
      StringBuilder result = new StringBuilder(); 

      foreach (char symbol in value) 
      { 
       if (unreservedChars.IndexOf(symbol) != -1) 
       { 
        result.Append(symbol); 
       } 
       else 
       { 
        result.Append('%' + String.Format("{0:X2}", (int)symbol)); 
       } 
      } 

      return result.ToString(); 
     } 

     /// <summary> 
     /// Normalizes the request parameters according to the spec 
     /// </summary> 
     /// <param name="parameters">The list of parameters already sorted</param> 
     /// <returns>a string representing the normalized parameters</returns> 
     protected string NormalizeRequestParameters(IList<QueryParameter> parameters) 
     { 
      StringBuilder sb = new StringBuilder(); 
      QueryParameter p = null; 
      for (int i = 0; i < parameters.Count; i++) 
      { 
       p = parameters[i]; 
       sb.AppendFormat("{0}={1}", p.Name, p.Value); 

       if (i < parameters.Count - 1) 
       { 
        sb.Append("&"); 
       } 
      } 

      return sb.ToString(); 
     } 

     /// <summary> 
     /// Generate the signature base that is used to produce the signature 
     /// </summary> 
     /// <param name="url">The full url that needs to be signed including its non OAuth url parameters</param> 
     /// <param name="consumerKey">The consumer key</param>   
     /// <param name="token">The token, if available. If not available pass null or an empty string</param> 
     /// <param name="tokenSecret">The token secret, if available. If not available pass null or an empty string</param> 
     /// <param name="httpMethod">The http method used. Must be a valid HTTP method verb (POST,GET,PUT, etc)</param> 
     /// <param name="signatureType">The signature type. To use the default values use <see cref="OAuthBase.SignatureTypes">OAuthBase.SignatureTypes</see>.</param> 
     /// <returns>The signature base</returns> 
     public string GenerateSignatureBase(Uri url, string consumerKey, string token, string tokenSecret, string httpMethod, string timeStamp, string nonce, string signatureType, out string normalizedUrl, out string normalizedRequestParameters) 
     { 
      if (token == null) 
      { 
       token = string.Empty; 
      } 

      if (tokenSecret == null) 
      { 
       tokenSecret = string.Empty; 
      } 

      if (string.IsNullOrEmpty(consumerKey)) 
      { 
       throw new ArgumentNullException("consumerKey"); 
      } 

      if (string.IsNullOrEmpty(httpMethod)) 
      { 
       throw new ArgumentNullException("httpMethod"); 
      } 

      if (string.IsNullOrEmpty(signatureType)) 
      { 
       throw new ArgumentNullException("signatureType"); 
      } 

      normalizedUrl = null; 
      normalizedRequestParameters = null; 

      List<QueryParameter> parameters = GetQueryParameters(url.Query); 
      parameters.Add(new QueryParameter(OAuthVersionKey, OAuthVersion)); 
      parameters.Add(new QueryParameter(OAuthNonceKey, nonce)); 
      parameters.Add(new QueryParameter(OAuthTimestampKey, timeStamp)); 
      parameters.Add(new QueryParameter(OAuthSignatureMethodKey, signatureType)); 
      parameters.Add(new QueryParameter(OAuthConsumerKeyKey, consumerKey)); 

      if (!string.IsNullOrEmpty(token)) 
      { 
       parameters.Add(new QueryParameter(OAuthTokenKey, token)); 
      } 

      parameters.Sort(new QueryParameterComparer()); 

      normalizedUrl = string.Format("{0}://{1}", url.Scheme, url.Host); 
      if (!((url.Scheme == "http" && url.Port == 80) || (url.Scheme == "https" && url.Port == 443))) 
      { 
       normalizedUrl += ":" + url.Port; 
      } 
      normalizedUrl += url.AbsolutePath; 
      normalizedRequestParameters = NormalizeRequestParameters(parameters); 

      StringBuilder signatureBase = new StringBuilder(); 
      signatureBase.AppendFormat("{0}&", httpMethod.ToUpper()); 
      signatureBase.AppendFormat("{0}&", UrlEncode(normalizedUrl)); 
      signatureBase.AppendFormat("{0}", UrlEncode(normalizedRequestParameters)); 

      return signatureBase.ToString(); 
     } 

     /// <summary> 
     /// Generate the signature value based on the given signature base and hash algorithm 
     /// </summary> 
     /// <param name="signatureBase">The signature based as produced by the GenerateSignatureBase method or by any other means</param> 
     /// <param name="hash">The hash algorithm used to perform the hashing. If the hashing algorithm requires initialization or a key it should be set prior to calling this method</param> 
     /// <returns>A base64 string of the hash value</returns> 
     public string GenerateSignatureUsingHash(string signatureBase, HashAlgorithm hash) 
     { 
      return ComputeHash(hash, signatureBase); 
     } 

     /// <summary> 
     /// Generates a signature using the HMAC-SHA1 algorithm 
     /// </summary>  
     /// <param name="url">The full url that needs to be signed including its non OAuth url parameters</param> 
     /// <param name="consumerKey">The consumer key</param> 
     /// <param name="consumerSecret">The consumer seceret</param> 
     /// <param name="token">The token, if available. If not available pass null or an empty string</param> 
     /// <param name="tokenSecret">The token secret, if available. If not available pass null or an empty string</param> 
     /// <param name="httpMethod">The http method used. Must be a valid HTTP method verb (POST,GET,PUT, etc)</param> 
     /// <returns>A base64 string of the hash value</returns> 
     public string GenerateSignature(Uri url, string consumerKey, string consumerSecret, string token, string tokenSecret, string httpMethod, string timeStamp, string nonce, out string normalizedUrl, out string normalizedRequestParameters) 
     { 
      return GenerateSignature(url, consumerKey, consumerSecret, token, tokenSecret, httpMethod, timeStamp, nonce, SignatureTypes.HMACSHA1, out normalizedUrl, out normalizedRequestParameters); 
     } 

     /// <summary> 
     /// Generates a signature using the specified signatureType 
     /// </summary>  
     /// <param name="url">The full url that needs to be signed including its non OAuth url parameters</param> 
     /// <param name="consumerKey">The consumer key</param> 
     /// <param name="consumerSecret">The consumer seceret</param> 
     /// <param name="token">The token, if available. If not available pass null or an empty string</param> 
     /// <param name="tokenSecret">The token secret, if available. If not available pass null or an empty string</param> 
     /// <param name="httpMethod">The http method used. Must be a valid HTTP method verb (POST,GET,PUT, etc)</param> 
     /// <param name="signatureType">The type of signature to use</param> 
     /// <returns>A base64 string of the hash value</returns> 
     public string GenerateSignature(Uri url, string consumerKey, string consumerSecret, string token, string tokenSecret, string httpMethod, string timeStamp, string nonce, SignatureTypes signatureType, out string normalizedUrl, out string normalizedRequestParameters) 
     { 
      normalizedUrl = null; 
      normalizedRequestParameters = null; 

      switch (signatureType) 
      { 
       case SignatureTypes.PLAINTEXT: 
        return HttpUtility.UrlEncode(string.Format("{0}&{1}", consumerSecret, tokenSecret)); 
       case SignatureTypes.HMACSHA1: 
        string signatureBase = GenerateSignatureBase(url, consumerKey, token, tokenSecret, httpMethod, timeStamp, nonce, HMACSHA1SignatureType, out normalizedUrl, out normalizedRequestParameters); 

        HMACSHA1 hmacsha1 = new HMACSHA1(); 
        hmacsha1.Key = Encoding.ASCII.GetBytes(string.Format("{0}&{1}", UrlEncode(consumerSecret), string.IsNullOrEmpty(tokenSecret) ? "" : UrlEncode(tokenSecret))); 

        return GenerateSignatureUsingHash(signatureBase, hmacsha1); 
       case SignatureTypes.RSASHA1: 
        throw new NotImplementedException(); 
       default: 
        throw new ArgumentException("Unknown signature type", "signatureType"); 
      } 
     } 

     /// <summary> 
     /// Generate the timestamp for the signature   
     /// </summary> 
     /// <returns></returns> 
     public virtual string GenerateTimeStamp() 
     { 
      // Default implementation of UNIX time of the current UTC time 
      TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0); 
      return Convert.ToInt64(ts.TotalSeconds).ToString(); 
     } 

     /// <summary> 
     /// Generate a nonce 
     /// </summary> 
     /// <returns></returns> 
     public virtual string GenerateNonce() 
     { 
      // Just a simple implementation of a random number between 123400 and 9999999 
      return random.Next(123400, 9999999).ToString(); 
     } 

    } 
} 

最後的OAuthRequestAuthorizer類:

using OAuth; 
using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Net; 
using System.Web; 

namespace LiveTwitter 
{ 
    public interface IRequestAuthorizer 
    { 
     void Authorize(HttpWebRequest request, string postData); 
    } 

    public class OAuthRequestAuthorizer : OAuthBase, IRequestAuthorizer 
    { 
     private readonly string _twitterConsumerKey; 
     private readonly string _twitterConsumerSecret; 
     private readonly string _twitterAccessToken; 
     private readonly string _twitterAccessTokenSecret; 

     public OAuthRequestAuthorizer(string twitterConsumerKey, string twitterConsumerSecret, string twitterAccessToken, string twitterAccessTokenSecret) 
     { 
      _twitterConsumerKey = twitterConsumerKey; 
      _twitterConsumerSecret = twitterConsumerSecret; 
      _twitterAccessToken = twitterAccessToken; 
      _twitterAccessTokenSecret = twitterAccessTokenSecret; 
     } 

     public override string GenerateNonce() 
     { 
      return new Nonce().Value; 
     } 

     private string BuildHeader(HttpWebRequest request, Uri uri) 
     { 
      var nonce = GenerateNonce(); 
      var timeStamp = GenerateTimeStamp(); 

      string normalizedUrl; 
      string normalizedRequestParameters; 

      var httpMethod = request.Method; 
      var signature = GenerateSignature(uri, _twitterConsumerKey, _twitterConsumerSecret, _twitterAccessToken, _twitterAccessTokenSecret, 
               httpMethod, timeStamp, nonce, out normalizedUrl, 
               out normalizedRequestParameters); 

      // https://dev.twitter.com/docs/auth/authorizing-request 
      return 
       string.Format(
        "OAuth oauth_consumer_key=\"{0}\", " + 
        "oauth_nonce=\"{1}\", " + 
        "oauth_signature=\"{2}\", " + 
        "oauth_signature_method=\"HMAC-SHA1\", " + 
        "oauth_timestamp=\"{3}\", " + 
        "oauth_token=\"{4}\", " + 
        "oauth_version=\"1.0\"", 

        UrlEncode(_twitterConsumerKey), 
        UrlEncode(_twitterConsumerKey), 
        UrlEncode(nonce), 
        UrlEncode(signature), 
        UrlEncode(timeStamp), 
        UrlEncode(_twitterAccessToken)); 
     } 

     public void Authorize(HttpWebRequest request, string postData) 
     { 
      //NOTE: It's a must to collect all param either in the header/querystring or post body 
      var baseUri = string.IsNullOrEmpty(postData) || request.Method.ToUpper() == "GET" 
         ? request.RequestUri 
         : new Uri(string.Format("{0}?{1}", request.RequestUri.AbsoluteUri, postData)); 

      request.Headers["Authorization"] = BuildHeader(request, baseUri); 
     } 
    } 

    public class Nonce 
    { 
     public string Value { get; set; } 

     public Nonce() 
     { 
      Value = Guid.NewGuid().ToString(); 
     } 
    } 
} 

我使用的是正確的API密鑰,我想我在應用程序上設置了正確的設置。任何想法爲什麼我一直得到401?另外,如果有一種更簡單的方法來做到這一點,請讓我知道!

回答

0

Temboo被分解爲兩個調用簡化了Twitter上的OAuth的過程:

  • InitializeOAuth - 返回一個Twitter的授權網址,你可以展示給用戶,並已授權給他們的Twitter帳戶,您的應用程序訪問
  • FinalizeOAuth - 返回您需要的訪問令牌,用於對Twitter Streaming API進行身份驗證訪問。

您可以通過下面的鏈接在您的瀏覽器中試用Temboo的Twitter OAuth支持,然後生成在您的應用中使用此行爲所需的源代碼。如果您的語言不受支持(Temboo目前沒有C#SDK),您可以生成REST API cURL命令。

https://www.temboo.com/library/Library/Twitter/OAuth/

(全面披露:我在Temboo工作,所以讓我知道如果您有任何問題)

0

你可能要麼找到你的bug或發現解決問題的另一種方式現在,但是如果您還沒有,則401錯誤是由於OAuthRequestAuthorizer類中的複製粘貼錯誤造成的。您在BuildHeader函數的string.format方法中提交了第一個參數兩次。

當我嘗試它時,它現在會返回一個406錯誤,但至少它會移過您的401問題!

0

我有一個非常類似的問題(我想我可能從同一個博客看同一個代碼)。

我發現,如果我使用所有UPPER CASE字符作爲track =參數的值到流式API,那麼我就不會收到401錯誤。