简体   繁体   English

带DotNetOpenAuth和Twitter的两足式OAuth。 收到401错误

[英]2-legged OAuth with DotNetOpenAuth and Twitter. Getting a 401 error

I'm trying to get 2-legged oauth with twitter working to be able to retrieve tweets to display on websites. 我正在尝试使用Twitter进行两足式OAuth,以便能够检索要在网站上显示的推文。 For this i want to use DotNetOpenAuth. 为此,我想使用DotNetOpenAuth。 To get me started i used the OAuthConsumer example that comes with DotNetOpenAuth. 首先,我使用了DotNetOpenAuth随附的OAuthConsumer示例。

Below is the codebehind code i have now. 以下是我现在拥有的代码隐藏代码。 Here i use either the 3-legged part or the 2-legged part. 在这里,我使用三足式或两足式。 The 3-legged part works fine (this is based on Twitter.aspx from the sample). 三足的部分工作正常(这是基于示例中的Twitter.aspx)。 The 2-legged part (based on GoogleApps2Legged.aspx) however gives a 401 error on twitter.RequestNewClientAccount(); 但是,两足部分(基于GoogleApps2Legged.aspx)在twitter.RequestNewClientAccount()上给出了401错误。

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

Twitter doesn't really support what I call 2-legged OAuth . Twitter并不真正支持我所说的两足式OAuth It only does "0-legged OAuth". 它仅执行“ 0足OAuth”。 Since the ConsumerBase.RequestNewClientAccount method that you're calling is for 2-legged OAuth, it's failing. 由于您正在调用的ConsumerBase.RequestNewClientAccount方法适用于两足式OAuth,因此失败了。

For 0-legged OAuth, you need to create an InMemoryTokenManager that is prefilled with your consumer key, secret and access token and secret. 对于使用0条腿的OAuth,您需要创建一个InMemoryTokenManager ,并预先填充您的使用者密钥,机密以及访问令牌和机密。 Then pass that token manager into your ConsumerBase -derived type ( WebConsumer or DesktopConsumer ) and begin making authorized calls. 然后将该令牌管理器传递给您的ConsumerBase派生类型( WebConsumerDesktopConsumer ),然后开始进行授权的调用。

Or much more simply, you can download the DotNetOpenAuth v4.3 preview that includes an DelegatingHandler where you can completely skip the above steps and just inject your key, token and secrets into one simple method and start making calls with HttpClient that are automatically OAuth 1 signed. 或者更简单地说,您可以下载包含DelegatingHandler 的DotNetOpenAuth v4.3预览版 ,在其中可以完全跳过上述步骤,只需将密钥,令牌和机密信息注入一种简单的方法中,并开始使用自动为OAuth 1的HttpClient进行调用签。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM