供参考,我的代码:
public class DropBoxClient : OAuthClient { public static readonly ServiceProviderDescription DropBoxServiceDescription = new ServiceProviderDescription { RequestTokenEndpoint = new MessageReceivingEndpoint("https://api.dropBox.com/1/oauth/request_token",HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),UserAuthorizationEndpoint = new MessageReceivingEndpoint("https://www.dropBox.com/1/oauth/authorize",AccessTokenEndpoint = new MessageReceivingEndpoint("https://api.dropBox.com/1/oauth/access_token",TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new PlaintextSigningBindingElement() } }; public DropBoxClient(string consumerKey,string consumerSecret) : this(consumerKey,consumerSecret,new AuthenticationOnlyCookieOAuthTokenManager()) { } public DropBoxClient(string consumerKey,string consumerSecret,IOAuthTokenManager tokenManager) : base("dropBox",DropBoxServiceDescription,new SimpleConsumerTokenManager(consumerKey,tokenManager)) { } protected override DotNetOpenAuth.AspNet.AuthenticationResult VerifyAuthenticationCore(DotNetOpenAuth.OAuth.Messages.AuthorizedTokenResponse response) { var profileEndpoint = new MessageReceivingEndpoint("https://api.dropBox.com/1/account/info",HttpDeliveryMethods.GetRequest); HttpWebRequest request = this.WebWorker.PrepareAuthorizedRequest(profileEndpoint,response.AccessToken); try { using (WebResponse profileResponse = request.GetResponse()) { using (Stream profileResponseStream = profileResponse.GetResponseStream()) { using (StreamReader reader = new StreamReader(profileResponseStream)) { string jsonText = reader.ReadToEnd(); JavaScriptSerializer jss = new JavaScriptSerializer(); dynamic jsonData = jss.DeserializeObject(jsonText); Dictionary<string,string> extraData = new Dictionary<string,string>(); extraData.Add("displayName",jsonData.display_name ?? "Unknown"); extraData.Add("userId",jsonData.uid ?? "Unknown"); return new DotNetOpenAuth.AspNet.AuthenticationResult(true,ProviderName,extraData["userId"],extraData["displayName"],extraData); } } } } catch (WebException ex) { using (Stream s = ex.Response.GetResponseStream()) { using (StreamReader sr = new StreamReader(s)) { string body = sr.ReadToEnd(); return new DotNetOpenAuth.AspNet.AuthenticationResult(new Exception(body,ex)); } } } } }
解决方法
我还会在这里复制并粘贴完整帖子:
DotNetOpenAuth.AspNet 401未经授权的错误和持久访问令牌秘密修复
在设计我们的云电子书管理器QuietThyme时,我们知道每个人都讨厌像我们一样创建新帐户.我们开始寻找可以利用社交登录的OAuth和OpenId库.我们最终使用DotNetOpenAuth.AspNet库进行用户身份验证,因为它支持Microsoft,Twitter,Facebook,LinkedIn和Yahoo等许多其他人.虽然我们有一些问题需要设置,但最后我们只需要进行一些小的自定义来完成大部分工作(在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",UserAuthorizationEndpoint = new MessageReceivingEndpoint("https://www.linkedin.com/uas/oauth/authorize",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,new AuthenticationOnlyCookieOAuthTokenManager()) { } public LinkedInCustomClient(string consumerKey,IOAuthTokenManager tokenManager) : base("linkedIn",LinkedInServiceDescription,tokenManager)) { ConsumerKey = consumerKey; ConsumerSecret = consumerSecret; } //public LinkedInCustomClient(string consumerKey,string consumerSecret) : // base("linkedIn",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,(response as ITokenSecretContainingMessage).TokenSecret); WebConsumer w = new WebConsumer(LinkedInServiceDescription,imoatm); HttpWebRequest request = w.PrepareAuthorizedRequest(profileEndpoint,accessToken);
不幸的是,IOAuthTOkenManger.ReplaceRequestTokenWithAccessToken(..)方法直到VerifyAuthentication()方法返回后才会执行,因此我们必须创建一个新的TokenManager,并使用我们刚刚检索的AccessToken凭据创建一个WebConsumer和HttpWebRequest.
这解决了我们简单的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,new PersistentCookieOAuthTokenManager()) { } public DropBoxCustomClient(string consumerKey,IOAuthTokenManager tokenManager) : base("dropBox",DropBoxServiceDescription,tokenManager)) {}