Tim*_*ter 4 dotnetopenauth twitter-oauth asp.net-mvc-4
如果我使用Internet Application模板创建ASP.NET MVC 4 Web应用程序,它会预先安装使用一系列OAuth和OpenID提供程序实现身份验证所需的所有组件和配置.只需添加我的Twitter消费者密钥和秘密即可AuthConfig.cs通过Twitter激活身份验证.
但是,它似乎没有像我期望的那样工作.
如果我尝试使用Twitter进行身份验证,则无论我是否已经登录Twitter,它都会显示Twitter登录页面.它还让我退出Twitter,因此我不得不在下次浏览器访问Twitter时重新进行身份验证.
这是一个错误,还是需要一些额外的配置来将其转换为更常见的无缝工作流程(对于像Google这样的其他提供商正常工作)?
提前致谢.
蒂姆
如果有其他人遇到这个问题,我会在这里介绍我发现的内容(以及一个相当丑陋的解决方法).
使用Fiddler来检查DotNetOpenAuthTwitter和Twitter 之间的HTTP流量,很明显,身份验证请求包含force_login=falsequerystring参数,这表明DNOA正常工作.但是,如果我使用Fiddler的脚本功能来修改出站请求并force_login完全删除参数,那么一切都会正常运行.我猜测Twitter的实现在这里是错误的,通过将任何force_login参数的存在视为等同于force_login=true.
由于我不认为可以让Twitter修改其API的行为,因此我调查了是否有更易于访问的解决方案.
查看DNOA代码,我看到该force_login=false方法无条件地将该参数添加到HTTP请求中DotNetOpenAuthWebConsumer.RequestAuthentication()(随后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();
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
另一个要求是OAuthClient基于原始TwitterClient类的自定义类.请注意,这需要比原始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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
创建这两个自定义类之后,实现只需要CustomTwitterClient在MVC4 AuthConfig.cs文件中注册新类的实例:
OAuthWebSecurity.RegisterClient(new CustomTwitterClient("myTwitterApiKey", "myTwitterApiSecret"));
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3065 次 |
| 最近记录: |