2012年9月20日木曜日

[CakePHP]TwitterのOAuth認証とAuthコンポーネントを連携させる

Twitterのログイン認証を自分のサイトで利用できるAPIを使用し、
CakePHPのAuthコンポーネントを連携させてみた。
難しくて時間がかかってしまったので概念と実装方法を説明する。

 概念


まずは概念から。
流れは以下の三つの図の通り。図1→図3という時系列で処理が進んでいく。
APIを使用する側のサイトをConsumerと呼ぶらしい。
また、パソコンの絵はユーザの端末を表している。
(excelで図を作ったが、bmpファイルにしたらぼやけて汚くなっちゃった。
どうすればきれいな図の画像ファイルできるかな。)

図1



図2




 図3



 実装方法 

 

1.Twitter API https://dev.twitter.com のサイトで自分のサイトの情報を登録。


 自分のTwitterアカウントでログインしたら右上にマウスを持っていくと現れる「My Applications」を押す。次の画面で「Create a new application」を押す。

現れる入力画面で自分のサイトの情報を入力する。
「Callback URL」以外は適当でもよさそう。
Callback URLは上の概念の「⑦Callback URLにリダイレクト」でTwitter側から
リダイレクトされる際のURLである。
このURLにはAuthコンポーネントで使用するモデルのコントローラに
専用のアクションを用意し、そのURLを指定する。
私の場合はUsersコントローラにtwtrCallback()というアクションを作ったので、

http://127.0.0.1/rakuzon/users/twtrCallback

とした。まだ、開発用なので自IPアドレスを指定している。
一つ注意点として、localhosotは指定できないので127.0.0.1を指定する。
このサイト参照。 Twitter アプリケーションのコールバックURLにlocalhostを指定する

2.Cnsumer KeyとConsumer Secretを控えておく


システムがTwitterのAPIを使うための、「システムに一つ」のIDとパスワードのようなもの。
「My applications」画面から、自分が今登録したサイト名をクリックした画面で、
Detailsタブを参照することにより確認できる。
これでTwitter側で行う作業は終わり。

3.OAuthを使うためのCakePHP用のライブラリをダウンロード、インストール


Consuming OAuth-enabled APIs with CakePHP

上記でダウンロードできる。
ただ、CakePHP 1.X系を使用している場合は、
「get the version for the older CakePHP 1.x.」
というリンクからダウンロードする。

自分はcakephp-cakephp-1.3.15-9-gacd25c3.zipを使っているが、
最初2.X用のものをダウンロードし使ってみたが、動かなかった。

ファイルを解凍したら、app/vendros直下にOAuthというフォルダをフォルダごと
配置する。これでインストールは完了。

4.ソースコードを書く


コメント増量中。
コメント中の丸の数字は上記の概念図の丸の数字に対応している。
まずはAuthで使うモデルのコントローラ。
私の場合は、users_controller.php
<?php
//インストールしたライブラリを使用するためにこの1行を入れる。
App::import('Vendor','oauth',array('file'=>'OAuth'.DS.'oauth_consumer.php'));


class UsersController extends AppController {

    var $name = 'Users';
    var $components = array('Auth');

    function beforeFilter()
    {
        $this->Auth->allow('index', 'login', 'twtrCallback');

        //Authの認証で使うIDとパスワードはデフォルトでは'username'と'password'だが
     //それぞれaccess_token_keyとaccess_token_secreteに変更する。当然Usersテーブルにも同カラムを準備している。
        $this->Auth->fields = array('username' => 'access_token_key', 'password' => 'access_token_secret');
       
        //これをやらないと、app_controllerで行った設定が反映されないらしい。
        parent::beforeFilter();
    }
    //①で呼び出されるURL
    public function index() {
        $consumer=new OAuth_Consumer(
            '【2で控えたご自分のCnsumer Key】',
            '【2で控えたご自分のCnsumer Secret】');

        // ②、③ request_tokenを取得。
        //認証後、「http://127.0.0.1/rakuzon/users/twtrCallback」にリダイレクトする
        $requestToken=$consumer->getRequestToken(
        'http://twitter.com/oauth/request_token',
        'http://127.0.0.1/rakuzon/users/twtrCallback');


        // 認証後、アクセストークンを取得する際に必要なので保存
        $this->Session->write('request_token',$requestToken);

        // ④、⑤Twitterの認証ページにリダイレクト
        $this->redirect('http://twitter.com/oauth/authorize?oauth_token='
        .$requestToken->key);
    }

    //⑦のリダイレクト先URLとなるアクション。
    public function twtrCallback() {
        if (isset($this->params['url']['denied'])) {
            echo 'access denied';
            return;
        }
        $consumer=new OAuth_Consumer(
            '【2で控えたご自分のCnsumer Key】',
            '【2で控えたご自分のCnsumer Secret】');
        $requestToken=$this->Session->read('request_token');

        //access_token取得する。
        //⑧、⑨ access_tokenはユーザごとに払い出されるもの。
        $accessToken=$consumer->getAccessToken(
            'http://api.twitter.com/oauth/access_token',$requestToken);
        //⑩、⑪ access_tokenを使用し、ユーザ情報をTwitter側から引き出す。
        $json = $consumer->get($accessToken->key, $accessToken->secret, 'http://twitter.com/account/verify_credentials.json', array());
        $twitterData = json_decode($json, true);

        //⑫モデルに渡す。モデルでは保存が行われている。
        $this->User->update(
        Array(
            "user_id" => $twitterData['id_str'],
            "user_name" => $twitterData['screen_name'],
            "access_token_key" => $accessToken->key,
            "access_token_secret" => $accessToken->secret,
            "img_url" => $twitterData['profile_image_url']
            )
        );
       
        //⑬Authのlogin()処理のために型にaccess_token_keyとaccess_token_secretを入れてあげる。
        $user['User']["access_token_key"] = $accessToken->key;
        $user['User']["access_token_secret"] = $accessToken->secret;
       
        //⑬Authのlogin処理
        $this->Auth->login($user);

        $this->redirect('/main/index');
    }

    public function logout(){
        $this->Auth->logout();
    }

    public function login(){
    }
}

users/indexがユーザがまずログインするときにアクセスするURLとなる。
つまり、ログインボタンなどに設定するURLだ。
twtrCallback()がTwitterからのリダイレクト先のURLとなる。
概念の「⑦CallbackURLにリダイレクト」。
63~70行目はログインするごとにユーザの情報をアップデートする
ことを意味している。

つづいて全てのコントローラーが継承するapp_controller.php。

class AppController extends Controller {

    function beforeFilter(){
    //全ての画面でログイン状態のチェックを行うため、
    //authのユーザ情報を取得する。
        $auth=$this->Auth->user();
        $this->set('auth',$auth);
    }
}

なお、view側で$authという変数に値が入っているかどうかで
ログインしているかどうかを確認する。全画面でログイン状態確認を
行うのでapp_controllerで行っている。
$this->session->check('Auth');で最初チェックしようと思ったのだが、
なぜかlogout後もtrueが帰ってきてしまう。
値の中身は空だったけど、変数の型が残っててもtrueとなってしまうからかも。
このあたりあまり定かではないけど、 ログイン状態確認の処理は、
後々Cookieを利用した自動ログインの機能を
追加する予定で、大きいプログラムになりそうなので、view側でなく、
controller側にあったほうがいいかな、と思い、深追いしないでおく。


そして、モデル。user.php

<?php

class User extends AppModel {

    var $name = 'User';

    public function update($user_data)
    {
        $user = $this->find('first', array('conditions' => array('user_id' => $user_data['user_id'])));
        if($user) {
            $user_data['id'] = $user['User']['id'];
        }
        $this->create();
        $this->save(Array("User" => $user_data));
    }
}

9~12行目でDB中にあるuser_idとTwitterから引っ張ってきた情報のuser_idを
引き合わせて、同じユーザ情報にはきちんと上書きしてくれるようになっている。
user_idというのはTwitterでTwitterアカウント一つ一つに割り当てられる数字のID。

このモデルに対応するテーブルは以下。
CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(64) NOT NULL,
  `user_name` varchar(64) NOT NULL,
  `access_token_key` text NOT NULL,
  `access_token_secret` text NOT NULL,
  `img_url` varchar(512) NOT NULL,
  `created` datetime NOT NULL,
  `updated` datetime NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `user_id` (`user_id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=6 ;


users_controller.phpの80行目で、ログイン後にmain/indexに飛ばしているが、
飛ばし先でどのようにauthを扱っているかを解説するためmain_controller.phpを載せる。

<?php
//3でインストールしたライブラリを使うための1行
App::import('Vendor','oauth',array('file'=>'OAuth'.DS.'oauth_consumer.php'));

class MainController extends AppController {

    public $name = 'Main';
    public $uses = array('Item','Tag','Theme');
   
    //Authを使うため。
    var $components = array('Auth');

    var $paginate = array(
            'Theme' => array (
            'limit' => 10,
            'order' => array(
                'theme.id' => 'asc'
            )
        ),
            'Tag' => array (
            'limit' => 10
        )
        );

    function beforeFilter(){

    //未ログイン状態でもindexにはアクセスを可能とするため。
        $this->Auth->allow('index');

        parent::beforeFilter();
    }

    function index(){
        //この辺はもともとあった処理
        $this->Theme->hasMany['Item']['limit'] = 5;
        $this->Theme->hasMany['Item']['order'] = 'Item.id asc';
        $this->Theme->unbindModel(array('belongsTo'=>array('User')),false);
        $themes = $this->paginate('Theme');
        $tags =   $this->paginate('Tag');
        $this->set('themes',$themes);
        $this->set('tags',$tags);
    }
}

11行目は全部のコントローラに設定するものなので本当はapp_controllerにもたせ
たかった。でもなぜかapp_controllerにもたせることができなかったので、各コントローラで
設定。ここ参照。[CakePHP]AppController でコンポーネント設定するとsessionコンポーネントが使えない。
28行目でindexを許容している。ログイン状態によって同じ画面でも出す部品を
変えようとしているだけだから、基本的に全部許容なんだよね。
だから、$this->Auth->allow(*);にしてアクセスさせない、登録系や管理系の画面だけ
$this->Auth->deny();で設定すればいい気もする。


つづいて、実際にTwitterのユーザ名やTwitterのプロフィール画像を表示するmain/index.ctp。
こいつは抜粋。

        <?php if(!empty($auth['User']['id'])): ?>
        <div>
            <img src = "<?php echo $auth['User']['img_url'];?>"/>
            <br/>
            <?php echo $auth['User']['user_name'];?>
        </div>
        <?php endif;?>


1行目でcontroller側で取り出したauthのユーザ情報があるか確認。
無ければ、未ログインと判断し、3行目のプロフィール画像や5行目のユーザ名は表示しない。
この部品は全画面に共通的に使おうと思う。

あとは、必要に応じてlogin.ctpやlogout.ctp等のviewも作成する。

5.課題


この実装だと、ブラウザを閉じてもう一度アクセス、ログインしようとすると、
また、Twitterのアプリケーション認証画面に飛ばされて承認ボタンを押す必要
が出てくる。
それだと面倒くさいのでCookieを使用して自動ログインを実装してもいいかもしれない。

6.参考にしたURL


CakePHP で OAuth 認証を使ったログイン認証・保持や会員データの保持・更新をするコード
[プログラミング][PHP][CakePHP][twitter]CakePHPでOAuth
CakePHPでOAuthを使ったログインと自サービスのログインの両方に対応させてみました。


※CakePHPのバージョンはcakephp-cakephp-1.3.15-9-gacd25c3.zip

0 件のコメント:

コメントを投稿