2012年8月27日月曜日

[PHP][cURL] PHPのcURLを使ってGoogleにログインする

YAHOO! 知恵袋に(どなたか本当にお願いします!phpのcurlに関して教えて頂きたいです。)というPHPのcURLを使って、Googleにログインする方法が聞かれていたのでちょっと組んでみた。

実はこういうのは意外と面倒くさくて、
POSTデータでIDとパスワードを飛ばせばいいというものではない。
もちろんそれでログインできる(できてしまう)サイトもあるのだが、
セキュリティポリシーの高いサイトではそうはいかない。
不正なログインを防ぐためにフォーム内にトークンを埋め込み、
かつCookieにもそのトークンを埋め込んでおき、
サブミットされた際にフォームから飛んできたPOSTとCookieを比較しているのだ。
ちなみにGoogleとPixivはこの方式を採用している(2012/08/27現在)。

とりあえず早速ソースを見ていこう。

//URLを指定する
$url='https://accounts.google.com/ServiceLoginAuth';
//POST用のデータを作っておく
$data=array
(
    //ID部分(適宜置き換え)
    'Email'=>'',
    //パスワード部分(適宜置き換え)
    'Passwd'=>'',
    //ログインを維持するかのチェックボックス部分
    //'PersistentCookie'=>'yes',
);
//テンポラリファイルを作成する
$cookie=tempnam(sys_get_temp_dir(),'cookie_');

//cURLを初期化して使用可能にする
$curl=curl_init();
//オプションにURLを設定する
curl_setopt($curl,CURLOPT_URL,$url);
//文字列で結果を返させる
curl_setopt($curl,CURLOPT_RETURNTRANSFER,true);
//クッキーを書き込むファイルを指定
curl_setopt($curl,CURLOPT_COOKIEJAR,$cookie);
//URLにアクセスし、結果を文字列として返す
$html=curl_exec($curl);
//cURLのリソースを解放する
curl_close($curl);

//Document初期化
$dom=new DOMDocument();
//html文字列を読み込む(htmlに誤りがある場合エラーが出るので@をつける)
@$dom->loadHTML($html);
//XPath初期化
$xpath=new DOMXPath($dom);
//inputのtypeがhiddenの要素をとってくる
$node=$xpath->query('//input[@type="hidden"]');
foreach($node as $v)
{
    //POST用のデータに追加する
    $data[$v->getAttribute('name')]=$v->getAttribute('value');
}

//cURLを初期化して使用可能にする
$curl=curl_init();
//オプションにURLを設定する
curl_setopt($curl,CURLOPT_URL,$url);
//メソッドをPOSTに設定
curl_setopt ($curl,CURLOPT_POST,true);
//POSTデータ設定
curl_setopt($curl,CURLOPT_POSTFIELDS,$data);
//クッキーを読み込むファイルを指定
curl_setopt($curl,CURLOPT_COOKIEFILE,$cookie);
//Locationをたどる
curl_setopt($curl,CURLOPT_FOLLOWLOCATION,true);
//URLにアクセスし、結果を表示させる
curl_exec($curl);
//cURLのリソースを解放する
curl_close($curl);

//テンポラリファイルを削除
unlink($cookie);

という感じになる。
さて、実際に何をしているか見ていくと

//URLを指定する
$url='https://accounts.google.com/ServiceLoginAuth';
//POST用のデータを作っておく
$data=array
(
    //ID部分(適宜置き換え)
    'Email'=>'',
    //パスワード部分(適宜置き換え)
    'Passwd'=>'',
    //ログインを維持するかのチェックボックス部分
    //'PersistentCookie'=>'yes',
);
//テンポラリファイルを作成する
$cookie=tempnam(sys_get_temp_dir(),'cookie_');

の部分で必要な変数を用意している。
ID部分とパスワード部分は適宜置き換えて欲しい。

//cURLを初期化して使用可能にする
$curl=curl_init();
//オプションにURLを設定する
curl_setopt($curl,CURLOPT_URL,$url);
//文字列で結果を返させる
curl_setopt($curl,CURLOPT_RETURNTRANSFER,true);
//クッキーを書き込むファイルを指定
curl_setopt($curl,CURLOPT_COOKIEJAR,$cookie);
//URLにアクセスし、結果を文字列として返す
$html=curl_exec($curl);
//cURLのリソースを解放する
curl_close($curl);

の部分でフォーム内のトークンを分析するためのHTMLを取得する。

//Document初期化
$dom=new DOMDocument();
//html文字列を読み込む(htmlに誤りがある場合エラーが出るので@をつける)
@$dom->loadHTML($html);
//XPath初期化
$xpath=new DOMXPath($dom);
//inputのtypeがhiddenの要素をとってくる
$node=$xpath->query('//input[@type="hidden"]');
foreach($node as $v)
{
    //POST用のデータに追加する
    $data[$v->getAttribute('name')]=$v->getAttribute('value');
}

の部分でHTML解析を実際に行いトークンを取得し、POST用データ配列に入れる。

//cURLを初期化して使用可能にする
$curl=curl_init();
//オプションにURLを設定する
curl_setopt($curl,CURLOPT_URL,$url);
//メソッドをPOSTに設定
curl_setopt ($curl,CURLOPT_POST,true);
//POSTデータ設定
curl_setopt($curl,CURLOPT_POSTFIELDS,$data);
//クッキーを読み込むファイルを指定
curl_setopt($curl,CURLOPT_COOKIEFILE,$cookie);
//Locationをたどる
curl_setopt($curl,CURLOPT_FOLLOWLOCATION,true);
//URLにアクセスし、結果を表示させる
curl_exec($curl);
//cURLのリソースを解放する
curl_close($curl);

の部分で今まで作ったPOSTデータを飛ばしログインしている。

//テンポラリファイルを削除
unlink($cookie);

の部分でcookie保持用に用意したファイルを消しておく。
ただ、実際は放っておけばそのうち勝手に消えてしまうファイルなので、
わざわざ消す必要がないと言えばないのだが・・・

ポイントとなる関数
  • curl_init ・・・ cURL セッションを初期化する
  • curl_setopt ・・・ cURL 転送用オプションを設定する
  • curl_exec ・・・ cURL セッションを実行する
  • curl_close ・・・ cURL セッションを閉じる
  • DOMDocument クラス ・・・ HTML ドキュメントあるいは XML ドキュメント全体を表し、 ドキュメントツリーのルートとなります。
  • DOMXPath クラス ・・・ XPath 1.0 をサポートします。

12 件のコメント:

  1. Yahooで質問させて頂いたものです!

    とても参考になりましたありがとうございます!

    実際にログインする事ができました!

    トークン=type="hidden"の部分でしょうか?

    初心者なのでわかりません;;

    また、こういう関連の情報っていうのがネットにはあまりないのですがどのようにして勉強されたのでしょうか?

    オススメの書籍やサイト等がありましたら教えて頂けるとうれしいです!

    返信削除
    返信
    1. はーい。
      参考になったようで何よりですよ。

      今回はおそらくtype="hidden"の所にトークンが隠されているようですね。
      いくつかhiddenがあるので、どれが実際にトークンになってるかはGoogleがどのような実装してるかによりますけどね。
      なので、今回はトークンかどうか関係なしに、すべてのhiddenをとってきてPOSTデータに突っ込むことにしてます。(どれが必要か検証するの大変ですしね)

      また、関連の情報はどれをさしてるのでしょう?cURLの手法?POSTとCookieのトークンの手法?Googleのログインに何のデータが必要かの手法?

      cURLであればPHPマニュアルを熟読することがまず近道になると思います。
      PHPマニュアルは非常に多くの役に立つ情報があるので、
      既に知ってる関数なんかも目を通すといいと思います。
      参考サイト:http://php.net/manual/ja/function.curl-setopt.php

      POSTとCookieのトークンであればこれは実は割とメジャーな手法で、
      CSRF(クロスサイトリクエストフォージェリ)という攻撃を防ぐために、
      ワンタイムトークンというものを利用する方法があるのですが、
      今回がまさにワンタイムトークンです。
      この辺はセキュリティ関連でググるか、
      PHPのフレームワークの中身を読んでたら勉強できると思います。
      参考サイト:http://www.jumperz.net/texts/csrf.htm

      Googleのログインに何のデータが必要かであればいくつか方法はありますが、
      私はChromeを使うので、Chromeのデベロッパーツールから、飛んでるPOSTデータと
      飛ばしてるCookieデータを調べて、それをもとに推測ですね。
      今回のことに限らず、Chromeのデベロッパーツールを使いこなすのはHTMLやCSS、JavaScriptだけでなくPHPなどのサーバサイドのプログラマにとっても非常に有益なので覚えておいて損はないです。
      参考サイト:http://gihyo.jp/dev/feature/01/devtools/0001

      上記ので答えになってますかね?
      不足があれば言っていただければ。

      削除
    2. すごくご丁寧にありがとうございます!

      まさに聞きたかった内容は上記の内容です!
      ※特にCSRF(クロスサイトリクエストフォージェリ)

      まずは一つずつ勉強できていければと思います。

      ありがとうございました!

      削除
    3. はーい。
      個人的には結構セキュリティ周りは気にするところなので、
      CSRFの理解が進んだなら何よりです。

      どのようなプログラムを作ってらっしゃるのかは分からないですが、
      そのプログラムがうまくいくといいですね。
      グッドラック!

      削除
  2. すみません、以前にこちらで質問させて頂いたものですが、
    curlを使ってIPアドレスを指定し任意のサイトへアクセスする事は
    可能でしょうか?

    ユーザーエージェントだけであれば、CURLOPT_USERAGENTで設定
    できると思うのですが、IPに関してがどのように設定したらよいのか
    わかりません;

    CURLOPT_FTPPORT

    ↑このオプションを使うよう気がするのですが・・・;

    お手すきな時にご返答頂けると幸いです!

    返信削除
    返信
    1. はーい。
      その任意のIPアドレスが相手のこと(つまり接続先)なのか自分のこと(つまり接続元)なのかによって少し変わりますが、
      基本的には可能です。

      接続先のIPアドレスだとしたら基本的に

      $url='http://74.125.235.120/';
      $curl=curl_init();
      curl_setopt($curl,CURLOPT_URL,$url);
      curl_exec($curl);
      curl_close($curl);

      の様なコードで実現可能です。
      上記は

      $url='http://google.co.jp/';
      $curl=curl_init();
      curl_setopt($curl,CURLOPT_URL,$url);
      curl_exec($curl);
      curl_close($curl);

      と同じ結果が得られます。
      (ただし、GoogleがIPを変えたらもちろんだめですが・・・)

      Googleにアクセスするためだけに74.125.235.120というのわざわざ覚えるのは大変です。
      でも、google.co.jpと覚えるのは数字の羅列に比べて覚える難易度は下がります。
      なので、google.co.jp=74.125.235.120というひも付けをしてgoogle.co.jpでもアクセスできるようにしてるのです。
      ゆえに、google.co.jpと74.125.235.120は同じところを指すのでURLのドメイン部分をそのままIPにするだけアクセスできます。
      詳しくは「DNS」でググるといいかもしれません。

      次に、接続元のIPだとしたらプロキシサーバというのを通してあげると、
      自分のIPをそのサーバのIPとして、アクセスすることができます。
      ゆえに、厳密には任意のIPというよりは任意のプロキシサーバのIPでアクセスするということになります。
      実現方法もそれほど難しくなくCURLOPT_PROXY系オプションを使えば実現できます。
      シンプルな例として

      $url='http://74.125.235.120/';
      //プロキシのIPやドメイン
      $proxy='http://xxx.xxx.xxx.xxx/';
      $curl=curl_init();
      curl_setopt($curl,CURLOPT_URL,$url);
      curl_setopt($curl,CURLOPT_PROXY,$proxy);
      curl_exec($curl);
      curl_close($curl);

      という用な感じになります。
      詳しくは「プロキシ」でググるといいかもしれません。

      削除
  3. ご回答頂きましてありがとうございます!☆

    お仕事バタバタで返信するのが遅れてしまいました><

    丁寧にお答え頂いたのにすみません><

    実際にやりたかったのは後者のものです!

    串をさせばよかったんですね!

    実際にやってみるとできました!!

    本当にありがとうございます!!

    返信削除
    返信
    1. はーい。
      役に立てたようで何よりです。
      これからもプログラミングを楽しんでくださいね。

      削除
  4. はじめまして、Googleへの認証方法を調べていて、とても勉強になりました。
    お聞きしたいのですが、Googleへのログインが確認できたあとのページ遷移は可能なのでしょうか?

    ログイン後のCookie情報を再利用して次のページに対するアクセスを記述しているのですが、ログアウトされてしまっています。orz

    お時間があります時にご返信いただければ幸いです。

    返信削除
    返信
    1. はーい。
      はじめまして~。
      もちろんページ遷移可能ですよ。
      お察しの通りログイン管理にクッキーが使われています。
      もし私のソースコードを使っていると仮定したら、
      メールとパスワードをPOSTでなげる際にクッキー読み込みだけして書き込んでないので、
      そこにcurl_setopt($curl,CURLOPT_COOKIEJAR,$cookie);をつける。

      //cURLを初期化して使用可能にする
      $curl=curl_init();
      //オプションにURLを設定する
      curl_setopt($curl,CURLOPT_URL,$url);
      //メソッドをPOSTに設定
      curl_setopt ($curl,CURLOPT_POST,true);
      //POSTデータ設定
      curl_setopt($curl,CURLOPT_POSTFIELDS,$data);
      //クッキーを読み込むファイルを指定
      curl_setopt($curl,CURLOPT_COOKIEFILE,$cookie);
      /*
      追加
      */
      //クッキーを書き込むファイルを指定
      curl_setopt($curl,CURLOPT_COOKIEJAR,$cookie);
      //Locationをたどる
      curl_setopt($curl,CURLOPT_FOLLOWLOCATION,true);
      //URLにアクセスし、結果を表示させる
      curl_exec($curl);
      //cURLのリソースを解放する
      curl_close($curl);

      次にクッキーデータの入ったファイルを最後に消しているので、
      それをすべての処理が終わった後に消すようにするする。

      /*
      これをすべての処理が終わった後にする
      */
      //テンポラリファイルを削除
      unlink($cookie);

      という点を改修した上で、行きたいURLにクッキー読み込みつつcURLで飛ばせば問題ないと思いますー。
      つまり

      //cURLを初期化して使用可能にする
      $curl=curl_init();
      //オプションにURLを設定する
      curl_setopt($curl,CURLOPT_URL,$url);
      //クッキーを読み込むファイルを指定
      curl_setopt($curl,CURLOPT_COOKIEFILE,$cookie);
      //Locationをたどる
      curl_setopt($curl,CURLOPT_FOLLOWLOCATION,true);
      //URLにアクセスし、結果を表示させる
      curl_exec($curl);
      //cURLのリソースを解放する
      curl_close($curl);

      こんな感じ。

      コメント欄だとソースコードが読みづらいので全体は省きますが、必要があれば載せますよー。
      また、逆にソースコード見せてもらったほうが不適切な点を指摘しやすいかもですね。

      うまくいくといいですね!

      削除
    2. ご回答ありがとうございます。
      教えていただいた方法でGoogleドライブ上のファイルまで遷移できました。(>_<)
      いろいろ試してみたいと思います、本当にありがとうございました。

      削除
    3. はーい。
      うまくいったならよかったです。
      機会があればプラウザもどきなども作ってみると面白いですよ!

      削除