5

現在、アプリケーションにDropboxOAuthクライアントを実装する作業を行っています。私が最後に到達するまで、それはかなり苦痛のないプロセスでした。承認した後、ユーザーデータにアクセスしようとすると、トークンが無効であるという401がDropboxから返されます。Dropboxフォーラムで質問しましたが、Dropboxが返すaccess_token_secretがリクエストに含まれていないようです。Fiddlerを使用して秘密を掘り起こし、それをリクエストURLに追加することができましたが、正常に機能したので、それは間違いなく問題です。では、DotNetOpenAuthがアクセストークンを返すときに、アクセストークンシークレットを返さないのはなぜですか?

参考までに、私のコード:

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", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
        AccessTokenEndpoint = new MessageReceivingEndpoint("https://api.dropbox.com/1/oauth/access_token", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
        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, consumerSecret, 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));
                }
            }
        }
    }
}
4

4 に答える 4

5

同様の問題の解決策を探していたときに、あなたの質問を見つけました。私は2つの新しいクラスを作成することでそれを解決しました。これについては、このcoderwallの投稿で読むことができます。

また、投稿全体をここにコピーして貼り付けます。


DotNetOpenAuth.AspNet401不正なエラーと永続的なアクセストークンの秘密の修正

Cloud Ebook ManagerであるQuietThymeを設計するとき、私たちと同じように、誰もが新しいアカウントを作成することを嫌うことを知っていました。ソーシャルログインを可能にするために活用できるOAuthおよびOpenIdライブラリを探し始めました。このDotNetOpenAuth.AspNetライブラリは、Microsoft、Twitter、Facebook、LinkedIn、Yahooなど、すぐにサポートされるため、ユーザー認証に使用することになりました。すべてを設定する際にいくつかの問題が発生しましたが、最終的には、ほとんどを機能させるためにいくつかの小さなカスタマイズを行うだけで済みました(以前のcoderwallの投稿で説明されています))。他のすべてとは異なり、LinkedInクライアントは認証されず、DotNetOpenAuthから401UnauthorizedErrorが返されることに気づきました。これは署名の問題が原因であることがすぐに明らかになり、ソースを調べた後、取得したAccessTokenシークレットが認証済みプロファイル情報要求で使用されていないことがわかりました。

OAuthClientクラスに取得したアクセストークンシークレットが含まれていない理由は、ASP.NETOAuthライブラリの主な目的である認証目的には通常必要ないためです。

ユーザーがログインした後、メールアドレスやフルネームなどの標準的なプロファイル情報を取得するために、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を作成し、取得したAccessToken資格情報を使用してWebConsumerを作成する必要があります。HttpWebRequest

これにより、単純な401Unauthorizedの問題が解決されます。

認証プロセス後にAccessToken資格情報を保持したい場合はどうなりますか?これは、たとえば、ファイルをユーザーのDropBoxに非同期的に同期したいDropBoxクライアントに役立つ可能性があります。問題はAspNetライブラリの作成方法に戻り、DotNetOpenAuthはユーザーの認証にのみ使用され、今後のOAuthapi呼び出しの基礎としては使用されないと想定されていました。ありがたいことに、修正はかなり簡単でした。私がしなければならなかった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))
    {}
于 2012-11-05T14:21:40.833 に答える
0

OAuthClientクラスにアクセストークンシークレットが含まれていない理由は、通常、ASP.NETOAuthライブラリの主な目的である認証目的には必要ないためです。

ただし、ケースでアクセストークンシークレットを取得する場合は、上記のようにVerifyAuthenticationCore()の代わりに、VerifyAuthentication()メソッドをオーバーライドできます。VerificationAuthentication()内で、WebWorker.ProcessUserAuthorization()を呼び出してログインを検証し、返されたAuthorizedTokenResponseオブジェクトからトークンシークレットにアクセスできます。

于 2012-09-20T18:30:55.213 に答える
0

いくつか掘り下げた後、コンストラクターロジックを次のように変更することでこれを解決することができました。

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, consumerSecret, tokenManager))
{
}

になります

public DropboxClient(string consumerKey, string consumerSecret) : 
        base("dropbox", DropboxServiceDescription, consumerKey, consumerSecret)
    {
    }

DNOAソースを掘り下げると、コンシューマキーとシークレットだけでOAuthClient(私の基本クラス)を構築すると、SimpleConsumerTokenManagerの代わりにInMemoryOAuthTokenManagerが使用されることがわかります。理由はわかりませんが、アクセストークンシークレットが承認されたリクエストの署名に適切に追加され、すべてが機能します。うまくいけば、これは他の誰かを助けるでしょう。それまでの間、これを行うための(私が見つけることができる)ネット上のガイダンスはゼロであるため、ブログ投稿のためにこれをクリーンアップする可能性があります。

編集:同僚が指摘したように、これは1つの要求を処理するので、答えを元に戻すつもりですが、メモリ内マネージャーを使用しているので、完全にラウンドトリップしてブラウザ(私は想定しています)。したがって、ここでの根本的な問題は、アクセストークンの秘密を取得する必要があることだと思いますが、その方法はまだわかりません。

于 2012-09-20T21:48:32.080 に答える
0

シークレットが応答で提供されないという元の質問については、verifyAuthenticationCore関数で応答を取得したときにシークレットがすぐそこにあります。あなたはこれらの両方を次のように取得します:

  string token = response.AccessToken; ;
  string secret = (response as ITokenSecretContainingMessage).TokenSecret; 
于 2013-10-30T12:10:10.830 に答える