2012-10-23 21 views
4

如果我使用Internet Application模板創建ASP.NET MVC 4 Web應用程序,它將預安裝使用一系列OAuth和OpenID提供程序實現身份驗證所需的所有組件和配置。只需將我的Twitter消費者密鑰和祕密添加到AuthConfig.cs即可激活通過Twitter進行的身份驗證。MVC4的DotNetOpenAuth TwitterClient示例不尊重事先登錄

但是,它並不像我所預期的那樣工作。

如果我嘗試使用Twitter進行身份驗證,它總是顯示一個Twitter登錄頁面,無論我是否已登錄Twitter。它還將我從Twitter註銷,這樣我就不得不在我的下一個瀏覽器中訪問Twitter時重新進行身份驗證。

這是一個缺陷,還是需要一些額外的配置才能將其轉換爲更常見的無縫工作流程(對於Google等其他提供者來說,它可以正常工作)?

謝謝,提前。

回答

8

如果任何人碰到這個問題,我在這裏介紹我所發現的(有比較難看的解決辦法在一起)。

使用Fiddler檢查DotNetOpenAuth和Twitter之間的HTTP流量,很明顯身份驗證請求包含force_login=false查詢字符串參數,這表明DNOA正常工作。但是,如果我使用Fiddler的腳本功能來修改出站請求並完全刪除參數,則所有內容都將開始正常工作。我猜測Twitter的實現在這裏是錯誤的,將任何force_login參數的存在視爲相當於force_login=true

因爲我不認爲有可能讓Twitter來修改他們的API的行爲,我調查了是否有一個更容易訪問的解決方案。

綜觀DNOA代碼,我看到force_login=false參數由DotNetOpenAuthWebConsumer.RequestAuthentication()方法無條件添加到HTTP請求(並隨後在需要時修改以true)。

因此,理想的解決方案是讓DNOA對其認證請求參數進行更細粒度的控制,並且TwitterClient明確地刪除參數force_login=false。不幸的是,目前的DNOA代碼庫並不直接支持這一點,但可以通過創建兩個自定義類來實現相同的效果。

首先是一個自定義實現的IOAuthWebWorker這是原始DotNetOpenAuthWebConsumer類的直接拷貝,除了單行變化初始化重定向參數字典作爲一個空的字典:

using System; 
using System.Collections.Generic; 
using System.Net; 
using DotNetOpenAuth.AspNet.Clients; 
using DotNetOpenAuth.Messaging; 
using DotNetOpenAuth.OAuth; 
using DotNetOpenAuth.OAuth.ChannelElements; 
using DotNetOpenAuth.OAuth.Messages; 

namespace CustomDotNetOpenAuth 
{ 
    public class CustomDotNetOpenAuthWebConsumer : IOAuthWebWorker, IDisposable 
    { 
     private readonly WebConsumer _webConsumer; 

     public CustomDotNetOpenAuthWebConsumer(ServiceProviderDescription serviceDescription, IConsumerTokenManager tokenManager) 
     { 
      if (serviceDescription == null) throw new ArgumentNullException("serviceDescription"); 
      if (tokenManager == null) throw new ArgumentNullException("tokenManager"); 

      _webConsumer = new WebConsumer(serviceDescription, tokenManager); 
     } 

     public HttpWebRequest PrepareAuthorizedRequest(MessageReceivingEndpoint profileEndpoint, string accessToken) 
     { 
      return _webConsumer.PrepareAuthorizedRequest(profileEndpoint, accessToken); 
     } 

     public AuthorizedTokenResponse ProcessUserAuthorization() 
     { 
      return _webConsumer.ProcessUserAuthorization(); 
     } 

     public void RequestAuthentication(Uri callback) 
     { 
      var redirectParameters = new Dictionary<string, string>(); 
      var request = _webConsumer.PrepareRequestUserAuthorization(callback, null, redirectParameters); 

      _webConsumer.Channel.PrepareResponse(request).Send(); 
     } 

     public void Dispose() 
     { 
      Dispose(true); 
      GC.SuppressFinalize(this); 
     } 

     protected virtual void Dispose(bool disposing) 
     { 
      if (disposing) 
      { 
       _webConsumer.Dispose(); 
      } 
     } 
    } 
} 

另要求是自定義OAuthClient類,基於原始TwitterClient類。請注意,這需要比原始TwitterClient類多一點的代碼,因爲它也需要複製一對夫婦的是內部的DNOA基類或其他實用工具類方法:

using System; 
using System.Collections.Generic; 
using System.IO; 
using System.Text; 
using System.Xml; 
using System.Xml.Linq; 
using DotNetOpenAuth.AspNet; 
using DotNetOpenAuth.AspNet.Clients; 
using DotNetOpenAuth.Messaging; 
using DotNetOpenAuth.OAuth; 
using DotNetOpenAuth.OAuth.ChannelElements; 
using DotNetOpenAuth.OAuth.Messages; 

namespace CustomDotNetOpenAuth 
{ 
    public class CustomTwitterClient : OAuthClient 
    { 
     private static readonly string[] UriRfc3986CharsToEscape = new[] { "!", "*", "'", "(", ")" }; 

     public static readonly ServiceProviderDescription TwitterServiceDescription = 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.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest), 
      TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() }, 
     }; 

     public CustomTwitterClient(string consumerKey, string consumerSecret) 
      : this(consumerKey, consumerSecret, new AuthenticationOnlyCookieOAuthTokenManager()) 
     { 
     } 

     public CustomTwitterClient(string consumerKey, string consumerSecret, IOAuthTokenManager tokenManager) 
      : base("twitter", new CustomDotNetOpenAuthWebConsumer(TwitterServiceDescription, new SimpleConsumerTokenManager(consumerKey, consumerSecret, tokenManager))) 
     { 
     } 

     protected override AuthenticationResult VerifyAuthenticationCore(AuthorizedTokenResponse response) 
     { 
      var accessToken = response.AccessToken; 
      var userId = response.ExtraData["user_id"]; 
      var userName = response.ExtraData["screen_name"]; 

      var profileRequestUrl = new Uri("https://api.twitter.com/1/users/show.xml?user_id=" + EscapeUriDataStringRfc3986(userId)); 
      var profileEndpoint = new MessageReceivingEndpoint(profileRequestUrl, HttpDeliveryMethods.GetRequest); 
      var request = WebWorker.PrepareAuthorizedRequest(profileEndpoint, accessToken); 

      var extraData = new Dictionary<string, string> { { "accesstoken", accessToken } }; 

      try 
      { 
       using (var profileResponse = request.GetResponse()) 
       { 
        using (var responseStream = profileResponse.GetResponseStream()) 
        { 
         var document = xLoadXDocumentFromStream(responseStream); 

         AddDataIfNotEmpty(extraData, document, "name"); 
         AddDataIfNotEmpty(extraData, document, "location"); 
         AddDataIfNotEmpty(extraData, document, "description"); 
         AddDataIfNotEmpty(extraData, document, "url"); 
        } 
       } 
      } 
      catch 
      { 
       // At this point, the authentication is already successful. Here we are just trying to get additional data if we can. If it fails, no problem. 
      } 

      return new AuthenticationResult(true, ProviderName, userId, userName, extraData); 
     } 

     private static XDocument xLoadXDocumentFromStream(Stream stream) 
     { 
      const int maxChars = 0x10000; // 64k 

      var settings = new XmlReaderSettings 
       { 
       MaxCharactersInDocument = maxChars 
      }; 

      return XDocument.Load(XmlReader.Create(stream, settings)); 
     } 

     private static void AddDataIfNotEmpty(Dictionary<string, string> dictionary, XDocument document, string elementName) 
     { 
      var element = document.Root.Element(elementName); 

      if (element != null) 
      { 
       AddItemIfNotEmpty(dictionary, elementName, element.Value); 
      } 
     } 

     private static void AddItemIfNotEmpty(IDictionary<string, string> dictionary, string key, string value) 
     { 
      if (key == null) 
      { 
       throw new ArgumentNullException("key"); 
      } 

      if (!string.IsNullOrEmpty(value)) 
      { 
       dictionary[key] = value; 
      } 
     } 

     private static string EscapeUriDataStringRfc3986(string value) 
     { 
      var escaped = new StringBuilder(Uri.EscapeDataString(value)); 

      for (var i = 0; i < UriRfc3986CharsToEscape.Length; i++) 
      { 
       escaped.Replace(UriRfc3986CharsToEscape[i], Uri.HexEscape(UriRfc3986CharsToEscape[i][0])); 
      } 

      return escaped.ToString(); 
     } 
    } 
} 

已經創建這兩個自定義類,實現只需要在MVC4 AuthConfig.cs文件註冊新的CustomTwitterClient類的一個實例:

OAuthWebSecurity.RegisterClient(new CustomTwitterClient("myTwitterApiKey", "myTwitterApiSecret")); 
+0

我已經證實了這一工作。花了5分鐘。謝謝蒂姆。 – Matt