サンプル・プログラムの実行例
サンプル・プログラムのダウンロード
plogin.php | サンプル・プログラム |
pahooInputData.php | データ入力に関わる関数群。 使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。 |
バージョン | 更新日 | 内容 |
---|---|---|
2.01 | 2022/06/27 | FastCGIで正常動作しない不具合を修正 |
2.0 | 2022/06/04 | 大幅改訂,PHP8対応 |
1.0 | 2018/10/18 |
バージョン | 更新日 | 内容 |
---|---|---|
1.1 | 2022/06/04 | getValidString() 修正 |
1.0 | 2022/05/18 |
準備
  34: //表示幅(ピクセル)
  35: define('WIDTH', 600);
  36:
  37: //プログラム・タイトル
  38: define('TITLE_SIGNUP', '新規登録');
  39: define('TITLE_LOGIN', 'ログイン画面');
  40: define('TITLE_RESULT', '処理結果');
  41: define('TITLE_LIST', '登録情報一覧');
  42:
  43: //バリデーション用
  44: define('ID_MIN', 4); //IDの最小長
  45: define('ID_MAX', 20); //IDの最大長
  46: define('ID_PATTERN', "/^[0-9|a-z|A-Z]+$/"); //IDとして許容するパターン
  47: define('PSW_MIN', 8); //パスワードの最小長
  48: define('PSW_MAX', 20); //パスワードの最大長
  49: define('PSW_PATTERN', "/^[0-9|a-z|A-Z|\"\#\$\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\@\[\\\\^\_\`\{\|\}\~]+$/"); //パスワードとして許容するパターン
  50:
  51: //セッション関連
  52: define('SESSION_KEY', 'login_pahoo'); //セッション・キー
  53: define('SESSION_LIFE', 2); //有効期間(分)
  54:
  55: //パスワードのハッシュ化
  56: define('DEF_SALT', 'qcq4vp5b9iu5nkcbfkqaic'); //Salt(PHP5.4以下で使用)
  57: if (!defined('PASSWORD_BCRYPT')) define('PASSWORD_BCRYPT', 0);
  58:
  59: //SQLite DBファイル名:各自の環境に合わせて変更すること
  60: define('DBFILE', './userinfo.sqlite3');
  61:
  62: //SQLite テーブル名
  63: define('TABLE_USER', 'user'); //ユーザー情報
  64:
  65: //実行するSQL
  66: define('PRE_SELECT', 'SELECT * FROM ' . TABLE_USER . ' WHERE id=:id');
  67: define('PRE_INSERT', 'INSERT INTO ' . TABLE_USER . ' (id, psw, session, dt, premiere, latest) VALUES (:id, :psw, :session, :dt, :premiere, :latest)');
  68: define('PRE_SESSION', 'UPDATE ' . TABLE_USER . ' SET session=:session, dt=:dt WHERE id=:id');
  69: define('PRE_LIST', 'SELECT * FROM ' . TABLE_USER . ' WHERE 1');
  70:
  71: //データ入力に関わる関数群:include_pathに配置すること
  72: require_once('pahooInputData.php');
後述するが、入力されたIDやパスワードの長さ、文字種を厳格にチェックするため、バリデーション用の定数を細かく用意している。
たとえば、IDは半角英数字(大文字・小文字識別)、パスワードは半角英数記号(大文字・小文字識別)としたが、ID_PATTERN や PSW_PATTERN を変更することで、文字種の制約を変更できる。許容パターンは正規表現である。
ログイン状態はセッションで管理しており、有効期間は SESSION_LIFE に定義する。
ID、パスワードを保存するデータベースは、インストールせずに利用できる SQLite を採用した。データベースファイルは DBFILE によって任意の場所に作成できる。
データ入力やパスワード処理に関わる関数群は別ファイル "pahooInputData.php" に分離しており、include_path が通ったディレクトリに配置すること。
画面遷移
登録情報一覧
ログイン状態の管理
今回は、次の条件でログイン状態かどうかを判断する。
- セッション変数 $_SESSION[SESSION_KEY] にIDがあるかどうか
- データベースのセッションIDと一致するかどうか
- データベースの最終アクセス日時と現在時刻の差が SESSION_LIFE 以内かどうか
 446: /**
 447: * ログイン状態かどうか
 448: * @param string $errmsg エラーメッセージ格納用(無ければ空文字)
 449: * @return bool TRUE:ログイン状態/FALSE:ではない
 450: */
 451: function islogged(&$errmsg) {
 452: $res = FALSE;
 453: if (isset($_SESSION[SESSION_KEY]) && ($_SESSION[SESSION_KEY] != '')) {
 454: if (getLoggedStatus($_SESSION[SESSION_KEY], $errmsg) == 1) {
 455: $res = TRUE; //ログイン中
 456: }
 457: }
 458: return $res;
 459: }
 810: //セッション開始
 811: session_set_cookie_params(SESSION_LIFE * 60);
 812: session_start();
 813:
 814: //DB初期化
 815: if (DBinit() == FALSE) {
 816: $title = TITLE_RESULT;
 817: $errmsg = 'データベースの初期化に失敗しました';
 818: $html = makeHTML_header($title) . makeHTML_result('', $errmsg) . makeHTML_footer();
 819: echo $html;
 820: exit(1);
 821: }
 822:
 823: //動作モード
 824: $mode = getParam('mode', FALSE, '');
 825: if ($mode == 'logout') logout($errmsg); //ログアウト
 826: if (islogged($errmsg)) $mode = 'logged'; //ログイン中
メインプログラムの冒頭に上述のように記載することで、ログイン状態かどうかの判定を行うことができる。
 371: /**
 372: * データベースからログイン状態を取得
 373: * @param string $id ID
 374: * @param string $errmsg エラーメッセージ格納用(無ければ空文字)
 375: * @return int 0:セッション破棄
 376: * 1:セッション維持
 377: * 2:二重ログイン
 378: * 3:エラー
 379: */
 380: function getLoggedStatus($id, &$errmsg) {
 381: $errmsg = '';
 382: if ($res = DBget_session($id, $session, $dt, $errmsg)) {
 383: $dt1 = new DateTime('now');
 384: $dt2 = new DateTime($dt);
 385: $t2 = dateInterval2Seconds($dt1->diff($dt2, TRUE));
 386: if ($t2 > (SESSION_LIFE * 60)) {
 387: DBset_session($id, '', '', $errmsg);
 388: unset($_SESSION[SESSION_KEY]);
 389: $res = 0;
 390: } else if (($session != '') && ($session != session_id())) {
 391: $errmsg = $id . ' はすでにログインしています';
 392: $res = 2;
 393: } else if (isset($_SESSION[SESSION_KEY])) {
 394: $session = session_id();
 395: $dt = date(DATE_W3C, time());
 396: $res = DBset_session($id, $session, $dt, $errmsg);
 397: $res = $res ? 1 : 3;
 398: } else {
 399: $res = 0;
 400: }
 401: } else {
 402: $res = 3;
 403: }
 404: return $res;
 405: }
まず、ユーザー関数 DBget_session を呼び出し、ログイン時にデータベースに記録したセッションID $session と、最後に DBget_session を呼び出した日時 $dt を取得する。
$dt と現在日時の差分が SESSION_LIFE より大きければ、データベースのセッションIDと日時をクリアする(ログイン状態ではない)。これにより、ログインしたままという状態を回避する。
日時差分が SESSION_LIFE より小さく、セッションIDがデータベースに登録してあるものと異なれば、多重ログインと判断する。
最後に、セッション変数が存在していればログイン状態を維持していると判断し、データベースの最終アクセス日時を現在日時に更新する。
ログイン/ログアウト
 407: /**
 408: * ログインする
 409: * @param string $id ID
 410: * @param string $errmsg エラーメッセージ格納用(無ければ空文字)
 411: * @return bool TRUE:ログイン成功/FALSE:失敗
 412: */
 413: function login($id, &$errmsg) {
 414: $res = getLoggedStatus($id, $errmsg);
 415: if ($res <2) {
 416: $session = session_id();
 417: $dt = date(DATE_W3C, time());
 418: $res = DBset_session($id, $session, $dt, $errmsg);
 419: if ($res == TRUE) $_SESSION[SESSION_KEY] = $id;
 420: } else {
 421: $res = FALSE;
 422: }
 423: return $res;
 424: }
 426: /**
 427: * ログアウトする
 428: * @param string $errmsg エラーメッセージ格納用(無ければ空文字)
 429: * @return bool TRUE:登録成功/FALSE:失敗
 430: */
 431: function logout(&$errmsg) {
 432: $errmsg = '';
 433: $res = TRUE;
 434: if (isset($_SESSION[SESSION_KEY])) {
 435: $id = $_SESSION[SESSION_KEY];
 436: if ($id != '') {
 437: $res = DBset_session($id, '', '', $errmsg);
 438: if ($res == TRUE) unset($_SESSION[SESSION_KEY]);
 439: } else {
 440: $res = FALSE;
 441: }
 442: }
 443: return $res;
 444: }
ログアウト処理を行う関数 logout は、ログインの逆で、データベースのセッションIDと最終アクセス日時をクリアし、セッション変数を削除する。
データベース処理
 133: /**
 134: * DBの初期化
 135: * @param なし
 136: * @return bool TRUE/FALSE
 137: */
 138: function DBinit() {
 139: try {
 140: $pdo = new PDO('sqlite:' . DBFILE);
 141: $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
 142:
 143: //テーブル作成:ユーザー情報
 144: //id:ユーザーID, psw:パスワード名,
 145: //premiere:登録日時, latest:更新日時
 146: $pdo->exec('CREATE TABLE IF NOT EXISTS ' . TABLE_USER . '(
 147: id TEXT PRIMARY KEY,
 148: psw TEXT,
 149: session TEXT,
 150: dt TEXT,
 151: premiere TEXT,
 152: latest TEXT
 153: )');
 154: $res = TRUE;
 155: } catch (PDOException $e) {
 156: $res = FALSE;
 157: }
 158:
 159: return $res;
 160: }
メインプログラムの冒頭で、ユーザー関数 DBinit を呼び出し、データベースファイルが存在しなければ自動的に CREATE するようにしている。
ID、パスワードを検索するSELECTや、新規登録のためのINSERT処理については、「PHPとデータベース」を参考にしてほしい。
参考サイト
- PHPセキュリティ対策:パスワードとcrypt関数:ぱふぅ家のホームページ
- 2018年のパスワードハッシュ:Qiita
- PHPセキュリティー 認証と認可:WEPICKS!
- 【PHP】ログイン_サンプルコード(Login form sample):忘れんうちに書いとけ
ID・パスワードの新規登録、ログイン、ログアウト、ログイン状態の管理といった基本的な機能を用意する。ID・パスワードはデータベースに登録するが、定石通り、パスワードはハッシュ関数によって暗号化して保存する。また、セッション変数とデータベースの両方にログイン状態を記録することで、一定時間後の自動ログアウトや多重ログイン禁止を実現する。
今回のサンプル・プログラムは、「PHPセキュリティ対策:パスワードとcrypt関数」の応用として、データベースとして SQLite を利用し、PHP バージョン5.3 以上を使い、ID・パスワードの新規登録、ログイン/ログアウトの状態遷移を制御する。
(2022年6月27日)FastCGIで正常動作しない不具合を修正
(2022年6月4日)大幅改訂,PHP8対応