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

标签 asp.net-mvc-4 twitter-oauth dotnetopenauth

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

但是,它似乎不像我期望的那样工作。

如果我尝试使用 Twitter 进行身份验证,它总是会显示一个 Twitter 登录页面,无论我是否已经登录到 Twitter。它还将我从 Twitter 上注销,以便我在下次浏览器访问 Twitter 时被迫重新进行身份验证。

这是一个错误,还是需要一些额外的配置才能将其转换为更常见的无缝工作流程(这对于 Google 等其他提供商也能正常工作)?

提前致谢。

蒂姆

最佳答案

万一其他人遇到这个问题,我会在这里展示我的发现(以及一个相当丑陋的解决方法)。

使用 Fiddler 检查 DotNetOpenAuth 之间的 HTTP 流量和推特,很明显认证请求包含force_login=false querystring 参数,这表明 DNOA 工作正常。但是,如果我使用 Fiddler 的脚本功能来修改出站请求并删除 force_login参数完全,一切开始正常工作。通过处理任何 force_login 的存在,我猜测 Twitter 的实现在这里有问题。参数等同于 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类(class)。请注意,这需要比原始 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();
        }
    }
}

创建了这两个自定义类后,实现只需注册一个新 CustomTwitterClient 的实例。 MVC4 中的类 AuthConfig.cs文件:
OAuthWebSecurity.RegisterClient(new CustomTwitterClient("myTwitterApiKey", "myTwitterApiSecret"));

关于asp.net-mvc-4 - MVC4 的 DotNetOpenAuth TwitterClient 示例不尊重事先登录,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13034084/

相关文章:

openid - 我们的场景推荐什么 : OpenID + self-provider or SAML?

c# - 读取 WebException 的响应流

iphone - iphone oauth twitter 上的自定义 URL 方案

android - 是否有任何 Twitter 演示示例可以在 Twitter 中使用 OAuth 在 Twitter 上发布照片?

oauth-2.0 - DotNetOpenAuth OAuth2.0 状态参数

asp.net-mvc - Entity Framework 5 迁移 : Setting up an initial migration and single seed of the database

curl - 使用 Curl 和 Windows 命令行将照片发布到 Twitter

c# - 如何在空 MVC 4 项目模板中添加 ASP.NET 成员资格提供程序?

c# - CustomAttribute反射(reflect)html属性MVC5

html - 如何在 CSS 中为 MVC 4 Razor 创建类层次结构?