2012-10-23 2 views
4

Internet Application 템플릿을 사용하여 ASP.NET MVC 4 웹 응용 프로그램을 만들면 다양한 OAuth 및 OpenID 공급자를 사용하여 인증을 구현하는 데 필요한 모든 구성 요소와 구성이 사전 설치됩니다. 단지 Twitter 고객 키와 비밀을 AuthConfig.cs에 추가하면 Twitter를 통해 인증이 활성화됩니다.MVC4의 DotNetOpenAuth TwitterClient 샘플이 이전 로그인을 고려하지 않음

그러나 예상대로 작동하지 않는 것 같습니다.

트위터를 사용하여 인증을 시도하면 트위터에 이미 사인온되었는지 여부에 관계없이 항상 트위터 사인온 페이지가 표시됩니다. 또한 트위터에서 나를 로그 아웃하므로 다음 브라우저 방문시 트위터에 다시 인증해야합니다.

버그입니까? 더 일반적인 워크 플로 (Google과 같은 다른 공급 업체에서 올바르게 작동하는)로 변환하는 데 필요한 추가 구성이 있습니까?

미리 감사드립니다. 다른

답변

8

경우 아무도 내가 (함께 오히려 추한 해결에) 발견 한 내용을이 문제에 대한 위로, 내가 여기에 제시 것이다 온다.

DotNetOpenAuth과 Twitter 사이의 HTTP 트래픽을 조사하기 위해 Fiddler를 사용하면 인증 요청에 force_login=false querystring 매개 변수가 포함되어있어 DNOA가 올바르게 작동하고 있음을 알 수 있습니다. 그러나 Fiddler의 스크립팅 기능을 사용하여 아웃 바운드 요청을 수정하고 force_login 매개 변수를 모두 제거하면 모든 것이 올바르게 작동하기 시작합니다. 어떤 force_login 매개 변수의 존재를 force_login=true과 같은 것으로 처리함으로써 트위터의 구현이 잘못되었다고 생각합니다.

Twitter에서 API의 동작을 수정하는 것이 가능할 것이라고 생각하지 않기 때문에 좀 더 접근하기 쉬운 솔루션이 있는지 조사했습니다. DNOA 코드를 찾고

, 난 force_login=false 파라미터 무조건 DotNetOpenAuthWebConsumer.RequestAuthentication() 방법에 의해 HTTP 요청에 추가 된 (및 필요한 경우 후속 적으로 개질 true)되는 것을 알 수있다.

이상적인 솔루션은 DNOA가 인증 요청 매개 변수를보다 세밀하게 제어하고 TwitterClientforce_login=false 매개 변수를 명시 적으로 제거하는 것입니다. 안타깝게도 현재의 DNOA 코드베이스는 이것을 직접 지원하지는 않지만 두 개의 사용자 정의 클래스를 생성하여 동일한 효과를 얻을 수 있습니다.

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(); 
      } 
     } 
    } 
} 

다른 :

첫번째 빈 사전로 리디렉션 파라미터 사전 초기화 한 줄 변화 이격 원래 DotNetOpenAuthWebConsumer 클래스의 직접 카피 IOAuthWebWorker의 맞춤 구현 요구 사항은 원래 TwitterClient 클래스를 기반으로하는 맞춤 OAuthClient 클래스입니다.

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(); 
     } 
    } 
} 

이 두 개의 사용자 정의 클래스를 만든 데 : 그것은 또한 DNOA 기본 클래스 또는 다른 유틸리티 클래스 내부에있는 방법 중 몇 가지를 복제하는 데 필요로이 원래 TwitterClient 클래스보다 약간 더 많은 코드를 필요로합니다 구현은 단순히 MVC4 AuthConfig.cs 파일에 새로운 CustomTwitterClient 클래스의 인스턴스를 등록 수반 :

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

나는이 작동 확인했습니다. 그것은 5 분이 걸렸다. Tim 감사합니다. – Matt

관련 문제