2011年12月22日
CakePHP2.0 で FacebookAuthenticateによるAuthの認証 CakePHP Advent Calendar 2011 Day
CakePHP Advent Calendar 2011 22日目担当です。
昨日は、@ogaaaanさん
でした。
CakePHP2.0でAuthComponentは大きく変わったものの一つです。
1.3のAuthComponentでのユーザ認証は、基本的にはPOSTされたusername,passwordを
userModelに問い合わせに行くことで認証をおこなっていましたが、
2.0ではこの認証方法にBASIC認証と、Digest認証が追加されました。
さらにオリジナルの認証方法の追加もできるようになりました。
今回は、この認証方法にFacebookのOAuth認証を利用したユーザ認証を追加してみたいとおもいます。
作成したソースは以下になります。
GitHub
https://github.com/MiuraKatsu/cakephp-FacebookAuthenticate
オリジナルの認証方法を追加するには、Controller/Component/Auth/の下に
Authenticateクラスファイルを作成します。
今回はFacebookAuthenticate.phpを作成しました。
FacebookのOAuthに関してはFacebook提供のSDKを使用することにします。
SDK
https://github.com/facebook/php-sdk/
http://developers.facebook.com/blog/post/534/
vendorsかVendor配下にSDKを置いて
App::import('Vendor','facebook', array('file' => 'facebook/src/facebook.php'));
で宣言します。
拡張Authenticateの作り方ですが、とりあえずBaseAuthenticateを継承したクラスを作ります。
そして認証のロジックをauthenticate()メソッドにオーバーライドします。
その他の共通的なメソッドはBaseAuthenticateに記述してあるものでカバーできますので、
基本的にはauthenticate()メソッドで認証ロジックを実装するだけでOKです。
このauthenticate()メソッドですが、具体的にAuthComponetnの中で呼び出されるのは
Auth->identify()の中で、そのidentify()が呼び出されるのが、Auth->login()になります。
ですので、通常のloginではそのままAuth->loign()を利用し、
Callbackの中ではAuth->identify()を呼んで、最終的に認証させています。
ではまず使用するコントローラー側の説明です。
Componentの宣言は,
public $components = array('Auth');
でもいいですし
beforeFilter()のなかで行ってもいいです。
$this->Auth->authenticateに指定した認証方法が複数あれば、それら全てで認証チェックされます。
1.3では、login処理はlogin()アクションを定義するだけで、勝手に行われていましたが
2.0では明示的に$this->Auth->login()を呼ぶ必要があります。
今回は、FacebookのOAuth認証を使用しています。
OAuth認証では、認証処理の途中でFacebookへのリダイレクトが入るので、login()ではRenderはしません。
さらにOAuth認証はFacebookへのリダイレクトの後、callback URLにアクセスし、認証が完了します。
ですので、前述したように、このcallback()メソッドの中で、Auth->identify()にアクセスし、
最終的にFacebookAuthenticate->authenticate()を呼び出しています。
このcallback()も最終的にはAuth->loginRedirectにリダイレクトされるので、こちらもRenderしてません。
つまりauthenticate()メソッドが一回の認証内で2度呼び出されているので
呼び出し元のaction名によって処理を分けています。
最初のlogin()からの呼び出しではfacebookへのリダイレクトを行い、
facebookでのアプリ認証が許可されてcallbackされたcallback()からの呼び出しでは、
accessTokenを取得し、そのAccessTokenを利用して
Facebookのユーザ情報を入手しています。
callback()からの呼び出しでは、
FacebookSDKのgetAccessToken()メソッドを使って、AccessTokenを取得しているのですが、
getAccessToken()メソッドは$_REQUEST['state']を直接参照しており、その値でAccessTokenを取得しにいっています。
自分の環境ですと、$_REQUESTにstateが入ってこずに、$_REQUEST['url']の中に入っていました。
ですのでそれを抽出し、$_REQUESTに再度入れています。
AccessTokenが取得できれば、あとは簡単にユーザ情報を取得できます。
このユーザ情報を、Auth->login()に引数として渡してあげれば
以降ログインユーザとしてこのFacebookユーザの情報が利用できます。
この様に、自前でユーザテーブルを持ってなくても
Facebookで認証したユーザの情報でログインできますし、
もちろんDBを用意し、AccessTokenをDBに保存して再利用することもできると思います。
いかがだったでしょうか。
OAuth認証はFacebookだけでなくTwitterやMixiなどでも
利用されている認証方法です。
authenticate()をオーバーライドするだけで
Authコンポーネントから簡単に利用することができるので
CakePHP2.0を使う際にはちょっと試してみるのも、面白いかと思います。
明日は、@takuo_doiさんです。
よろしくお願いします〜!
追記
Facebookのアプリ認証の後callback url にリダイレクトされる際に
URL末尾に'#_=_'という謎の文字列がくっついてしまう、という現象がありました。
どうやらこれはFacebook側のBUGのようです。
この文字列がついているとloginRedirectなどにリダイレクトした後も、
'#_=_'がついてまわってしまうので、どこかの処理で取り除いてあげる必要がありそうです。
すいません。この辺、ほったらかしてます。
BUG
https://developers.facebook.com/blog/post/552/
http://bugs.developers.facebook.net/show_bug.cgi?id=20504
参照:CakePHP2.0Cookbook
http://book.cakephp.org/2.0/en/core-libraries/components/authentication.html
昨日は、@ogaaaanさん
でした。
CakePHP2.0でAuthComponentは大きく変わったものの一つです。
1.3のAuthComponentでのユーザ認証は、基本的にはPOSTされたusername,passwordを
userModelに問い合わせに行くことで認証をおこなっていましたが、
2.0ではこの認証方法にBASIC認証と、Digest認証が追加されました。
さらにオリジナルの認証方法の追加もできるようになりました。
今回は、この認証方法にFacebookのOAuth認証を利用したユーザ認証を追加してみたいとおもいます。
作成したソースは以下になります。
GitHub
https://github.com/MiuraKatsu/cakephp-FacebookAuthenticate
オリジナルの認証方法を追加するには、Controller/Component/Auth/の下に
Authenticateクラスファイルを作成します。
今回はFacebookAuthenticate.phpを作成しました。
FacebookのOAuthに関してはFacebook提供のSDKを使用することにします。
SDK
https://github.com/facebook/php-sdk/
http://developers.facebook.com/blog/post/534/
vendorsかVendor配下にSDKを置いて
App::import('Vendor','facebook', array('file' => 'facebook/src/facebook.php'));
で宣言します。
拡張Authenticateの作り方ですが、とりあえずBaseAuthenticateを継承したクラスを作ります。
そして認証のロジックをauthenticate()メソッドにオーバーライドします。
その他の共通的なメソッドはBaseAuthenticateに記述してあるものでカバーできますので、
基本的にはauthenticate()メソッドで認証ロジックを実装するだけでOKです。
App::uses('Router', 'Routing');
App::uses('BaseAuthenticate', 'Controller/Component/Auth');
App::import('Vendor','facebook', array('file' => 'facebook/src/facebook.php'));
class FacebookAuthenticate extends BaseAuthenticate {
このauthenticate()メソッドですが、具体的にAuthComponetnの中で呼び出されるのは
Auth->identify()の中で、そのidentify()が呼び出されるのが、Auth->login()になります。
ですので、通常のloginではそのままAuth->loign()を利用し、
Callbackの中ではAuth->identify()を呼んで、最終的に認証させています。
ではまず使用するコントローラー側の説明です。
Componentの宣言は,
public $components = array('Auth');
でもいいですし
beforeFilter()のなかで行ってもいいです。
public beforeFilter(){
$this->Auth->authenticate = array(
'Facebook',
);
$this->Auth->allow('index', 'login','callback','logout');
$this->Auth->loginRedirect = '/users/index/';
$this->Auth->logoutRedirect = '/users/index/';
parent::beforeFilter();
}$this->Auth->authenticateに指定した認証方法が複数あれば、それら全てで認証チェックされます。
public function index(){
$user = $this->Auth->user();
if($user['Member']['user_id']){
$this->set('title_for_layout', $user['Member']['user_name'] . 'さんのマイページ');
}else{
$this->set('title_for_layout', 'ゲストさんのマイページ');
}
}1.3では、login処理はlogin()アクションを定義するだけで、勝手に行われていましたが
2.0では明示的に$this->Auth->login()を呼ぶ必要があります。
今回は、FacebookのOAuth認証を使用しています。
OAuth認証では、認証処理の途中でFacebookへのリダイレクトが入るので、login()ではRenderはしません。
public function login(){
$user = $this->Auth->user();
if(isset($user['Member']['user_id'])){
$this->redirect($this->Auth->loginRedirect);
}else{
$this->Auth->login();
$this->autoRender = false;
}
}さらにOAuth認証はFacebookへのリダイレクトの後、callback URLにアクセスし、認証が完了します。
ですので、前述したように、このcallback()メソッドの中で、Auth->identify()にアクセスし、
最終的にFacebookAuthenticate->authenticate()を呼び出しています。
このcallback()も最終的にはAuth->loginRedirectにリダイレクトされるので、こちらもRenderしてません。
public function callback(){
$this->autoRender = false;
$user = $this->Auth->identify($this->request,$this->response);
}つまりauthenticate()メソッドが一回の認証内で2度呼び出されているので
呼び出し元のaction名によって処理を分けています。
最初のlogin()からの呼び出しではfacebookへのリダイレクトを行い、
facebookでのアプリ認証が許可されてcallbackされたcallback()からの呼び出しでは、
accessTokenを取得し、そのAccessTokenを利用して
Facebookのユーザ情報を入手しています。
callback()からの呼び出しでは、
FacebookSDKのgetAccessToken()メソッドを使って、AccessTokenを取得しているのですが、
getAccessToken()メソッドは$_REQUEST['state']を直接参照しており、その値でAccessTokenを取得しにいっています。
自分の環境ですと、$_REQUESTにstateが入ってこずに、$_REQUEST['url']の中に入っていました。
ですのでそれを抽出し、$_REQUESTに再度入れています。
preg_match('/state=(.*)/',$_REQUEST['url'],$state);
$_REQUEST['state'] = $state[1];
$accessToken = $facebook->getAccessToken();AccessTokenが取得できれば、あとは簡単にユーザ情報を取得できます。
$me = $facebook->api('/me');
$user = $this->_Collection->Auth->user();
$user['Member']["user_id"] = $me['id'];
$user['Member']["user_name"] = $me['name'];
$user['Member']["access_oauth_token"] = $access_oauth_token;
if ($this->_Collection->Auth->login($user)) {
$loginRedirect = $this->_Collection->Auth->loginRedirect;
$response->header('Location', $loginRedirect);
$response->send();
}このユーザ情報を、Auth->login()に引数として渡してあげれば
以降ログインユーザとしてこのFacebookユーザの情報が利用できます。
この様に、自前でユーザテーブルを持ってなくても
Facebookで認証したユーザの情報でログインできますし、
もちろんDBを用意し、AccessTokenをDBに保存して再利用することもできると思います。
いかがだったでしょうか。
OAuth認証はFacebookだけでなくTwitterやMixiなどでも
利用されている認証方法です。
authenticate()をオーバーライドするだけで
Authコンポーネントから簡単に利用することができるので
CakePHP2.0を使う際にはちょっと試してみるのも、面白いかと思います。
明日は、@takuo_doiさんです。
よろしくお願いします〜!
追記
Facebookのアプリ認証の後callback url にリダイレクトされる際に
URL末尾に'#_=_'という謎の文字列がくっついてしまう、という現象がありました。
どうやらこれはFacebook側のBUGのようです。
この文字列がついているとloginRedirectなどにリダイレクトした後も、
'#_=_'がついてまわってしまうので、どこかの処理で取り除いてあげる必要がありそうです。
すいません。この辺、ほったらかしてます。
BUG
https://developers.facebook.com/blog/post/552/
http://bugs.developers.facebook.net/show_bug.cgi?id=20504
参照:CakePHP2.0Cookbook
http://book.cakephp.org/2.0/en/core-libraries/components/authentication.html
2010年10月26日
CakePHP1.2 CacheBehaviorでモデルのメソッドキャッシュ
http://www.exgear.jp/blog/2008/11/method_cache_behavior/
http://d.hatena.ne.jp/lifegood/20090604/p1
こちらを元ネタにして
若干改良をほどこしました。
1.CacheBehaviorの導入
2.find()のオーバーライド
http://d.hatena.ne.jp/lifegood/20090604/p1
こちらを元ネタにして
若干改良をほどこしました。
1.CacheBehaviorの導入
class CacheBehavior extends ModelBehavior {
var $enabled = true;
var $config = array();
function setup(&$model, $config = array()) {
$this->config[$model->alias] = $config;
}
//config設定
function _setConfig(&$model,$config = null){
if(empty($config)){
$config = $this->config[$model->alias];
}
if(is_array($config)){
if(!empty($config['config'])){
$config = $config['config'];
}else{
$config = null;
}
}
return $config;
}
/**
* メソッドキャッシュ
*/
function cacheMethod(&$model, $method, $args = array(),$config = null){
$config = $this->_setConfig($model,$config);
$this->enabled = false;
// キャッシュキー
$cachekey = $this->createCacheKey($model, $method, $args ,$config);
$ret = Cache::read($cachekey,$config);
if(!empty($ret)){
$this->enabled = true;
return $ret;
}
$ret = call_user_func_array(array($model, $method), $args);
$this->enabled = true;
Cache::write($cachekey, $ret, $config);
// クリア用にモデル毎のキャッシュキーリストを作成
$cacheListKey = get_class($model) . '_cacheMethodList';
$list = Cache::read($cacheListKey);
$list[$cachekey] = $config;
Cache::write($cacheListKey, $list);
return $ret;
}
/**
* キャッシュキーの生成
*
*/
function createCacheKey(&$model, $method, $args = array(),$config = null){
return get_class($model) . '_' . $method . '_' . $this->_setConfig($model,$config) . '_' . md5(serialize($args));
}
/**
* 再帰防止判定用
*/
function cacheEnabled(&$model){
return $this->enabled;
}
/**
* キャッシュ個別クリア
*/
function cacheDelete(&$model, $method, $args = array(),$config = null){
$config = $this->_setConfig($model,$config);
$cacheListKey = $this->createCacheKey($model, $method, $args, $config);
Cache::delete($cacheListKey,$config);
}
/**
* キャッシュ全クリア
*/
function cacheDeleteAll(&$model){
$cacheListKey = get_class($model) . '_cacheMethodList';
$list = Cache::read($cacheListKey);
if(empty($list)) return;
foreach($list as $key => $config){
Cache::delete($key,$config);
}
Cache::delete($cacheListKey);
}
/**
* 追加・変更・削除時にはキャッシュをクリア
*/
function afterSave(&$model, $created) {
$this->cacheDeleteAll($model);
}
function afterDelete(&$model) {
$this->cacheDeleteAll($model);
}
}
2.find()のオーバーライド
class AppModel extends Model {
var $actsAs = array('Cache' => array('config'=>'_app_find_'));
function find($conditions = null, $fields = array(), $order = null, $recursive = null) {
// Call cache method
$args = func_get_args();
if ($this->Behaviors->attached('Cache')) {
if($this->cacheEnabled()) {
return $this->cacheMethod(__FUNCTION__, $args );
}
}
// Case normal find. The model does not have cache behavior.
return parent::find($conditions, $fields, $order, $recursive);
}
}
2009年06月08日
CakePHPのafter,beforeフック系メソッドの順番
「むしの手記。」さんが
CakePHPのフック系メソッドの動きについてまとめられていましたので、
さらにComponentのメソッドも含めて試してみました。
結果はこちら
想外だったのは、Controller の beforeRender の後に Component の beforeRender が動くこと。なんとなく Component のほうが先に動くような気がしていた。
CakePHPのフック系メソッドの動きについてまとめられていましたので、
さらにComponentのメソッドも含めて試してみました。
結果はこちら
Component::initialize(&$controller)
↓
Controller::beforeFilter()
↓ ↓
↓ Component::startup(&$controller)
↓
Controller::Action()
↓
↓
Controller::beforeRender()
↓ ↓
↓ Component::beforRender(&$controller)
↓
↓ Helper::beforeLayout()
↓ ↓
---- View->renderLayout() ---------------------
↓ ↓
↓ Helper::afterLayout()
↓
---- View->render() ---------------------------
↓
↓ Component::shutdown(&$controller)
↓ ↓
Controller::afterFilter()
↓
Helper::afterRender()
想外だったのは、Controller の beforeRender の後に Component の beforeRender が動くこと。なんとなく Component のほうが先に動くような気がしていた。

