seseragiseven

【PHP】安全な自動ログイン機能というものを作ってみた

現在勉強した限りの知識で、最もセキュアなPHP自動ログイン機能を作ってみた。

今回参考にしたサイト

↑にすべて書いてあるんだけど、備忘録も兼ねて自分なりにわかりやすくまとめてみる。

大前提として、日々新しいハッキングの方法やらウイルスやらが作られている時代なので、本当に重要な情報を扱っている場合、自動ログイン機能は無い方がいい。情報の重要さに対してユーザーの効率のほうが優先されるべきだと判断した場合に実装する。Twitterなんて見られようが消されようがどうでもいいもんね。

実装方法の種類

まずは実装方法。
よくある自動ログイン機能だけど、いざ実装しようと思ったら(セキュリティ的な部分は置いておいて)3つの方法がある。

1、CookieにIDとパスワードを保存しておく

一番ダメな例。
cookieにログインに必要な情報(クレデンシャル情報と言うらしい)を保存しておき、サイト側でそれを読み取ることで自動ログインを実現する。

cookieの値はXSS(クロスサイトスクリプティング)攻撃などで盗まれる可能性があるため、クレデンシャル情報は保存するべきではない。
たとえ暗号化していたとしても完璧とはいえないし、そもそもここに保存する必要がない情報を保存することにより「攻撃可能領域の最小化の原則」に反するため、この方法は取らない方がいい。

2、セッションの有効期限を長くする

すぐに自動ログイン機能を作って!と言われた時はこれになりがち。機能自体はすぐに作れる。
ユーザーが今ログインしているかというのは普通セッションで判断しているので、そのセッションを切れないように延長して自動ログインを実現する。

この場合、ユーザーの意思にかかわらず自動ログイン状態になりがちで、長期期限のセッションデータがユーザーの数だけ溜まっていく。セッションは破棄のタイミングも割と曖昧なので、時間が経つと現状を把握もできず気持ち悪い。

3、オートログイン専用のcookieを作り、それで判断する

一番まともな実装。
cookieに保存する情報はクレデンシャル情報とは関係ないランダムな値にする。それをデータベースと参照して、合致したら自動的にログインするという仕組み。

ログイン管理自体はセッションで管理しているけど、期限を長くしたりはせずそのままで使う。ブラウザを閉じたら切れる。セッションは切れて、オートログインのキーを持ったcookieだけが残る。
ログイン情報のセッションが切れている状態でサイトを訪れると、上記の仕組みでログインされる。
cookieの情報はログインには全く関係のない情報なので、取られでも問題はない。より安全性を高めるためには、ログイン毎にキーを作り直すと良い。

作ってみた

  • 自動ログインは、cookieとデータベースを使って実装する。
  • cookieに保存する値は、自動ログイン時だけに使うランダムな値。

手動ログイン時

ユーザー名、パスワードを入力してログインした時。

// 一旦auto_loginを削除
if ( ! empty( $_COOKIE['auto_login'] )) {
	$this->delete_auto_login( $_COOKIE['auto_login'] );
}
// 新たにauto_loginをセット
if( ! empty( $auto_login )) {
	$this->setup_auto_login( $user_name );
}

/*--------------------------------------------------
オートログイン セットアップ
--------------------------------------------------*/
public function setup_auto_login( $user_name )
{
	$cookieName = 'auto_login';
	$auto_login_key = sha1( uniqid() . mt_rand( 1,999999999 ) . '_auto_login' );
	$cookieExpire = time() + 3600 * 24 * 7; // 7日間
	$cookiePath = '/';
	$cookieDomain = $_SERVER['SERVER_NAME'];
	
	/*
	$sql = "
	INSERT INTO auto_login ( user_name , auto_login_key )
	VALUES ( :user_name , :auto_login_key )";
	*/
	$this->db_manager->get('Author')->autoLoginSet( $user_name , $auto_login_key );
	
	setcookie( $cookieName, $auto_login_key, $cookieExpire, $cookiePath, $cookieDomain );
}

/*--------------------------------------------------
オートログイン デリート
--------------------------------------------------*/
public function delete_auto_login( $auto_login_key = '' )
{
	$author = $this->session->get('author');
	
	/*
	$sql = "DELETE FROM auto_login WHERE user_name=:user_name";
	*/
	$this->db_manager->get('Author')->autoLoginDeleteByName( $author['user_name'] );
	
	$cookieName = 'auto_login';
	$cookieExpire = time() - 1800;
	$cookiePath = '/';
	$cookieDomain = $_SERVER['SERVER_NAME'];
	
	setcookie( $cookieName, $auto_login_key, $cookieExpire, $cookiePath, $cookieDomain );
}

ちょこちょこパーフェクトPHPのフレームワークの用法が出てきててすみません。

フォームで「自動でログインする」にチェックが入っていた時に$auto_loginに値が入り、自動ログインキーをセットアップする。
cookieの値はsha1( uniqid() . mt_rand( 1,999999999 ) . '_auto_login' );というランダムな文字列にしていて、これをデータベースとcookieに保存する。その際最後に何らかの固定の文字列(今回は「_auto_login」通称salt)を入れることでかなり解読されにくくなる。ランダムなキーは必ずsalt付きでハッシュすると良い。
期限は仮に7日間としているけど、適宜変更してください。

自動ログイン時

/*--------------------------------------------------
オートログイン処理
--------------------------------------------------*/
if ( ! empty( $_COOKIE['auto_login'] )) {
	$auto_login_key = $_COOKIE['auto_login'];
	$author_repository = $this->db_manager->get('Author');
	
	/*
	$sql = "SELECT * FROM auto_login WHERE auto_login_key = :auto_login_key";
	*/
	$is_auto_login = $author_repository->autoLoginFetch( $auto_login_key );
	
	if ( $is_auto_login !== false ) {
		if ( ! empty( $auto_login_key )) {
			$this->delete_auto_login( $auto_login_key );
		}
		$this->setup_auto_login( $is_auto_login['user_name'] );
		
		$author = $this->db_manager->get('Author')->fetchByUserName( $is_auto_login['user_name'] );
		$this->session->setAuthenticated( true );
		$this->session->set( 'author' , $author );
	}
}

セッションが切れている状態でページに来訪した場合、cookieに先ほど保存した値があるかを確認し、あればデータベースと照合する。それが合致すればそのユーザーをログイン状態とみなし、ログイン時のセッション情報を与える。その後新しくキーをセットし直す。

ログアウト時

if ( ! empty( $_COOKIE['auto_login'] )) {
	$this->delete_auto_login( $_COOKIE['auto_login'] );
}

ログアウト時は、データベースとcookieの値を削除する。

ちなみに自動ログインの期間は7日間としたけど、この仕組みではログインする度に期間がリセットされて、そこからまた7日間に設定される。
この値はログアウト時に破棄されるので、しばらく使わない時にはログアウトしておくと安全。

まとめ

何度も書いているけど、完全なる安全はないので、自動ログインしなくても良い場合は無い方が好ましい。
付ける場合は、なるべく安全に実装しましょう。

今回参考としている記事の執筆者はPHPのセキュリティにおいて知識をお持ちなので、今回の自動ログイン機能以外も見てみると楽しいと思う。
全体の仕組みや、防ぐやり方を知らないとセキュアなアプリケーションは作れないので、こういう記事は大変助かります。ありがとうございます。

久しぶりに長い記事を書いた。
こういうのも大事じゃ。

ちなみに文中に出てきたパーフェクトPHPとはこれのことです。MVCフレームワークの作り方や、PHPでは弱いとされるセキュリティの概要と対策など書かれていて、読んで損はない本です。

パーフェクトPHP (PERFECT SERIES 3)
小川 雄大 柄沢 聡太郎 橋口 誠
技術評論社
売り上げランキング: 141,186

この記事をシェアする

1 件のコメント

    コメントを残す

    カテゴリー

    人気の記事