如果我使用 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/