使用C#创建Twitter搜索的授权头部

7
我正在写一个C#方法,用于为Twitter生成身份验证标头。我正在尝试通过该API搜索twitter:https://api.twitter.com/1.1/search/tweets.json
以下是我调用的URL:
https://api.twitter.com/1.1/search/tweets.json?q=%23countryman+OR+%23johncooperworks+OR+%40mini%26since_id%3d24012619984051000%26max_id%3d250126199840518145%26result_type%3dmixed%26count%3d4

这是我的方法:
private string GetTwitterAuthHeader()
{
    const string oauthConsumerKey = "";
    const string oauthConsumerSecret = "";
    const string oauthToken = "";
    const string oauthTokenSecret = "";
    const string oauthVersion = "1.0";
    const string oauthSignatureMethod = "HMAC-SHA1";

    var oauthNonce = Convert.ToBase64String(new ASCIIEncoding().GetBytes(DateTime.Now.Ticks.ToString(CultureInfo.InvariantCulture)));
    var timeSpan = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
    var oauthTimestamp = Convert.ToInt64(timeSpan.TotalSeconds).ToString(CultureInfo.InvariantCulture);

    const string resourceUrl = "https://api.twitter.com/1.1/search/tweets.json";
    const string baseFormat = "oauth_consumer_key={0}&oauth_nonce={1}&oauth_signature_method={2}" +
                                "&oauth_timestamp={3}&oauth_token={4}&oauth_version={5}";

    var baseString = string.Format(baseFormat,
                                oauthConsumerKey,
                                oauthNonce,
                                oauthSignatureMethod,
                                oauthTimestamp,
                                oauthToken,
                                oauthVersion
                                );

    baseString = string.Concat("GET&", Uri.EscapeDataString(resourceUrl), "&", Uri.EscapeDataString(baseString));

    var compositeKey = string.Concat(Uri.EscapeDataString(oauthConsumerSecret),
                            "&", Uri.EscapeDataString(oauthTokenSecret));

    string oauthSignature;
    using (var hasher = new HMACSHA1(Encoding.ASCII.GetBytes(compositeKey)))
    {
        oauthSignature = Convert.ToBase64String(
            hasher.ComputeHash(Encoding.ASCII.GetBytes(baseString)));
    }

    const string headerFormat = "OAuth oauth_consumer_key=\"{0}\", " +
                                "oauth_nonce=\"{1}\", " +
                                "oauth_signature=\"{2}\", " +
                                "oauth_signature_method=\"{3}\", " +
                                "oauth_timestamp=\"{4}\", " +
                                "oauth_token=\"{5}\", " +
                                "oauth_version=\"{6}\"";

    var authHeader = string.Format(headerFormat,
                            Uri.EscapeDataString(oauthConsumerKey),
                            Uri.EscapeDataString(oauthNonce),
                            Uri.EscapeDataString(oauthSignature),
                            Uri.EscapeDataString(oauthSignatureMethod),
                            Uri.EscapeDataString(oauthTimestamp),
                            Uri.EscapeDataString(oauthToken),
                            Uri.EscapeDataString(oauthVersion)
                    );

    return authHeader;
}

The error I get is:

{
    "errors": [
        {
            "message": "Bad Authentication data",
            "code": 215
        }
    ]
}

有什么提示吗?

在生成授权标头时,我需要考虑实际搜索查询吗?(例如我附加到搜索API的值)?

我发现很难调试。

谢谢!

编辑:

根据反馈,这是一个更新:

var resourceUrl = "https://api.twitter.com/1.1/search/tweets.json";
const string baseFormat = "oauth_consumer_key={0}&oauth_nonce={1}&oauth_signature_method={2}" +
                            "&oauth_timestamp={3}&oauth_token={4}&oauth_version={5}&q={6}";

var baseString = string.Format(baseFormat,
                            oauthConsumerKey,
                            oauthNonce,
                            oauthSignatureMethod,
                            oauthTimestamp,
                            oauthToken,
                            oauthVersion,
                            query
                            );

baseString = string.Concat("GET&", 
    Uri.EscapeDataString(resourceUrl), "&", 
    Uri.EscapeDataString(baseString));

我仔细阅读了https://dev.twitter.com/oauth/overview/creating-signatures,看起来这个是正确的。然而,我仍然得到相同的错误。


是的,在 OAuth 1 中,请求参数需要包含在签名字符串中。 - user94559
1
这是 Twitter 关于此的文档(但我相信它只是遵循 OAuth 1.0a 规范):https://dev.twitter.com/oauth/overview/creating-signatures。 - user94559
1
这个可以工作:https://github.com/Twitterizer/Twitterizer/tree/develop/Twitterizer2/OAuth - L.B
我已经更新了代码,使得 var resourceUrl = string.Format("https://api.twitter.com/1.1/search/tweets.json?q={0}", HttpUtility.UrlEncode(query)); 包含传入的参数。但是,它仍然失败了。是其他地方出了问题吗? - Wade
这不是包含参数的正确方式。(它们在基本URL之后的和号后面放入字符串以进行签名。) - user94559
感谢@smarx。我已经在上面添加了更新;我认为这就捕捉到了它,但仍然失败了。 - Wade
2个回答

4

将您的“查询”包装在Uri.EscapeDataString()中。

如果这样做,您上面的确切代码对我有效。话虽如此,您真的应该转义所有那些参数键和值,而且我有点困惑为什么nonce可以正常工作而无需转义,因为它是base64编码的。但也许我在测试中很幸运,从未碰到过一个含有斜杠的nonce。


4
就是这个了!哎呀,有时候小事情也会阻碍我们前进。感谢帮助。如果有人感兴趣,这里是结果链接:https://github.com/wadewegner/TwitterOAuthRESTAPI/tree/master/src - Wade
@Wade,两年后,我对你的TwitterOAuthRESTAPI代码有一个问题。在GetHeader(...)函数中,为什么要使用UriBuilder?难道你不能直接使用传入的uri变量吗? - Daevin
不是Wade,但他这样做是为了去掉查询字符串,因为查询字符串不在字符串签名的资源URL部分中。 - user94559

3

所以,我写了一个类来实现这个功能。让我们看看我能不能将它详细地解释一下。我们先从整个类开始。

    public class TwitterAuthTool : IDisposable {
    private const string BASE_AUTH_URL = "https://api.twitter.com/oauth2/token";
    private const string BASE_SEARCH_URL = "https://api.twitter.com/1.1/search/tweets.json";
    private const string BASE_INVALIDATE_URL = "https://api.twitter.com/oauth2/invalidate_token";

    private AccessToken Credentials;
    private string BearerTokenCredentials;

    public TwitterAuthTool( string p_ConsumerKey, string p_ConsumerSecret ) {
        BearerTokenCredentials =
            Convert.ToBase64String(
                Encoding.ASCII.GetBytes(
                    string.Format( "{0}:{1}",
                        Uri.EscapeUriString( p_ConsumerKey ),
                        Uri.EscapeUriString( p_ConsumerSecret ) ) ) );

        HttpWebRequest _Request = HttpWebRequest.Create( BASE_AUTH_URL ) as HttpWebRequest;
        _Request.KeepAlive = false;
        _Request.Method = "POST";
        _Request.Headers.Add( "Authorization", string.Format( "Basic {0}", BearerTokenCredentials ) );
        _Request.ContentType = "application/x-www-form-urlencoded;charset=UTF-8";

        byte[] _Content = Encoding.ASCII.GetBytes( "grant_type=client_credentials" );
        using( Stream _Stream = _Request.GetRequestStream() )
            _Stream.Write( _Content, 0, _Content.Length );

        HttpWebResponse _Response = _Request.GetResponse() as HttpWebResponse;

        DataContractJsonSerializer _AccessTokenJsonSerializer = new DataContractJsonSerializer( typeof( AccessToken ) );
        Credentials = (AccessToken)_AccessTokenJsonSerializer.ReadObject( _Response.GetResponseStream() );
    }

    public List<Tweet> GetLatest( string p_Query, int p_Count = 100 ) {
        TwitterResults _TwitterResults;
        List<Tweet> _ReturnValue = new List<Tweet>();
        DataContractJsonSerializer _JsonSerializer = new DataContractJsonSerializer( typeof( TwitterResults ) );

        HttpWebRequest _Request = WebRequest.Create( string.Format( "{0}?q={1}&result_type=recent&count={2}", BASE_SEARCH_URL, p_Query, p_Count ) ) as HttpWebRequest;
        _Request.Headers.Add( "Authorization", string.Format( "Bearer {0}", Credentials.access_token ) );
        _Request.KeepAlive = false;
        _Request.Method = "GET";

        HttpWebResponse _Response = _Request.GetResponse() as HttpWebResponse;
        _TwitterResults = (TwitterResults)_JsonSerializer.ReadObject( _Response.GetResponseStream() );
        _ReturnValue.AddRange( _TwitterResults.statuses );

        while( !string.IsNullOrWhiteSpace( _TwitterResults.search_metadata.next_results ) ) {
            _Request = WebRequest.Create( string.Format( "{0}{1}", BASE_SEARCH_URL, _TwitterResults.search_metadata.next_results ) ) as HttpWebRequest;
            _Request.Headers.Add( "Authorization", string.Format( "Bearer {0}", Credentials.access_token ) );
            _Request.KeepAlive = false;
            _Request.Method = "GET";

            _Response = _Request.GetResponse() as HttpWebResponse;
            _TwitterResults = (TwitterResults)_JsonSerializer.ReadObject( _Response.GetResponseStream() );
            _ReturnValue.AddRange( _TwitterResults.statuses );
        }

        return _ReturnValue;
    }

    public List<Tweet> GetLatestSince( string p_Query, long p_SinceId, int p_Count = 100 ) {
        TwitterResults _TwitterResults;
        List<Tweet> _ReturnValue = new List<Tweet>();
        DataContractJsonSerializer _JsonSerializer = new DataContractJsonSerializer( typeof( TwitterResults ) );

        HttpWebRequest _Request = WebRequest.Create( string.Format( "{0}?q={1}&result_type=recent&count={2}&since_id={3}", BASE_SEARCH_URL, p_Query, p_Count, p_SinceId ) ) as HttpWebRequest;
        _Request.Headers.Add( "Authorization", string.Format( "Bearer {0}", Credentials.access_token ) );
        _Request.KeepAlive = false;
        _Request.Method = "GET";

        HttpWebResponse _Response = _Request.GetResponse() as HttpWebResponse;
        _TwitterResults = (TwitterResults)_JsonSerializer.ReadObject( _Response.GetResponseStream() );
        _ReturnValue.AddRange( _TwitterResults.statuses );

        while( !string.IsNullOrWhiteSpace( _TwitterResults.search_metadata.next_results ) ) {
            _Request = WebRequest.Create( string.Format( "{0}{1}", BASE_SEARCH_URL, _TwitterResults.search_metadata.next_results ) ) as HttpWebRequest;
            _Request.Headers.Add( "Authorization", string.Format( "Bearer {0}", Credentials.access_token ) );
            _Request.KeepAlive = false;
            _Request.Method = "GET";

            _Response = _Request.GetResponse() as HttpWebResponse;
            _TwitterResults = (TwitterResults)_JsonSerializer.ReadObject( _Response.GetResponseStream() );
            _ReturnValue.AddRange( _TwitterResults.statuses );
        }

        return _ReturnValue;
    }

    public void Dispose() {
        HttpWebRequest _Request = WebRequest.Create( BASE_INVALIDATE_URL ) as HttpWebRequest;
        _Request.KeepAlive = false;
        _Request.Method = "POST";
        _Request.Headers.Add( "Authorization", string.Format( "Basic {0}", BearerTokenCredentials ) );
        _Request.ContentType = "application/x-www-form-urlencoded";

        byte[] _Content = Encoding.ASCII.GetBytes( string.Format( "access_token={0}", Credentials.access_token ) );
        using( Stream _Stream = _Request.GetRequestStream() )
            _Stream.Write( _Content, 0, _Content.Length );

        try {
            _Request.GetResponse();
        } catch {
            // The bearer token will time out if this fails.
        }
    }
}

Twitter使用类似OAuth2的东西。在做任何其他操作之前,您必须登录。这就是上述类的构造函数所做的事情。当您这样做时,您会收到一个访问令牌。它是一个简单的JSON对象,可以轻松反序列化为以下小类。

    [DataContract]
public class AccessToken {
    [DataMember]
    public string token_type;

    [DataMember]
    public string access_token;
}

一旦您拥有正确的凭证,就可以使用它们来访问内容。不幸的是,从查询中获取的JSON对象非常庞大。我不得不创建17个不同的类来反序列化它。我会考虑将该库发布在github上。

正如您在Dispose中所看到的,如果验证令牌失败,我并不在意。他们提供给您的凭据在8小时后超时。


1
我将整个项目放在Github上:https://github.com/freestylecoder/TwitterAuthTool - Chris Gardner
谢谢您!不过,我真的在寻找一种使用三腿授权流程来完成这个操作的方法。 - Wade

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