PHPでのOpenIDを利用したログイン認証の実装について。
OpenIDの概要は調べるかWebの分散ID認証システムについてにて。とりあえず実装してみて、という感じなので間違っていたら指摘お願いします。
OpenID EnabledのPHP用ライブラリを利用する。
他にもVidentity.orgやPHP ClassesにOpenIDのライブラリはあるが更新が止まっていたり簡易的なものだったりするのでOpenID Enabledのものを使用。
実行環境
- WindowsXP SP2
- PHP 5.2.3
- PHP OpenID Library 2.0.0-rc2
PHP OpenID Library 1.x.x系統と若干実装が違うので互換性は無い。
サンプルを動かしてみる
ライブラリを解凍するとexamplesディレクトリがある、コンシューマ側なのでexamples/consumerを実行してみる。
OpenID関連で生成されるファイル格納ディレクトリが作れないとエラーが出た。デフォルトで「/tmp/_php_consumer_test」に作るようになっているので「/tmp」を作ってやる(Windowsで「/tmp」は、Dドライブで実行しているなら「D:\tmp」)。
次にOpenIDで使用する暗号生成部でエラーになった。追ってみるとデフォルトで「/dev/urandom」を使用するようになっている。「/dev/urandom」はカーネル乱数ジェネレータでWindowsには無い。
定数Auth_OpenID_RAND_SOURCEがNULLの場合、PHP内で乱数を生成するのでWindowsの場合は先に以下のようにNULLで定数定義する。
<?php
// Windowsの場合カーネル乱数ジェネレータを使用しない
if (!strncmp(PHP_OS, 'WIN', 3)) {
define('Auth_OpenID_RAND_SOURCE', NULL);
}
この2点の変更で動作確認は出来た。確認にはOpenID.ne.jpを使用。
OpenIDでの認証をアプリケーションに組み込むために
組み込む為には入力されたIDとアプリケーションのデータ(会員ID)を結び付ける必要がある。
先に会員登録としてOpenIDを入力させて登録させる形式より、先に認証をおこない認可された場合ログインフラグを立ててアプリケーションデータと比較し、無ければ入力したIDと取得したデータ(ニックネームやメールアドレス)を登録する方がOpenIDで管理している情報を利用できるのでスマート。
取得したデータ以外に必要な情報があれば、認証時に登録画面に遷移。OpenID1.0で登録できる情報はE-mail、ニックネーム、氏名、性別、国籍、郵便番号、言語、生年月日。OpenID2.0ではプロトコルにXRIが使えてもう少し詳細な情報がやりとりできるらしい。
ユーザーから入力されたIDと結果として取得できるIDは違う場合があるので認証リダイレクト前に一度セッションに格納してから認可後にセッションから取得した値をアプリケーション側に登録する必要がある。
Zend Frameworkで使用
軽くラッピングしてZend Frameworkで使ってみた。
とりあえずザッと書いて動かしてみたものを以下に。
Zend_Controller_Actionを継承してpreDispatch()に認証処理を実装、実際のControllerはこのクラスを継承する。デフォルトで認証をおこなうようになっているので、認証が必要ない場合は$this->checkLogin = false;でオーバーライド。
<?php
class Sample_Controller_Action_Base extends Zend_Controller_Action {
/**
* @var bool ログインチェックフラグ
*/
protected $checkLogin = true;
/**
* @var Zend_Session
*/
public $session;
/**
* 初期化
*/
public function init() {
$this->session = new Zend_Session_Namespace();
}
/**
* preDispatch
*/
public function preDispatch() {
// ログイン認証
if ($this->checkLogin && !$this->session->login) {
$auth = new Sample_Auth_OpenID();
if (!empty($_REQUEST['openid_url'])) {
$this->session->openid_url = $_REQUEST['openid_url'];
$process_path = '/home'; // 適当に遷移に合わせて
$auth->authenticate($_REQUEST['openid_url'], $process_path);
}
// 認可結果判別
if ($auth->checkResult()) {
// 認可時
$this->session->login = true;
echo $this->session->openid_url;
print_r($auth->getContents());
} else {
// 拒否/失敗
$this->session->destroy();
echo 'Auth failed...';
}
}
}
}
で、以下がOpenIDの認証用クラス。といってもexample纏めた程度。
<?php
class Sample_Auth_OpenID {
/**
* @var Auth_OpenID_Consumer
*/
protected $_consumer = NULL;
/**
* OpenID関連で生成されるファイル格納ディレクトリパス
* @var string
*/
public $_store_path = "/tmp/_php_consumer";
/**
* 承認フラグ
* @var bool
*/
protected $_valid = false;
/**
* 認証レスポンス
* @var object
*/
protected $_response = NULL;
/**
* 取得内容
* @var array
*/
protected $_contents = array();
/**
* コンストラクタ
*/
public function __construct() {
// Windowsの場合カーネル乱数ジェネレータを使用しない
if (!strncmp(PHP_OS, 'WIN', 3)) {
define('Auth_OpenID_RAND_SOURCE', NULL);
}
if (!file_exists($this->_store_path) && !mkdir($this->_store_path)) {
die("Could not create the FileStore directory");
}
$this->_consumer = new Auth_OpenID_Consumer(new Auth_OpenID_FileStore($this->_store_path));
require_once "Auth/OpenID/SReg.php";
}
/**
* OpenIDによる認証
*
* @param string $checkid
* @param string $process_path
* @return bool 認証失敗時
*/
public function authenticate($checkid, $process_path) {
// 接続スキーマ
$scheme = 'http';
$scheme = (isset($_SERVER['HTTPS']) and $_SERVER['HTTPS'] == 'on') ? 'https' : 'http';
// 承認URL
$trust_root = sprintf("$scheme://%s:%s%s",
$_SERVER['SERVER_NAME'], $_SERVER['SERVER_PORT'],
dirname($_SERVER['PHP_SELF']));
// 承認後リダイレクト先URL
$process_url = $trust_root . $process_path;
// 認証開始
$auth_request = $this->_consumer->begin($checkid);
// 入力されたOpenIDが使用出来ず失敗
if (!$auth_request) {
return false;
}
// 認証時に取得するデータ
$sreg_request = Auth_OpenID_SRegRequest::build(
// Required
array('nickname'),
// Optional
array('fullname', 'email'));
if ($sreg_request) {
$auth_request->addExtension($sreg_request);
}
// 承認の為リダイレクト
if ($auth_request->shouldSendRedirect()) {
$redirect_url = $auth_request->redirectURL($trust_root, $process_url);
// If the redirect URL can't be built, display an error
// message.
if (Auth_OpenID::isFailure($redirect_url)) {
displayError("Could not redirect to server: " . $redirect_url->message);
} else {
// Send redirect.
header("Location: ".$redirect_url);
exit;
}
}
}
/**
* 認証結果判別
*
* @return bool 認証可否
*/
public function checkResult() {
$this->_response = $this->_consumer->complete();
// 認可時のみ値セット
if ($this->_response->status == Auth_OpenID_SUCCESS) {
$this->_valid = true;
$this->_contents = Auth_OpenID_SRegResponse::fromSuccessResponse($this->_response)->contents();
$id = ($this->_response->endpoint->canonicalID) ? $this->_response->endpoint->canonicalID : $this->_response->identity_url;
}
return $this->isValid();
}
/**
* @var bool 認可結果
*/
public function isValid() {
return $this->_valid;
}
/**
* @return array 取得情報
*/
public function getContents() {
return $this->_contents;
}
}
認証APIやプロトコル仕様が乱立してるけど何が主流になるやら。
