asp.net-mvc-4 – MVC4的DotNetOpenAuth TwitterClient示例不尊重先前的登录

如果我使用Internet应用程序模板创建ASP.NET MVC 4 Web应用程序,它会预先安装使用一系列OAuth和OpenID提供程序实现身份验证所需的所有组件和配置.只需将我的Twitter消费者密钥和秘密添加到AuthConfig.cs即可通过Twitter激活身份验证.

但是,它似乎没有像我期望的那样工作.

如果我尝试使用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"));

相关文章

ASP.NET与IIS是紧密联系的,由于IIS6.0与IIS7.0的工作方式的...
在之前的ASP.NET是如何在IIS下工作的这篇文章中介绍了ASP.NE...
这篇文章主要讲解了“WPF如何实现带筛选功能的DataGrid”,文...
本篇内容介绍了“基于WPF如何实现3D画廊动画效果”的有关知识...
Some samples are below for ASP.Net web form controls:(fr...
问题描述: 对于未定义为 System.String 的列,唯一有效的值...