使用DotNetOpenAuth和Twitter的双腿OAuth。得到401错误

时间:2013-04-08 15:40:12

标签: c# twitter oauth dotnetopenauth

我正试图通过推特获得2条腿的oauth,以便能够检索要在网站上显示的推文。为此,我想使用DotNetOpenAuth。为了让我开始,我使用了DotNetOpenAuth附带的OAuthConsumer示例。

下面是我现在的代码隐藏代码。在这里,我使用3腿部分或2腿部分。三条腿部分工作正常(这是基于样本中的Twitter.aspx)。然而,2条腿部分(基于GoogleApps2Legged.aspx)在twitter上发出401错误.RequestNewClientAccount();

private string AccessToken
{
    get { return (string)Session["TwitterAccessToken"]; }
    set { Session["TwitterAccessToken"] = value; }
}

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        // 3 legged
        var twitter = new WebConsumer(TwitterConsumer.ServiceDescription, TwitterConsumer.ShortTermUserSessionTokenManager);

        //// Is Twitter calling back with authorization?
        var accessTokenResponse = twitter.ProcessUserAuthorization();
        if (accessTokenResponse != null)
        {
            this.AccessToken = accessTokenResponse.AccessToken;
        }
        else if (this.AccessToken == null)
        {
            // If we don't yet have access, immediately request it.
            twitter.Channel.Send(twitter.PrepareRequestUserAuthorization());
        }

        var response = TwitterConsumer.GetUserTimeLine(twitter, this.AccessToken);

        // 2 legged
        var twitter = new WebConsumer(TwitterConsumer.ServiceDescription, TwitterConsumer.ShortTermUserSessionTokenManager);

        this.AccessToken = twitter.RequestNewClientAccount();
        var response = twitter.PrepareAuthorizedRequestAndSend(TwitterConsumer.GetUserTimelineStatusEndpoint, this.AccessToken);
    }
}

TwitterConsumer.cs

//-----------------------------------------------------------------------
// <copyright file="TwitterConsumer.cs" company="Outercurve Foundation">
//     Copyright (c) Outercurve Foundation. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

namespace DotNetOpenAuth.ApplicationBlock {
    using System;
    using System.Collections.Generic;
    using System.Configuration;
    using System.Globalization;
    using System.IO;
    using System.Net;
    using System.Web;
    using System.Linq;
    using System.Xml;
    using System.Xml.Linq;
    using System.Xml.XPath;
    using DotNetOpenAuth.Messaging;
    using DotNetOpenAuth.OAuth;
    using DotNetOpenAuth.OAuth.ChannelElements;

    /// <summary>
    /// A consumer capable of communicating with Twitter.
    /// </summary>
    public static class TwitterConsumer {
        /// <summary>
        /// The description of Twitter's OAuth protocol URIs for use with actually reading/writing
        /// a user's private Twitter data.
        /// </summary>
        public static readonly ServiceProviderDescription ServiceDescription = new ServiceProviderDescription {
            RequestTokenEndpoint = new MessageReceivingEndpoint("https://api.twitter.com/oauth/request_token", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
            UserAuthorizationEndpoint = new MessageReceivingEndpoint("https://api.twitter.com/oauth/authorize", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
            AccessTokenEndpoint = new MessageReceivingEndpoint("https://api.twitter.com/oauth/access_token", HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
            TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() },
        };

        /// <summary>
        /// The description of Twitter's OAuth protocol URIs for use with their "Sign in with Twitter" feature.
        /// </summary>
        public static readonly ServiceProviderDescription SignInWithTwitterServiceDescription = new ServiceProviderDescription {
            RequestTokenEndpoint = new MessageReceivingEndpoint("https://api.twitter.com/oauth/request_token", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
            UserAuthorizationEndpoint = new MessageReceivingEndpoint("https://api.twitter.com/oauth/authenticate", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
            AccessTokenEndpoint = new MessageReceivingEndpoint("https://api.twitter.com/oauth/access_token", HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
            TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() },
        };

        /// <summary>
        /// The URI to get a user's favorites.
        /// </summary>
        private static readonly MessageReceivingEndpoint GetFavoritesEndpoint = new MessageReceivingEndpoint("http://api.twitter.com/1/favorites.xml", HttpDeliveryMethods.GetRequest);

        /// <summary>
        /// The URI to get the data on the user's home page.
        /// </summary>
        public static readonly MessageReceivingEndpoint GetFriendTimelineStatusEndpoint = new MessageReceivingEndpoint("http://api.twitter.com/1/statuses/friends_timeline.xml", HttpDeliveryMethods.GetRequest);

        public static readonly MessageReceivingEndpoint UpdateProfileBackgroundImageEndpoint = new MessageReceivingEndpoint("http://api.twitter.com/1/account/update_profile_background_image.xml", HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest);

        public static readonly MessageReceivingEndpoint UpdateProfileImageEndpoint = new MessageReceivingEndpoint("http://api.twitter.com/1/account/update_profile_image.xml", HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest);

        public static readonly MessageReceivingEndpoint VerifyCredentialsEndpoint = new MessageReceivingEndpoint("http://api.twitter.com/1/account/verify_credentials.xml", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest);

        public static readonly MessageReceivingEndpoint GetUserTimelineStatusEndpoint = new MessageReceivingEndpoint("http://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=concepts2go", HttpDeliveryMethods.GetRequest);

        /// <summary>
        /// The consumer used for the Sign in to Twitter feature.
        /// </summary>
        private static WebConsumer signInConsumer;

        /// <summary>
        /// The lock acquired to initialize the <see cref="signInConsumer"/> field.
        /// </summary>
        private static object signInConsumerInitLock = new object();

        /// <summary>
        /// Initializes static members of the <see cref="TwitterConsumer"/> class.
        /// </summary>
        static TwitterConsumer() {
            // Twitter can't handle the Expect 100 Continue HTTP header. 
            ServicePointManager.FindServicePoint(GetFavoritesEndpoint.Location).Expect100Continue = false;
        }

        /// <summary>
        /// Gets a value indicating whether the Twitter consumer key and secret are set in the web.config file.
        /// </summary>
        public static bool IsTwitterConsumerConfigured {
            get {
                return true;
            }
        }

        /// <summary>
        /// Gets the consumer to use for the Sign in to Twitter feature.
        /// </summary>
        /// <value>The twitter sign in.</value>
        private static WebConsumer TwitterSignIn {
            get {
                if (signInConsumer == null) {
                    lock (signInConsumerInitLock) {
                        if (signInConsumer == null) {
                            signInConsumer = new WebConsumer(SignInWithTwitterServiceDescription, ShortTermUserSessionTokenManager);
                        }
                    }
                }

                return signInConsumer;
            }
        }

        public static InMemoryTokenManager ShortTermUserSessionTokenManager {
            get {
                var store = HttpContext.Current.Session;
                var tokenManager = (InMemoryTokenManager)store["TwitterShortTermUserSessionTokenManager"];
                if (tokenManager == null) {
                    string consumerKey = "dgnwPLz40rcvmGKn0SugfA";
                    string consumerSecret = "IvaV0cA3N146mXTXPH5EaEHX8XbULch1vwBYpeI4Xc";
                    if (IsTwitterConsumerConfigured) {
                        tokenManager = new InMemoryTokenManager(consumerKey, consumerSecret);
                        store["TwitterShortTermUserSessionTokenManager"] = tokenManager;
                    } else {
                        throw new InvalidOperationException("No Twitter OAuth consumer key and secret could be found in web.config AppSettings.");
                    }
                }

                return tokenManager;
            }
        }

        public static XDocument GetUpdates(ConsumerBase twitter, string accessToken) {
            IncomingWebResponse response = twitter.PrepareAuthorizedRequestAndSend(GetFriendTimelineStatusEndpoint, accessToken);
            return XDocument.Load(XmlReader.Create(response.GetResponseReader()));
        }

        public static IncomingWebResponse GetUserTimeLine(ConsumerBase twitter, string accessToken)
        {
            IncomingWebResponse response = twitter.PrepareAuthorizedRequestAndSend(GetUserTimelineStatusEndpoint, accessToken);

            return response;

            //return XDocument.Load(XmlReader.Create(response.GetResponseReader()));
        }

        public static XDocument GetFavorites(ConsumerBase twitter, string accessToken) {
            IncomingWebResponse response = twitter.PrepareAuthorizedRequestAndSend(GetFavoritesEndpoint, accessToken);
            return XDocument.Load(XmlReader.Create(response.GetResponseReader()));
        }

        public static XDocument UpdateProfileBackgroundImage(ConsumerBase twitter, string accessToken, string image, bool tile) {
            var parts = new[] {
                MultipartPostPart.CreateFormFilePart("image", image, "image/" + Path.GetExtension(image).Substring(1).ToLowerInvariant()),
                MultipartPostPart.CreateFormPart("tile", tile.ToString().ToLowerInvariant()),
            };
            HttpWebRequest request = twitter.PrepareAuthorizedRequest(UpdateProfileBackgroundImageEndpoint, accessToken, parts);
            request.ServicePoint.Expect100Continue = false;
            IncomingWebResponse response = twitter.Channel.WebRequestHandler.GetResponse(request);
            string responseString = response.GetResponseReader().ReadToEnd();
            return XDocument.Parse(responseString);
        }

        public static XDocument UpdateProfileImage(ConsumerBase twitter, string accessToken, string pathToImage) {
            string contentType = "image/" + Path.GetExtension(pathToImage).Substring(1).ToLowerInvariant();
            return UpdateProfileImage(twitter, accessToken, File.OpenRead(pathToImage), contentType);
        }

        public static XDocument UpdateProfileImage(ConsumerBase twitter, string accessToken, Stream image, string contentType) {
            var parts = new[] {
                MultipartPostPart.CreateFormFilePart("image", "twitterPhoto", contentType, image),
            };
            HttpWebRequest request = twitter.PrepareAuthorizedRequest(UpdateProfileImageEndpoint, accessToken, parts);
            IncomingWebResponse response = twitter.Channel.WebRequestHandler.GetResponse(request);
            string responseString = response.GetResponseReader().ReadToEnd();
            return XDocument.Parse(responseString);
        }

        public static XDocument VerifyCredentials(ConsumerBase twitter, string accessToken) {
            IncomingWebResponse response = twitter.PrepareAuthorizedRequestAndSend(VerifyCredentialsEndpoint, accessToken);
            return XDocument.Load(XmlReader.Create(response.GetResponseReader()));
        }

        public static string GetUsername(ConsumerBase twitter, string accessToken) {
            XDocument xml = VerifyCredentials(twitter, accessToken);
            XPathNavigator nav = xml.CreateNavigator();
            return nav.SelectSingleNode("/user/screen_name").Value;
        }

        /// <summary>
        /// Prepares a redirect that will send the user to Twitter to sign in.
        /// </summary>
        /// <param name="forceNewLogin">if set to <c>true</c> the user will be required to re-enter their Twitter credentials even if already logged in to Twitter.</param>
        /// <returns>The redirect message.</returns>
        /// <remarks>
        /// Call <see cref="OutgoingWebResponse.Send"/> or
        /// <c>return StartSignInWithTwitter().<see cref="MessagingUtilities.AsActionResult">AsActionResult()</see></c>
        /// to actually perform the redirect.
        /// </remarks>
        public static OutgoingWebResponse StartSignInWithTwitter(bool forceNewLogin) {
            var redirectParameters = new Dictionary<string, string>();
            if (forceNewLogin) {
                redirectParameters["force_login"] = "true";
            }
            Uri callback = MessagingUtilities.GetRequestUrlFromContext().StripQueryArgumentsWithPrefix("oauth_");
            var request = TwitterSignIn.PrepareRequestUserAuthorization(callback, null, redirectParameters);
            return TwitterSignIn.Channel.PrepareResponse(request);
        }

        /// <summary>
        /// Checks the incoming web request to see if it carries a Twitter authentication response,
        /// and provides the user's Twitter screen name and unique id if available.
        /// </summary>
        /// <param name="screenName">The user's Twitter screen name.</param>
        /// <param name="userId">The user's Twitter unique user ID.</param>
        /// <returns>
        /// A value indicating whether Twitter authentication was successful;
        /// otherwise <c>false</c> to indicate that no Twitter response was present.
        /// </returns>
        public static bool TryFinishSignInWithTwitter(out string screenName, out int userId) {
            screenName = null;
            userId = 0;
            var response = TwitterSignIn.ProcessUserAuthorization();
            if (response == null) {
                return false;
            }

            screenName = response.ExtraData["screen_name"];
            userId = int.Parse(response.ExtraData["user_id"]);

            // If we were going to make this LOOK like OpenID even though it isn't,
            // this seems like a reasonable, secure claimed id to allow the user to assume.
            OpenId.Identifier fake_claimed_id = string.Format(CultureInfo.InvariantCulture, "http://twitter.com/{0}#{1}", screenName, userId);

            return true;
        }
    }
}

InMemoryTokenManager.cs

//-----------------------------------------------------------------------
// <copyright file="InMemoryTokenManager.cs" company="Outercurve Foundation">
//     Copyright (c) Outercurve Foundation. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

namespace DotNetOpenAuth.ApplicationBlock {
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using DotNetOpenAuth.OAuth.ChannelElements;
    using DotNetOpenAuth.OAuth.Messages;
    using DotNetOpenAuth.OpenId.Extensions.OAuth;

    /// <summary>
    /// A token manager that only retains tokens in memory. 
    /// Meant for SHORT TERM USE TOKENS ONLY.
    /// </summary>
    /// <remarks>
    /// A likely application of this class is for "Sign In With Twitter",
    /// where the user only signs in without providing any authorization to access
    /// Twitter APIs except to authenticate, since that access token is only useful once.
    /// </remarks>
    public class InMemoryTokenManager : IConsumerTokenManager, IOpenIdOAuthTokenManager {
        private Dictionary<string, string> tokensAndSecrets = new Dictionary<string, string>();

        /// <summary>
        /// Initializes a new instance of the <see cref="InMemoryTokenManager"/> class.
        /// </summary>
        /// <param name="consumerKey">The consumer key.</param>
        /// <param name="consumerSecret">The consumer secret.</param>
        public InMemoryTokenManager(string consumerKey, string consumerSecret) {
            if (string.IsNullOrEmpty(consumerKey)) {
                throw new ArgumentNullException("consumerKey");
            }

            this.ConsumerKey = consumerKey;
            this.ConsumerSecret = consumerSecret;
        }

        /// <summary>
        /// Gets the consumer key.
        /// </summary>
        /// <value>The consumer key.</value>
        public string ConsumerKey { get; private set; }

        /// <summary>
        /// Gets the consumer secret.
        /// </summary>
        /// <value>The consumer secret.</value>
        public string ConsumerSecret { get; private set; }

        #region ITokenManager Members

        /// <summary>
        /// Gets the Token Secret given a request or access token.
        /// </summary>
        /// <param name="token">The request or access token.</param>
        /// <returns>
        /// The secret associated with the given token.
        /// </returns>
        /// <exception cref="ArgumentException">Thrown if the secret cannot be found for the given token.</exception>
        public string GetTokenSecret(string token) {
            return this.tokensAndSecrets[token];
        }

        /// <summary>
        /// Stores a newly generated unauthorized request token, secret, and optional
        /// application-specific parameters for later recall.
        /// </summary>
        /// <param name="request">The request message that resulted in the generation of a new unauthorized request token.</param>
        /// <param name="response">The response message that includes the unauthorized request token.</param>
        /// <exception cref="ArgumentException">Thrown if the consumer key is not registered, or a required parameter was not found in the parameters collection.</exception>
        /// <remarks>
        /// Request tokens stored by this method SHOULD NOT associate any user account with this token.
        /// It usually opens up security holes in your application to do so.  Instead, you associate a user
        /// account with access tokens (not request tokens) in the <see cref="ExpireRequestTokenAndStoreNewAccessToken"/>
        /// method.
        /// </remarks>
        public void StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response) {
            this.tokensAndSecrets[response.Token] = response.TokenSecret;
        }

        /// <summary>
        /// Deletes a request token and its associated secret and stores a new access token and secret.
        /// </summary>
        /// <param name="consumerKey">The Consumer that is exchanging its request token for an access token.</param>
        /// <param name="requestToken">The Consumer's request token that should be deleted/expired.</param>
        /// <param name="accessToken">The new access token that is being issued to the Consumer.</param>
        /// <param name="accessTokenSecret">The secret associated with the newly issued access token.</param>
        /// <remarks>
        ///     <para>
        /// Any scope of granted privileges associated with the request token from the
        /// original call to <see cref="StoreNewRequestToken"/> should be carried over
        /// to the new Access Token.
        /// </para>
        ///     <para>
        /// To associate a user account with the new access token,
        /// <see cref="System.Web.HttpContext.User">HttpContext.Current.User</see> may be
        /// useful in an ASP.NET web application within the implementation of this method.
        /// Alternatively you may store the access token here without associating with a user account,
        /// and wait until <see cref="WebConsumer.ProcessUserAuthorization()"/> or
        /// <see cref="DesktopConsumer.ProcessUserAuthorization(string, string)"/> return the access
        /// token to associate the access token with a user account at that point.
        /// </para>
        /// </remarks>
        public void ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret) {
            this.tokensAndSecrets.Remove(requestToken);
            this.tokensAndSecrets[accessToken] = accessTokenSecret;
        }

        /// <summary>
        /// Classifies a token as a request token or an access token.
        /// </summary>
        /// <param name="token">The token to classify.</param>
        /// <returns>Request or Access token, or invalid if the token is not recognized.</returns>
        public TokenType GetTokenType(string token) {
            throw new NotImplementedException();
        }

        #endregion

        #region IOpenIdOAuthTokenManager Members

        /// <summary>
        /// Stores a new request token obtained over an OpenID request.
        /// </summary>
        /// <param name="consumerKey">The consumer key.</param>
        /// <param name="authorization">The authorization message carrying the request token and authorized access scope.</param>
        /// <remarks>
        ///     <para>The token secret is the empty string.</para>
        ///     <para>Tokens stored by this method should be short-lived to mitigate
        /// possible security threats.  Their lifetime should be sufficient for the
        /// relying party to receive the positive authentication assertion and immediately
        /// send a follow-up request for the access token.</para>
        /// </remarks>
        public void StoreOpenIdAuthorizedRequestToken(string consumerKey, AuthorizationApprovedResponse authorization) {
            this.tokensAndSecrets[authorization.RequestToken] = string.Empty;
        }

        #endregion
    }
}

1 个答案:

答案 0 :(得分:1)

Twitter并不真正支持what I call 2-legged OAuth。它只做“0腿OAuth”。由于您调用的ConsumerBase.RequestNewClientAccount方法适用于双腿OAuth,因此失败。

对于0-legged OAuth,您需要创建一个预先填入您的消费者密钥,密码和访问令牌以及机密的InMemoryTokenManager。然后将该令牌管理器传递到ConsumerBase派生类型(WebConsumerDesktopConsumer)并开始拨打授权电话。

或者更简单地说,您可以download the DotNetOpenAuth v4.3 preview包含DelegatingHandler,您可以完全跳过上述步骤,只需将密钥,令牌和秘密注入一个简单的方法,然后开始使用HttpClient拨打电话这是自动OAuth 1签名。