我在尋找類似問題的解決方案時發現了您的問題。我通過製作2個新課程來解決這個問題,你可以在這個coderwall post中看到。
我也將在這裏複製並粘貼完整的信息:
DotNetOpenAuth.AspNet 401未授權錯誤和持久訪問令牌密鑰固定
在設計QuietThyme,我們的雲電子書經理,我們知道每個人都喜歡創造新的賬戶,就像我們一樣。我們開始尋找OAuth和OpenId庫,我們可以利用這些庫來進行社交登錄。我們最終使用DotNetOpenAuth.AspNet
庫進行用戶身份驗證,因爲它支持微軟,Twitter,Facebook,LinkedIn和雅虎等許多其他用戶。儘管我們遇到了一些問題,但最終我們只需要進行一些小的自定義工作即可實現其中的大部分工作(如previous coderwall post中所述)。我們注意到,與所有其他人不同,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);
}
}
}
以下是微軟編寫的基礎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和使用我們剛剛取得的憑證的accessToken創建WebConsumer
和HttpWebRequest
。
這解決了我們簡單的401未授權問題。
現在如果您想在驗證過程後持續保存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祕密
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))
{}
我知道有一個更好的方式來格式化代碼,但我不能爲我的生活找到它。點擊問題中的代碼按鈕似乎不起作用。如果有人想告訴如何解決這個問題,非常感謝。 –
代碼格式現在基於標籤,並且您的帖子中沒有任何語言特定的標籤,因此它沒有任何操作。我在代碼上面添加了<! - language:lang-cs - >強制它突出顯示它。請參閱http://meta.stackexchange.com/a/128910/190311 –