我在寻找类似问题的解决方案时发现了您的问题。我通过制作 2 个新课程解决了这个问题,您可以在这篇coderwall 帖子中了解这些内容。
我还将在此处复制并粘贴完整的帖子:
DotNetOpenAuth.AspNet 401 未经授权的错误和持久访问令牌秘密修复
在设计我们的云电子书管理器 QuietThyme 时,我们知道每个人都和我们一样讨厌创建新帐户。我们开始寻找可以用来进行社交登录的 OAuth 和 OpenId 库。我们最终使用该DotNetOpenAuth.AspNet
库进行用户身份验证,因为它立即支持 Microsoft、Twitter、Facebook、LinkedIn 和 Yahoo 以及许多其他公司。虽然我们在设置这一切时遇到了一些问题,但最终我们只需要进行一些小的自定义即可使其大部分工作(在之前的 coderwall 帖子中描述))。我们注意到,与其他所有客户端不同,LinkedIn 客户端不会进行身份验证,从 DotNetOpenAuth 返回 401 Unauthorized Error。很快就发现这是由于签名问题造成的,在查看源代码后,我们能够确定检索到的 AccessToken 密钥没有与经过身份验证的配置文件信息请求一起使用。
这确实是有道理的,OAuthClient 类不包含检索到的访问令牌秘密的原因是它通常不需要用于身份验证目的,这是 ASP.NET OAuth 库的主要目的。
我们需要在用户登录后针对 api 发出经过身份验证的请求,以检索一些标准的个人资料信息,包括电子邮件地址和全名。我们可以通过临时使用 InMemoryOAuthTokenManager 来解决这个问题。
public class LinkedInCustomClient : OAuthClient
{
private static XDocument LoadXDocumentFromStream(Stream stream)
{
var settings = new XmlReaderSettings
{
MaxCharactersInDocument = 65536L
};
return XDocument.Load(XmlReader.Create(stream, settings));
}
/// Describes the OAuth service provider endpoints for LinkedIn.
private static readonly ServiceProviderDescription LinkedInServiceDescription =
new ServiceProviderDescription
{
AccessTokenEndpoint =
new MessageReceivingEndpoint("https://api.linkedin.com/uas/oauth/accessToken",
HttpDeliveryMethods.PostRequest),
RequestTokenEndpoint =
new MessageReceivingEndpoint("https://api.linkedin.com/uas/oauth/requestToken?scope=r_basicprofile+r_emailaddress",
HttpDeliveryMethods.PostRequest),
UserAuthorizationEndpoint =
new MessageReceivingEndpoint("https://www.linkedin.com/uas/oauth/authorize",
HttpDeliveryMethods.PostRequest),
TamperProtectionElements =
new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() },
//ProtocolVersion = ProtocolVersion.V10a
};
private string ConsumerKey { get; set; }
private string ConsumerSecret { get; set; }
public LinkedInCustomClient(string consumerKey, string consumerSecret)
: this(consumerKey, consumerSecret, new AuthenticationOnlyCookieOAuthTokenManager()) { }
public LinkedInCustomClient(string consumerKey, string consumerSecret, IOAuthTokenManager tokenManager)
: base("linkedIn", LinkedInServiceDescription, new SimpleConsumerTokenManager(consumerKey, consumerSecret, tokenManager))
{
ConsumerKey = consumerKey;
ConsumerSecret = consumerSecret;
}
//public LinkedInCustomClient(string consumerKey, string consumerSecret) :
// base("linkedIn", LinkedInServiceDescription, consumerKey, consumerSecret) { }
/// Check if authentication succeeded after user is redirected back from the service provider.
/// The response token returned from service provider authentication result.
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
Justification = "We don't care if the request fails.")]
protected override AuthenticationResult VerifyAuthenticationCore(AuthorizedTokenResponse response)
{
// See here for Field Selectors API http://developer.linkedin.com/docs/DOC-1014
const string profileRequestUrl =
"https://api.linkedin.com/v1/people/~:(id,first-name,last-name,headline,industry,summary,email-address)";
string accessToken = response.AccessToken;
var profileEndpoint =
new MessageReceivingEndpoint(profileRequestUrl, HttpDeliveryMethods.GetRequest);
try
{
InMemoryOAuthTokenManager imoatm = new InMemoryOAuthTokenManager(ConsumerKey, ConsumerSecret);
imoatm.ExpireRequestTokenAndStoreNewAccessToken(String.Empty, String.Empty, accessToken, (response as ITokenSecretContainingMessage).TokenSecret);
WebConsumer w = new WebConsumer(LinkedInServiceDescription, imoatm);
HttpWebRequest request = w.PrepareAuthorizedRequest(profileEndpoint, accessToken);
using (WebResponse profileResponse = request.GetResponse())
{
using (Stream responseStream = profileResponse.GetResponseStream())
{
XDocument document = LoadXDocumentFromStream(responseStream);
string userId = document.Root.Element("id").Value;
string firstName = document.Root.Element("first-name").Value;
string lastName = document.Root.Element("last-name").Value;
string userName = firstName + " " + lastName;
string email = String.Empty;
try
{
email = document.Root.Element("email-address").Value;
}
catch(Exception)
{
}
var extraData = new Dictionary<string, string>();
extraData.Add("accesstoken", accessToken);
extraData.Add("name", userName);
extraData.AddDataIfNotEmpty(document, "headline");
extraData.AddDataIfNotEmpty(document, "summary");
extraData.AddDataIfNotEmpty(document, "industry");
if(!String.IsNullOrEmpty(email))
{
extraData.Add("email",email);
}
return new AuthenticationResult(
isSuccessful: true, provider: this.ProviderName, providerUserId: userId, userName: userName, extraData: extraData);
}
}
}
catch (Exception exception)
{
return new AuthenticationResult(exception);
}
}
}
这是从 Microsoft 编写的基本 LinkedIn 客户端更改的部分。
InMemoryOAuthTokenManager imoatm = new InMemoryOAuthTokenManager(ConsumerKey, ConsumerSecret);
imoatm.ExpireRequestTokenAndStoreNewAccessToken(String.Empty, String.Empty, accessToken, (response as ITokenSecretContainingMessage).TokenSecret);
WebConsumer w = new WebConsumer(LinkedInServiceDescription, imoatm);
HttpWebRequest request = w.PrepareAuthorizedRequest(profileEndpoint, accessToken);
不幸的是,该IOAuthTOkenManger.ReplaceRequestTokenWithAccessToken(..)
方法直到方法返回后才会执行VerifyAuthentication()
,因此我们必须创建一个新的 TokenManager 并WebConsumer
使用HttpWebRequest
我们刚刚检索到的 AccessToken 凭据创建一个和。
这解决了我们简单的 401 Unauthorized 问题。
现在,如果您想在身份验证过程之后保留 AccessToken 凭据,会发生什么?例如,这对于 DropBox 客户端可能很有用,您希望将文件异步同步到用户的 DropBox。问题可以追溯到 AspNet 库的编写方式,假设 DotNetOpenAuth 仅用于用户身份验证,而不是作为进一步 OAuth api 调用的基础。值得庆幸的是,修复相当简单,我所要做的就是修改基础AuthetnicationOnlyCookieOAuthTokenManger
,以便该ReplaceRequestTokenWithAccessToken(..)
方法存储新的 AccessToken 密钥和秘密。
/// <summary>
/// Stores OAuth tokens in the current request's cookie
/// </summary>
public class PersistentCookieOAuthTokenManagerCustom : AuthenticationOnlyCookieOAuthTokenManager
{
/// <summary>
/// Key used for token cookie
/// </summary>
private const string TokenCookieKey = "OAuthTokenSecret";
/// <summary>
/// Primary request context.
/// </summary>
private readonly HttpContextBase primaryContext;
/// <summary>
/// Initializes a new instance of the <see cref="AuthenticationOnlyCookieOAuthTokenManager"/> class.
/// </summary>
public PersistentCookieOAuthTokenManagerCustom() : base()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AuthenticationOnlyCookieOAuthTokenManager"/> class.
/// </summary>
/// <param name="context">The current request context.</param>
public PersistentCookieOAuthTokenManagerCustom(HttpContextBase context) : base(context)
{
this.primaryContext = context;
}
/// <summary>
/// Gets the effective HttpContext object to use.
/// </summary>
private HttpContextBase Context
{
get
{
return this.primaryContext ?? new HttpContextWrapper(HttpContext.Current);
}
}
/// <summary>
/// Replaces the request token with access token.
/// </summary>
/// <param name="requestToken">The request token.</param>
/// <param name="accessToken">The access token.</param>
/// <param name="accessTokenSecret">The access token secret.</param>
public new void ReplaceRequestTokenWithAccessToken(string requestToken, string accessToken, string accessTokenSecret)
{
//remove old requestToken Cookie
//var cookie = new HttpCookie(TokenCookieKey)
//{
// Value = string.Empty,
// Expires = DateTime.UtcNow.AddDays(-5)
//};
//this.Context.Response.Cookies.Set(cookie);
//Add new AccessToken + secret Cookie
StoreRequestToken(accessToken, accessTokenSecret);
}
}
然后要使用它PersistentCookieOAuthTokenManager
,您需要做的就是修改您的 DropboxClient 构造函数,或您希望保留 AccessToken Secret 的任何其他客户端
public DropBoxCustomClient(string consumerKey, string consumerSecret)
: this(consumerKey, consumerSecret, new PersistentCookieOAuthTokenManager()) { }
public DropBoxCustomClient(string consumerKey, string consumerSecret, IOAuthTokenManager tokenManager)
: base("dropBox", DropBoxServiceDescription, new SimpleConsumerTokenManager(consumerKey, consumerSecret, tokenManager))
{}