IIJIMASが勉強しようとしています。

これからこれから♪

目次

Blog 利用状況

ニュース

共著:




わんくま同盟 東京勉強会 #99 私の資料

個数を数えてみたい!

わんくま同盟 東京勉強会 #91 私の資料

きっと楽しいトポロジー

わんくま同盟 東京勉強会 #45 数学デー 私の資料(pptxとxlsx)

確率の不思議

わんくま同盟 東京勉強会 #37 私のLT資料

数の冪の和の式 を求めてみよう!

デブサミ09でわんくま同盟の紹介をしたスライド

わんくま同盟 東京勉強会 #26 LT祭りの私のスライド

わんくま同盟 東京勉強会 #23の私のスライド

わんくま同盟 東京勉強会 #18の私のスライド


自己紹介(仮)

IIJIMASと申します。
東京都多摩市在住です。多摩川のそばです。
猫好きです。
IIJIMASのSは何って言われます。 IIJIMASって
なんて読むかは自由です。
魚や網間企業とゲームとは無関係です。
数学とか宇宙とかの読み物とか好きです。
血液型:果汁100%A
メタボ予備軍。。。orz
Twitter

...............
MVP 審査応募ページ~ 求む、日本のMVP。
MCTS:.NET Framework 2.0: Windows アプリケーション
MCTS:.NET Framework 2.0: Windows アプリケーション

MCSD.NET:マイクロソフト認定ソリューションデベロッパー(MCSD) Microsoft .NET トラック
Microsoft Certified Solution Developer for Microsoft.NET

リンク

わんくま同盟
わんくま同盟

C#VB.NET掲示板
C#VB.NET掲示板

わんくま同盟 Blog's

ちょっと一言(仮)


書庫

日記カテゴリ

2010年8月1日 #

自作TwitterクライアントをOAuth認証に対応させる。(2)

自作TwitterクライアントをOAuth認証に対応させる。の続き。

  1. クライアントがTwitterにRequest Tokenを要求する。
  2. TwitterがクライアントにRequest Tokenを返す。
  3. ユーザにAuthorizeページでクライアントアプリケーションを「許可」してもらう。
  4. Twitterが暗証番号を表示する。
  5. ユーザがクライアントに暗証番号を入力する。
  6. クライアントがTwitterにAccess Tokenを要求する。
  7. TwitterがクライアントにAccess Tokenを返す。
  8. クライアントにAccess TokenとAccess TokenSecretを保存する。
  9. 以降、クライアントはAccess Tokenを含めてAPIの各リクエストをする。

各手順では細かいパラメータがあります。このうちクライアントに関連づける必須のパラメータConsumer keyと対になるConsumer Secretを取得するためにクライアント開発者はいかのページアクセスしてフォームを埋める必要があります。
「Twitter / Applications: Register」 http://twitter.com/apps/new

Application Name:にアプリケーション名(twitterという文字列を含むことができません!)を入力、Description:に説明(10文字以上)を入力します。Application Website:は必須の項目になっていますので、今回は自分のホームページを設定します。公開クライアントの場合はクライアントのダウンロードサイトやサポートサイト等を指定します。Application Type:はデスクトップアプリケーションの場合Clientを選択します。

これらはあとからでも修正できます。

入力を終え[Save]をクリックすると、Consumer key と Consumer secret の値が表示されます。アプリケーションの端末に保存して使用します。

自分のアプリは複数登録できて、以下のページより入力した情報の確認と修正が行えます。
[Link]「Twitter / Applications」 http://twitter.com/apps

あとは実装です。私の自作クライアントはC#を使用していましたので、「Twitter API Wiki / OAuth Examples」 のリンク先の.NETのさらにリンク先などを参考にしました。それ以外にもすでにいろいろなところでライブラリやサンプルも提供されているようですが、勉強も兼ねてそれらを参考にあえて自分でライブラリ部分もコーディングしました。アプリケーションは公開しませんが、改めて作成して、ライブラリ部分のコードの主要部分をこちらに公開しようかと思います。


Const.cs(定数クラス、IntelliSenseを使いたいだけです()

 

namespace TwitterUtils

{

    /// <summary>

    /// constant

    /// </summary>

    public static class Const

    {

        //URL

        public const string URL_TWITTER = "http://twitter.com/";

        public const string URL_API_TWITTER = "http://api.twitter.com/";

        public const string URL_API_TWITTER_1 = "http://api.twitter.com/1/";

        //API

        public const string API_REQUEST_TOKEN = URL_API_TWITTER + "oauth/request_token";

        public const string API_AUTHORIZE = URL_API_TWITTER + "oauth/authorize";

        public const string API_ACCESS_TOKEN = URL_API_TWITTER + "oauth/access_token";

        public const string API_STATUSES = URL_API_TWITTER_1 + "statuses/";

        //API_METHOD

        public const string API_METHOD_PUBLIC_TIMELINE = "public_timeline";

        public const string API_METHOD_HOME_TIMELINE = "home_timeline";

        public const string API_METHOD_FRIENDS_TIMELINE = "friends_timeline";

        public const string API_METHOD_USER_TIMELINE = "user_timeline ";

        public const string API_METHOD_MENTIONS = "mentions";

        public const string API_METHOD_FRIENDS = "friends";

        public const string API_METHOD_FOLLOWERS = "followers";

        public const string API_METHOD_SHOW = "show";

        public const string API_METHOD_UPDATE = "update";

        public const string API_METHOD_DESTROY = "destroy  ";

        public const string API_METHOD_RETWEET = "retweet";

        public const string API_METHOD_RETWEETS = "retweets";

 

        //FORMAT

        public const string FORMAT_XML = ".xml";

        public const string FORMAT_JSON = ".json";

        public const string FORMAT_ATOM = ".atom";

        public const string FORMAT_RSS = ".rss";

 

        //URL query parameter

        public const string QUERY_OAUTH_CONSUMER_KEY = "oauth_consumer_key";

        public const string QUERY_OAUTH_NONCE = "oauth_nonce";

        public const string QUERY_OAUTH_SIGNATURE_METHOD = "oauth_signature_method";

        public const string QUERY_OAUTH_TOKEN = "oauth_token";

        public const string QUERY_OAUTH_TOKEN_SECRET = "oauth_token_secret";

        public const string QUERY_OAUTH_TIMESTAMP = "oauth_timestamp";

        public const string QUERY_OAUTH_VERSION = "oauth_version";

        public const string QUERY_OAUTH_SIGNATURE = "oauth_signature";

        public const string QUERY_OAUTH_VERIFIER = "oauth_verifier";

 

        public const string QUERY_STATUS = "status";

        public const string QUERY_IN_REPLY_TO_STATUS_ID = "in_reply_to_status_id";

        //constant values

        public const string VALUE_OAUTH_VERSION = "1.0";

        public const string VALUE_HMAC_SHA1 = "HMAC-SHA1";

        public const string HTTPMETHOD_GET = "GET";

        public const string HTTPMETHOD_POST = "POST";

    }

}

 


BasicUtil.cs (名前をBasicUtilという名前にしましたが、Basic認証とは何の関係もありません。単なる文字列操作のメソッドです。)

 

 

namespace TwitterUtils

{

    using System;

    using System.Collections.Generic;

    using System.Text;

    /// <summary>

    /// Util methods

    /// </summary>

    public static class BasicUtil {

        /// <summary>

        /// UrlEncode

        /// </summary>

        /// <param name="src">string</param>

        /// <returns>encoded string</returns>

        public static string UrlEncode(string src)

        {

            string escaped = Uri.EscapeDataString(src);

 

            StringBuilder sb = new StringBuilder(escaped.Length);

            foreach (var c in escaped)

            {

                if (c <= 0x2A && "!&'()*".IndexOf(c) > -1)

                {

                    sb.AppendFormat("%{0:X2}", (int)c);

                }

                else

                {

 

                    sb.Append(c);

                }

            }

            return sb.ToString();

        }

        /// <summary>

        /// IDictionary<string,string> object => Url query string

        /// </summary>

        /// <param name="dic">IDictionary<string,string> object</param>

        /// <returns>Url query string</returns>

        public static string BuildQueryString(IDictionary<string, string> dic)

        {

            StringBuilder sb = new StringBuilder();

            foreach (var item in dic)

            {

                sb.Append(item.Key).Append('=').Append(item.Value);

                sb.Append('&');

            }

            sb.Remove(sb.Length - 1, 1);

            return sb.ToString();

        }

        /// <summary>

        /// Url query string => IDictionary<string,string> object 

        /// </summary>

        /// <param name="queryString">Url query string</param>

        /// <returns>IDictionary<string,string>IDictionary object</returns>

        public static IDictionary<string, string> QueryStringToDictionary(string queryString)

        {

            queryString = queryString.Trim('?');

            var dic = new Dictionary<string, string>();

            if (!string.IsNullOrEmpty(queryString))

            {

                string[] qs = queryString.Split('&');

                foreach (var item in qs)

                {

                    string[] nv = item.Split('=');

                    dic.Add(nv[0], nv[1]);

                }

            }

            return dic;

        }

    }

  

}

 


OAuthUtil.cs (OAuth認証の各手順で使用する値の計算メソッド群です。)

 

 

namespace TwitterUtils

{

    using System;

    using System.Text;

    using System.Security.Cryptography;

    /// <summary>

    /// Utility methods for OAuth

    /// </summary>

    public static class OAuthUtil

    {

        static Random random = new Random(Environment.TickCount);

        /// <summary>

        /// Hash method using HMACSHA1

        /// </summary>

        /// <param name="keyBytes">key</param>

        /// <param name="dataBytes">data</param>

        /// <returns>result</returns>

        public static byte[] ComputeHash(byte[] keyBytes, byte[] dataBytes)

        {

            HMACSHA1 hmacsha1 = new HMACSHA1();

            hmacsha1.Key = keyBytes;

            byte[] resultBytes = hmacsha1.ComputeHash(dataBytes);

            return resultBytes;

        }

        /// <summary>

        /// Hash method using HMACSHA1

        /// </summary>

        /// <param name="keyString">key string</param>

        /// <param name="dataString">data string</param>

        /// <param name="enc">encoding (from string  to byte)</param>

        /// <returns>result string</returns>

        public static string ComputeHash(string keyString, string dataString, Encoding enc)

        {

            byte[] keyBytes = enc.GetBytes(keyString);

            byte[] dataBytes = enc.GetBytes(dataString);

            return Convert.ToBase64String(ComputeHash(keyBytes, dataBytes), Base64FormattingOptions.None);

        }

        /// <summary>

        /// TIME Stamp

        /// </summary>

        /// <returns>TIME Stamp</returns>

        public static string GetTimeStamp()

        {

            TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);

            return Convert.ToInt64(ts.TotalSeconds).ToString();

        }

        /// <summary>

        /// random integer between 123400 - 9999999

        /// </summary>

        /// <returns>random integer string</returns>

        public static string GetNonce()

        {

            return random.Next(123400, 9999999).ToString();

        }

 

    }

 

}

 


OAuthURLBuilder.csOAuth認証で使用するURLを組み立てるメソッドがあります。tokentoken_secret等を保持させるのでインスタンス化が可能なクラスにしました。)

 

 

namespace TwitterUtils

{

    using System.Collections.Generic;

    using System.Text;

    /// <summary>

    ///  OAuth URL methods

    /// </summary>

    public class OAuthURLBuilder

    {

        string oauth_consumer_key;

        string oauth_consumer_secret;

        string oauth_token;

        string oauth_token_secret;

        /// <summary>

        /// constractor

        /// </summary>

        /// <param name="oauthConsumerKey">oauthConsumerKey</param>

        /// <param name="oauthConsumerSecret">oauthConsumerSecret</param>

        public OAuthURLBuilder(string oauthConsumerKey, string oauthConsumerSecret)

        {

            oauth_consumer_key = oauthConsumerKey;

            oauth_consumer_secret = oauthConsumerSecret;

            oauth_token = "";

            oauth_token_secret = "";

        }

        /// <summary>

        /// sets OAuth token

        /// </summary>

        /// <param name="token">OAuth token</param>

        /// <returns>OAuthURLBuilder object</returns>

        public OAuthURLBuilder SetOAuthToken(string token)

        {

            oauth_token = token;

            return this;

        }

        /// <summary>

        /// sets OAuth tokenSecret

        /// </summary>

        /// <param name="tokenSecret">OAuth tokenSecret</param>

        /// <returns>OAuthURLBuilder object</returns>

        public OAuthURLBuilder SetOAuthTokenSecret(string tokenSecret)

        {

            oauth_token_secret = tokenSecret;

            return this;

        }

        /// <summary>

        /// Gets Signature

        /// </summary>

        private static string GetSignature(string httpMethod, string url, string queries, string consumerSecret, string tokenSecret)

        {

            string sigData = string.Format("{0}&{1}&{2}", httpMethod, BasicUtil.UrlEncode(url), BasicUtil.UrlEncode(queries));

            string sigKey = string.Format("{0}&{1}", BasicUtil.UrlEncode(consumerSecret), BasicUtil.UrlEncode(tokenSecret));

            return OAuthUtil.ComputeHash(sigKey, sigData, Encoding.UTF8);

        }

        /// <summary>

        /// get twitter API Url with OAuth

        /// </summary>

        /// <param name="httpMethod">Http method (GET/POST)</param>

        /// <param name="originalUrl">Original Url</param>

        /// <param name="queries">url queries</param>

        /// <returns>twitter API Url with OAuth</returns>

        public string GetURL(string httpMethod, string originalUrl, IDictionary<string, string> queries)

        {

            var paramsDic = GetDictionaryWithOAuthParams();

            foreach (var en in queries)

            {

                paramsDic.Add(en.Key, en.Value);

            }

            if (!string.IsNullOrEmpty(oauth_token))

            {

                paramsDic.Add(Const.QUERY_OAUTH_TOKEN, oauth_token);

            }

 

            string queryString = BasicUtil.BuildQueryString(paramsDic);

            string signature = GetSignature(httpMethod, originalUrl, queryString, oauth_consumer_secret, oauth_token_secret);

            paramsDic.Add(Const.QUERY_OAUTH_SIGNATURE, BasicUtil.UrlEncode(signature));

            return string.Format("{0}?{1}", originalUrl, BasicUtil.BuildQueryString(paramsDic));

        }

        /// <summary>

        /// Gets DictionaryWithOAuthParams

        /// </summary>

        private SortedDictionary<string, string> GetDictionaryWithOAuthParams()

        {

            return new SortedDictionary<string, string> {

                                                    { Const.QUERY_OAUTH_CONSUMER_KEY, oauth_consumer_key },

                                                    { Const.QUERY_OAUTH_NONCE, OAuthUtil.GetNonce()},

                                                    { Const.QUERY_OAUTH_SIGNATURE_METHOD, Const.VALUE_HMAC_SHA1 },

                                                    { Const.QUERY_OAUTH_TIMESTAMP, OAuthUtil.GetTimeStamp() },

                                                    { Const.QUERY_OAUTH_VERSION, Const.VALUE_OAUTH_VERSION },

                                                };

        }

        /// <summary>

        /// gets RequestTokenURL

        /// </summary>

        /// <returns>RequestTokenURL</returns>

        public string GetRequestTokenURL()

        {

            return GetURL(

                Const.HTTPMETHOD_GET,

                Const.API_REQUEST_TOKEN,

                new Dictionary<string, string>());

        }

        /// <summary>

        /// gets AccessTokenURL

        /// </summary>

        /// <param name="pin">pin number</param>

        /// <returns>AccessTokenURL</returns>

        public string GetAccessTokenURL(string pin)

        {

            return GetURL(

                Const.HTTPMETHOD_GET,

                Const.API_ACCESS_TOKEN,

                new Dictionary<string, string>() { { Const.QUERY_OAUTH_VERIFIER, pin } });

        }

 

 

        /// <summary>

        /// Gets AuthorizeURL

        /// </summary>

        /// <returns>Authorize page URL</returns>

        public string GetAuthorizeURL()

        {

            var queries = new Dictionary<string, string>() { { Const.QUERY_OAUTH_TOKEN, oauth_token } };

            return string.Format("{0}?{1}", Const.API_AUTHORIZE, BasicUtil.BuildQueryString(queries)); ;

        }

    }

}

 


 

  で、これらを使用するコードMain()の含まれるコードなのですが、長くなりましたのでまた後のエントリにします。

posted @ 23:56 | Feedback (26)

自作TwitterクライアントをOAuth認証に対応させる。

先月7月に久々(1年ぶりくらい)にわんくまブログにエントリしました。3エントリ(3日分)です。これでやめたらまさに3日坊主になってしまいます(笑)。毎月1エントリぐらいはしないとということで、8月になったのでエントリしてみます。

既に山ほどTwitterクライアントはありますが、自分好みの機能がない場合があります。その場合は自分で作成してしまうのもよいプログラミングの練習になって面白いです。Twitter APIの勉強にもなります。私ももちろんTwitやモバツイなどの有名なツイッタークライアントにお世話になっていますが、実は一昨年ごろから自分専用の自作クライアントを作成していました。公開はしていないので好き勝手にいろいろな機能を仕込んだりできます。

以前、in_reply_to_status_idが追加されたころには@をつけなくても、リクエストにin_reply_to_status_idに実際の誰かのつぶやきのstatus_idを仕込めば相手の返信リストに発言を入れることができて驚かせるというようないたずらもできました。今は@もないと相手の返信リストに入らなくなっています。

また、タイムライン取得などAPI制限と呼ばれる1時間当たりの実行回数制限があるAPIがありますが、以前は、POSTでもタイムラインを取得できてAPIが減らさずに済むという裏技もありました。今はGETしかつかえないようです。さらに、タイムラインの取得にcountパラメータを知っていると便利です。公式サイトやcountなしの場合のタイムラインは基本的に20発言しか表示できません。countを使うと以前は800件、現在は200件ぐらい取得できます。これを使って、Javascriptのbookmarkletで「一行Twitterクライアント」「Twitterのユーザ発言を過去にさかのぼるBookmarklet」を日記のネタにしたりしました。

Twitterより4月にセキュリティの弱いBasic認証を終了するという発表がありました。

「Twitterブログ: ベーシック認証について」 http://blog.twitter.jp/2010/04/blog-post_30.html

こうした利点を備えるOAuthの普及が進んだことから、われわれはベーシック認証への対応を2010年6月30日をもって終了する予定です。このためそれ以降は、ベーシック認証を行っていたサービスが利用できなくなる可能性がありますのでご注意ください。

Basic認証は単純な認証方式で、ユーザ名・パスワードが端末のどこかに保存され、通信の度に毎回ネットワークに流されます。たしかにいろいろな不安要素があります。

クライアントが既にこれ以外の認証方式に対応していればいいのですが、Basic認証しか使っていないものは対応しなければならなくなりました。

この期限は6月の発表で延長されました。

「Twitterブログ: Twitter APIデベロッパー・コミュニティへのお知らせ (OAuthへの移行に関しての期限延長)」 http://blog.twitter.jp/2010/06/twitter-api-oauth.html

8月16日から8月31日の間、毎日APIを呼ぶ回数制限(rate limit)を毎日減らします。(1時間あたり10コールの単位で減らします。) 8月31日からはベーシック認証のAPIコールに対しては全てHTTP 403エラーを返します。

9月にはBasic認証のみだとAPIを利用できなくなってしまします。

私も自分の自分しか使用していない自作クライアントをTwitterのOAuth認証に対応させることにしました。
[Link]「OAuth - Wikipedia」 http://ja.wikipedia.org/wiki/OAuth

OAuth認証の手順は以下に仕様があります。(英語)
[Link]「OAuth Core 1.0」 http://oauth.net/core/1.0/#anchor9

実装例は以下にあります。
[Link]「Twitter API Wiki / OAuth Examples」 http://apiwiki.twitter.com/OAuth-Examples

OAuthの認証手順は大まかには「OAuth Core 1.0のページ」 の図に書いてあるとおり以下の流れです。

今回の場合、図のConsumerがTwitterクライアント、Service ProviderがTwitterです。

  1. クライアントがTwitterにRequest Tokenを要求する。
  2. TwitterがクライアントにRequest Tokenを返す。
  3. ユーザにAuthorizeページでクライアントアプリケーションを「許可」してもらう。
  4. Twitterが暗証番号を表示する。
  5. ユーザがクライアントに暗証番号を入力する。
  6. クライアントがTwitterにAccess Tokenを要求する。
  7. TwitterがクライアントにAccess Tokenを返す。
  8. クライアントにAccess TokenとAccess TokenSecretを保存する。
  9. 以降、クライアントはAccess Tokenを含めてAPIの各リクエストをする。

各手順では細かいパラメータがあります。

クライアントがデスクトップアプリケーションの場合、ユーザにTwitterのwebページで許可してもらって、そこで表示される暗証番号(PIN, oauth_verifier)を入力してもらわないといけません。

この部分を不要にしたxAuth認証というものがありますが、

「Twitter API Wiki / Twitter REST API Method: oauth access_token for xAuth」 http://apiwiki.twitter.com/Twitter-REST-API-Method:-oauth-access_token-for-xAuth

In order to get access to this method, you must apply by sending an email to api@twitter.com ? all other applications will receive an HTTP 401 error.

と書かれているとおりapi@twitter.comにメールを送って承認してもらわなければなりません。自分しかユーザがいないクライアントの場合にはこれをするのは気が引けます。承認されないかもしれません。

また、xAuthはユーザにとっても、クライアントにユーザ・パスワードに入力する必要があるので、端末のどこかに保存されるというBasic認証と同様の不安要素が残ってしまいます。

というわけで、結局、OAuth認証に対応させようというわけです。

長くなってしまったので、実装編(?)は別のエントリにします。

posted @ 22:09 | Feedback (309)