PHP OpenID 認証。mixi でログイン。コードサンプル :【OpenID】

2010/11/14 : 更新しました
オリジナルのサンプルコードは少し使いづらいし、解りにくいと思うので
少し整理してみました。

ログインをどう設計するかによって使い方は変わると思いますが、
このほうがファイルが多くてもいろいろな仕様に対応しやすいと思います


※ このパッケージだけでは実行できません
( PHP OpenID library を使用しています )

OpenID を使用したログイン用のコードのサンプルです。

login.php と return.php は、サーバーの設定でエラーメッセージ
が出無い場合の対応の為に分割しています( nifty LaCoocan 等 )



共通
common.php
<?php
// *********************************************************
// デバッグ用ログファイルの位置
// ( コメントにすると、ログは出力されません )
// *********************************************************
$logfile = "./debug.log";
// *********************************************************
// 証明書の位置
// *********************************************************
$openid_pem = realpath("./cacert.pem");
// *********************************************************
// 作業ディレクトリの位置
// *********************************************************
$store_path = realpath("../") . DIRECTORY_SEPARATOR  . "_php_consumer_dir";
if (!file_exists($store_path) && !mkdir($store_path)) {
	print "保存用ディレクトリを作成できませんでした '$store_path'".
	" 書き込み権限をチェックして下さい";
	exit(0);
}

// *********************************************************
// OpenID 用 URL 文字列
// *********************************************************
$scheme = 'http';
if (isset($_SERVER['HTTPS']) and $_SERVER['HTTPS'] == 'on') {
	$scheme .= 's';
}

// 戻ってきた情報を受け取る場所
$return_to = "return.php";
$return_to = 
	sprintf("%s://%s:%s%s/$return_to",
		$scheme, $_SERVER['SERVER_NAME'],
		$_SERVER['SERVER_PORT'],
		dirname($_SERVER['PHP_SELF'])
	);

// 呼び出し元のディレクトリ
$trust_root = 
	sprintf("%s://%s:%s%s/",
		$scheme, $_SERVER['SERVER_NAME'],
		$_SERVER['SERVER_PORT'],
		dirname($_SERVER['PHP_SELF'])
	);

// *********************************************************
// HTTP ヘッダ
// *********************************************************
header( "Content-Type: text/html; Charset=shift_jis" );
header( "Expires: Wed, 31 May 2000 14:59:58 GMT" );

// *********************************************************
// デバッグログ開始位置
// *********************************************************
log_file("+++++++++++++++++++");
log_file("openid_pem=$openid_pem");
log_file("store_path=$store_path");
log_file("scheme=$scheme");
log_file("return_to=$return_to");
log_file("trust_root=$trust_root");

// *********************************************************
// Windows 環境とランダム要素(/dev/urandom)の対応
// *********************************************************
if ( substr( strtoupper( php_uname("s") ), 0, 7 ) == 'WINDOWS' ) {

	log_file("windowです");

	define('Auth_OpenID_RAND_SOURCE', NULL);
	if ( !extension_loaded( "curl" ) ) {
		log_file("php_curl.dll load");
//		dl("php_curl.dll"); 非推奨または定義されない関数
		exit("curl を使用できません");
	}
	if ( !extension_loaded( "openssl" ) ) {
		log_file("php_openssl.dll load");
//		dl("php_openssl.dll"); 非推奨または定義されない関数
		exit("openssl を使用できません");
	}
}
else {
	if ( @is_readable('/dev/urandom') ) {
	}
	else {
		define('Auth_OpenID_RAND_SOURCE', NULL);
	}
}

// *********************************************************
// include_path に、PHP OpenID Library の位置をセット
// *********************************************************
$path_extra = realpath("../openid2");
$path = ini_get('include_path');
$path = $path_extra . PATH_SEPARATOR . $path;
ini_set('include_path', $path);

// *********************************************************
// PHP OpenID Library ( 利用側 )
// *********************************************************
require_once "Auth/OpenID/Consumer.php";
require_once "Auth/OpenID/FileStore.php";
require_once "Auth/OpenID/SReg.php";
require_once "Auth/OpenID/PAPE.php";


// *********************************************************
// デバッグログ
// *********************************************************
function log_file($message) {
	if ( $GLOBALS['logfile'] != "" ) {
		error_log("$message\n", 3, $GLOBALS['logfile']);
	}
}

?>


mixi へ、リダイレクト前
ログイン済みのページは PHP 部分の先頭で以下の構成にする必要があります
( ログインされていない場合は、ログイン用のページを表示 )
index.php
<?
session_start();
header( "Content-Type: text/html; Charset=shift_jis" );
header( "Expires: Wed, 31 May 2000 14:59:58 GMT" );

if ( $_GET['logout'] != "" ) {
	$_SESSION['id'] = "";
	$_SESSION['nickname'] = "";
}

if ( $_SESSION['id'] == "" ) {
	// エラーメッセージを表示
	ini_set( 'display_errors', "1" );
	
	// ログインされていないので、ログインページを表示
	require_once( 'login_view.php' );


	exit();
}

?>
<HTML>
<HEAD>
<META http-equiv="Content-type" content="text/html; charset=shift_jis" />
<TITLE>ようこそ</TITLE>
<STYLE type="text/css">
* {
	font-size: 30px;
}
</STYLE>
<SCRIPT language="javascript" type="text/javascript">

</SCRIPT>
</HEAD>
<BODY>

<?
	if ( $_SESSION['nickname'] != '' ) {
		print "ようこそ {$_SESSION['nickname']} さん";
	}

?>
<br><br>
ID: <?= $_SESSION['id'] ?>

<FORM>
<INPUT type=submit name=logout value="ログアウト">
</FORM>
</BODY>
</HTML>
login_view.php
<HTML>
<HEAD>
<META http-equiv="Content-type" content="text/html; charset=shift_jis" />
<TITLE>ログイン</TITLE>
<STYLE type="text/css">
* {
	font-size: 16px;
}
</STYLE>
</HEAD>
<BODY>

<? if ( $_SESSION['id'] == "" ) { ?>
<A href="login.php?id=https%3A%2F%2Fmixi.jp"><IMG src="http://winofsql.jp/test/openid/openid_sample/login_btn002.gif" border=0></A>
<br><br>
<A href="login.php?id=https%3A%2F%2Fwww.google.com%2Faccounts%2Fo8%2Fid">Google でログイン</A>
<br><br>
<A href="login.php?id=https%3A%2F%2Fme.yahoo.co.jp">Yahoo でログイン</A>
<? } ?>

</BODY>
</HTML>
以下のページを login.php?id=https%3A%2F%2Fmixi.jp と指定して
リンクを作成できます。
( login.php 単独では何も表示されません。)

※ mixi 以外でも動くはずですので、実際は入力チェックが必要です
login.php
<?
session_start();
// エラーメッセージを表示
ini_set( 'display_errors', "1" );

// エラーメッセージ対象のコード
require_once( 'login_control.php' );
?>
login_control.php
<?php
require_once "common.php";

// *********************************************************
// 基本オブジェクト作成
// *********************************************************
$store = new Auth_OpenID_FileStore($store_path);
log_file(print_r($store,true));
$consumer = new Auth_OpenID_Consumer($store);
log_file(print_r($consumer,true));

// *********************************************************
// 対象
// *********************************************************
if ( $_GET['id'] != "" ) {
	$openid = $_GET['id'];
	log_file(print_r($openid,true));

	$error_message = "";
	$auth_request = $consumer->begin($openid);
	if (!$auth_request) {
		$error_message = "OpenID が正しくありません";
	}

	if ( $error_message == "" )	 {
		// nickname 等が必要無い場合はここを実行する必要はない
		$sreg_request = 
			Auth_OpenID_SRegRequest::build(
				// Required
				array('nickname'),
				// Optional
				array('fullname', 'email')
			);
		if ($sreg_request) {
			$auth_request->addExtension($sreg_request);
		}
		log_file(print_r($auth_request,true));
	}
	
	if ( $error_message == "" )	 {
		if ($auth_request->shouldSendRedirect()) {
			$redirect_url = 
				$auth_request->redirectURL( $trust_root, $return_to );

				if (Auth_OpenID::isFailure($redirect_url)) {
					$error_message =
						"サーバーにリダイレクトできません:"
						. $redirect_url->message;
				}
				else {
					log_file(print_r($redirect_url,true));
					header("Location: ".$redirect_url);
				}
		}
		else {
			$form_id = 'openid_message';
			$form_html = 
				$auth_request->htmlMarkup(
					$trust_root, $return_to,
					false,
					array('id' => $form_id )
				);
		
			if (Auth_OpenID::isFailure($form_html)) {
				$error_message =
					"サーバーにリダイレクトできません(HTML):"
					. $form_html->message;
			}
			else {
				log_file(print_r($form_html,true));
				print $form_html;
			}
		
		}
	}
}

// リダイレクトされなかった場合の表示
require_once "login_view_message.php";

?>
JavaScript によってリダイレクトされるので以下のコードは
実際は必要ありません。しかし、「OpenID が正しくありません」
または、「サーバーにリダイレクトできません」を表示する必要
があるような場合は、使用すると良いでしょう。
login_view_message.pjp
<HTML>
<HEAD>
<META http-equiv="Content-type" content="text/html; charset=shift_jis" />
<TITLE>ログイン</TITLE>
<STYLE type="text/css">
* {
	font-size: 12px;
}
</STYLE>
</HEAD>
<BODY>

<?= $error_message ?>

</BODY>
</HTML>


mixi から戻って来てからの処理
return.php
<?
session_start();
// エラーメッセージを表示
ini_set( 'display_errors', "1" );

// エラーメッセージ対象のコード
require_once( 'return_control.php' );
?>
return_control.php
<?php
require_once "common.php";

// *********************************************************
// 基本オブジェクト作成
// *********************************************************
$store = new Auth_OpenID_FileStore($store_path);
log_file(print_r($store,true));
$consumer = new Auth_OpenID_Consumer($store);
log_file(print_r($consumer,true));

// *********************************************************
// 結果チェック
// *********************************************************
$error_message = "";
$response = $consumer->complete($return_to);
log_file(print_r($response,true));

if ($response->status == Auth_OpenID_CANCEL) {
	$error_message = 'キャンセルされました';
}
if ($response->status == Auth_OpenID_FAILURE) {
	$error_message = $response->message;
}
if ($response->status == Auth_OpenID_SUCCESS) {
	$start_message = "ログインされました";
	$_SESSION['id'] = $response->getDisplayIdentifier();

	$sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse($response);
	log_file(print_r($sreg_resp,true));

	$sreg = $sreg_resp->contents();
	log_file(print_r($sreg,true));

	if (@$sreg['nickname']) {
		$_SESSION['nickname'] = 
			mb_convert_encoding( $sreg['nickname'], "SHIFT_JIS", "UTF-8" );
	}

}


require_once "return_view.php";

?>
return_view.php
<HTML>
<HEAD>
<META http-equiv="Content-type" content="text/html; charset=shift_jis" />
<TITLE>ログイン</TITLE>
<STYLE type="text/css">
* {
	font-size: 12px;
}
</STYLE>
</HEAD>
<BODY>

<A href="login.php?id=https%3A%2F%2Fmixi.jp"><IMG src="http://winofsql.jp/test/openid/openid_sample/login_btn002.gif" border=0></A>
<br>
<B><?= $error_message ?></B>

<? if ( $error_message == '' ) { ?>
<SCRIPT type="text/javascript">
	window.location = "index.php";
</SCRIPT>
<? } ?>
</BODY>
</HTML>