但是,它似乎没有像我期望的那样工作.
如果我尝试使用Twitter进行身份验证,则无论我是否已登录Twitter,它都会显示Twitter登录页面.它还让我退出Twitter,因此我不得不在下次浏览器访问Twitter时重新进行身份验证.
这是一个错误,还是需要一些额外的配置才能将其转换为更常见的无缝工作流程(对于像Google这样的其他提供商而言正常工作)?
提前致谢.
蒂姆
解决方法
使用Fiddler检查DotNetOpenAuth和Twitter之间的HTTP流量,很明显,身份验证请求包含force_login = false查询字符串参数,这表明DNOA正常工作.但是,如果我使用Fiddler的脚本功能来修改出站请求并完全删除force_login参数,那么一切都会正常运行.我猜测Twitter的实现在这里是错误的,通过将任何force_login参数的存在视为等同于force_login = true.
由于我不认为可以让Twitter修改其API的行为,因此我调查了是否有更易于访问的解决方案.
查看DNOA代码,我发现dotNetOpenAuthWebConsumer.RequestAuthentication()方法无条件地将force_login = false参数添加到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(); } } } }
另一个要求是基于原始TwitterClient类的自定义OAuthClient类.请注意,这需要比原始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",AccessTokenEndpoint = new MessageReceivingEndpoint("https://api.twitter.com/oauth/access_token",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,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,"location"); AddDataIfNotEmpty(extraData,"description"); AddDataIfNotEmpty(extraData,"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 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"));