Ich habe Ihre Frage gefunden, als ich nach einer Lösung für ein ähnliches Problem suchte. Ich habe es gelöst, indem ich 2 neue Klassen gemacht habe, über die man in dieser coderwall post lesen kann.
Ich werde auch hier den vollständigen Beitrag kopieren und einfügen:
DotNetOpenAuth.AspNet 401 Unauthorized Fehler und Persistent Zugriffstoken Geheimnis Fix
Wenn QuietThyme Gestaltung unserer Cloud-Ebook-Manager, wussten wir, dass jeder es hasst, neue Konten genauso zu erstellen wie wir. Wir haben angefangen, nach OAuth- und OpenId-Bibliotheken zu suchen, die wir für den sozialen Login verwenden können. Wir haben am Ende die DotNetOpenAuth.AspNet
Bibliothek für die Benutzerauthentifizierung verwendet, weil sie Microsoft, Twitter, Facebook, LinkedIn und Yahoo und viele andere direkt aus dem Bug unterstützt. Während wir einige Probleme hatten, die alles aufstellten, mussten wir am Ende nur ein paar kleine Anpassungen vornehmen, um das meiste davon zum Laufen zu bringen (beschrieben in einer previous coderwall post).Uns ist aufgefallen, dass sich der LinkedIn-Client im Gegensatz zu allen anderen nicht authentifizieren und einen 401-nicht autorisierten Fehler von DotNetOpenAuth zurückgeben kann. Es wurde schnell klar, dass dies auf ein Signaturproblem zurückzuführen war, und nachdem wir uns die Quelle angesehen hatten, konnten wir feststellen, dass das abgerufene AccessToken-Geheimnis nicht mit der authentifizierten Profil-Informationsanforderung verwendet wurde.
Es ist sinnvoll, da die OAuthClient-Klasse das abgerufene Zugriffstokengeheimnis nicht enthält, weil es normalerweise nicht für Authentifizierungszwecke benötigt wird. Dies ist der Hauptzweck der OAuth-Bibliothek von ASP.NET.
Wir mussten authentifizierte Anfragen gegen die API machen, nachdem der Benutzer sich angemeldet hat, um einige Standardprofilinformationen, einschließlich E-Mail-Adresse und vollständiger Name, abzurufen. Wir konnten dieses Problem lösen, indem wir vorübergehend einen InMemoryOAuthTokenManager verwenden.
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);
}
}
}
Hier ist der Abschnitt, der von dem von Microsoft geschriebenen Basis-LinkedIn-Client geändert wurde.
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);
Leider ist die IOAuthTOkenManger.ReplaceRequestTokenWithAccessToken(..)
Methode erhält erst nach der VerifyAuthentication()
Methode zurückzugeführt, so dass wir, anstatt einen neuen TokenManager erstellen müssen und und ein WebConsumer
und HttpWebRequest
die AccessToken Anmeldeinformationen erstellen verwenden wir nur abgerufen werden.
Dies löst unser einfaches 401 Unauthorized-Problem.
Was passiert nun, wenn Sie die AccessToken-Anmeldeinformationen nach dem Authentifizierungsprozess beibehalten möchten? Dies könnte beispielsweise für einen DropBox-Client nützlich sein, bei dem Sie Dateien asynchron mit der DropBox eines Benutzers synchronisieren möchten. Das Problem geht zurück auf die Art und Weise, wie die AspNet-Bibliothek geschrieben wurde. Es wurde angenommen, dass DotNetOpenAuth nur für die Benutzerauthentifizierung und nicht als Grundlage für weitere OAuth-API-Aufrufe verwendet wird. Zum Glück war die Lösung ziemlich einfach, ich musste lediglich die Basis ändern AuthetnicationOnlyCookieOAuthTokenManger
, so dass die Methode ReplaceRequestTokenWithAccessToken(..)
den neuen AccessToken-Schlüssel und die geheimen Schlüssel speicherte.
/// <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);
}
}
Dann ist dieses PersistentCookieOAuthTokenManager
alles, was Sie tun müssen, verwenden, um sich Ihre DropboxClient Konstruktor oder andere Client ändern, wo Sie die AccessToken Geheimnis
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))
{}
Ich weiß, es gibt eine schönere Möglichkeit, den Code zu formatieren, aber ich kann nicht für das Leben von mir finden. Das Klicken auf die Code-Schaltfläche in der Frage schien nicht zu funktionieren. Wenn jemand Ratschläge geben möchte, wie das zu beheben ist, sehr geschätzt. –
Code-Formatierung basiert jetzt auf Tags und Sie hatten keine sprachspezifischen Tags in Ihrem Post, so dass es nichts getan hat. Ich habe über Ihrem Code hinzugefügt, um die Hervorhebung zu erzwingen. Siehe http://meta.stackexchange.com/a/128910/190311 –