2013-04-08 53 views
1

我正在嘗試使用twitter工作來獲取雙腿oauth,以便能夠檢索推文以在網站上顯示。爲此,我想使用DotNetOpenAuth。爲了讓我開始使用DotNetOpenAuth附帶的OAuthConsumer示例。DotNetOpenAuth和Twitter的雙腿OAuth。獲取401錯誤

下面是我現在的代碼隱藏代碼。在這裏,我使用3腿部分或2腿部分。 3腿部分工作正常(這是基於示例中的Twitter.aspx)。然而,這兩個部分(基於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

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

的Twitter並沒有真正支持what I call 2-legged OAuth。它只做「0腿OAuth」。由於您調用的ConsumerBase.RequestNewClientAccount方法是用於雙腿OAuth,因此它失敗。

對於0腳OAuth,您需要創建一個預填有消費者密鑰,祕密和訪問令牌和密碼的InMemoryTokenManager。然後將該令牌管理器傳遞到您的ConsumerBase派生類型(WebConsumerDesktopConsumer),並開始進行授權調用。

或者更簡單地說,你可以download the DotNetOpenAuth v4.3 preview了包括DelegatingHandler在那裏你可以完全跳過上面的步驟,只是注入你的鑰匙,令牌和祕密爲一個簡單的方法,並開始與HttpClient電話是自動的OAuth 1簽署。