おべんきょうメモ

Web制作の勉強メモ ほぼほぼ自分用

PHPメモ ログイン周りいろいろ

眠い

前提:ログイン判定に必要なもの

ログイン情報はセッション変数$_SESSIONに保存しますが、具体的に何を保存するかっていうと以下になります

'login_date'

最終ログイン時間をUNIXタイムスタンプ形式で保存します、概ねページ遷移する度に更新されます
格納する値は常に現在時刻のみなのでどこで格納するにしても

<?php
$_SESSION['login_date'] = time();

と記述しておけばいいです 一番わかりやすいですね
ちなみに僕はtime()data()をものすごい頻度で間違えます UNIXタイムスタンプは前者です

'login_limit'

最終ログイン日時から何秒経ったらセッション切れと見做すか、その秒数を格納します
デフォルト値は大体1~3時間くらいなんじゃないかなと思いますが、ログイン時にそれを延ばす(1週間~1ヶ月くらい?)選択肢をユーザーに選ばせることも可能です、ログイン画面によくある ✔ログイン状態を保存する みたいなアレです

<?php
// デフォルトの方 1時間
$_SESSION['login_limit'] = 60 * 60;
// 長い方 30日
$_SESSION['login_limit'] = 60 * 60 * 24 * 30;

'user_id'

DBのユーザー情報テーブルのidを格納します、厳密にはログインそのものには関わってきませんがページ表示に必要になることが多いのでログインの際に大体一緒に格納します

<?php
// SELECTで取ってきたとき ログイン時とか
$_SESSION['user_id'] = $result['id'];
// INSERTで新規登録した直後に格納するとき 詳細は後述
$_SESSION['user_id'] = $dbh -> lastInsertId();

変数のキーは例なので別に違っててもいいです
まぁただ自分は大体いつもこれ使う~っていうお決まりのパターン決めとく方が作業はしやすいやろうね

大体上の3つを新規登録やらログインのときに更新したりログイン状態を確認するときに見たりします

新規登録のときにセッションに登録すること

新規登録が完了した際、わざわざログインページヘ戻してもう一回ログインさせるのはあんまりユーザビリティ的によろしくないので 新規登録が完了した時点でセッションに必要なデータを格納してそのまま会員ページを利用できるようにするのがいいと思います

<?php
$_SESSION['login_date'] = time();
$_SESSION['login_limit'] = 60 * 60;

この2つはこれと言って特に変わったことはないのですがuser_idのみ

<?php
$_SESSION['user_id'] = $dbh -> lastInsertId();

という他では見ないやり方で取ってくることが出来ます
まぁ読んで字のごとし「最後にINSERTしたデータのidを取ってくる」ってだけなのですが idの判別はオートインクリメントがついている値、という判断のようです
最後にINSERTしたもの、というものなので対象が「データベース全体」になります、なのでfetchした後のデータとかじゃなくて$dbhから取り出していることに気をつけてください

しかしこれタイミングがめちゃくちゃシビアに重なってしまって最後にINSERTしたものが別の人のデータだったみたいなことは起こらんのやろうか

ログイン画面でやること

ログイン画面でやることの流れは大体こんな感じと思われます

  1. POST送信があるかのチェック
  2. バリデーションチェック
  3. DB接続、IDやらメールアドレス等の個人を識別するデータをキーにSELECTで検索かけて該当行を引っ張り出してくる
  4. 引っ張り出した行の中にあるパスワードとPOST送信されたパスワードが等しいかチェック
  5. セッション期限を長くするかどうかのチェック(期限の格納はここでしてもいい)
  6. セッション変数に現在時刻(と期限)とログインしたユーザーのIDを格納
  7. 会員ページ等に遷移

1とか2は他で記事にしてたりそもそも解説するまでもないことだったりなので省略
3も言うていつも通りSELECT文でクエリ実行してfetchで連想配列形式にしてやるだけなんですけども

<?php
$dbh = dbConnect();
$sql = 'SELECT * FROM users WHERE name = :name OR email = :name AND delete_flag = 0';
$data = array(
  ':name' => $name
);
$stmt = queryPost($dbh, $sql, $data);
$result = $stmt->fetch(PDO::FETCH_ASSOC);

この例は$nameの値をnameカラム及びemailカラムの両方で検索をかけ、どちらかに一致してればOKということにしたので
IDでもemailでもログインできるというちょっと便利な感じに

で、4のパスワード一致判定
パスワードはpassword_hash()で暗号化されているのでそのままでは当然ですが一致しません
ハッシュ化したパスワードとの比較にはpassword_verify()を使います

<?php
password_verify(比較する文字列, ハッシュ化済の文字列);

3の手順から引き継ぐとすれば

<?php
if (!empty($result) && password_verify($pass, $result['pass'])) {

みたいな感じでクエリ実行の結果が空ではなく且つパスワードと一致するもの、という条件にしてあげるのが良さそうです
クエリがちゃんと通ってるのかも条件に含めることで要らんエラーが減ると思う

5のセッション期限を長くするかどうかの判定、大体はチェックボックスにチェックが入ってるかの判定になるかと思うのでそれで

<?php
if (!empty($_POST['check'])) {
  debug('パスワードを保存にチェックがあります:セッション期限30日');
  $_SESSION['login_limit'] = 60 * 60 * 24 * 30;
} else {
  debug('パスワードを保存にチェックがありません:セッション期限1時間');
  $_SESSION['login_limit'] = 60 * 60;
}

if文の部分、最初はBoolean判定、if ($_POST['check'])という書き方をしていたのですが、どうもチェックボックス$_POSTの扱いはチェックが入ってる場合のみtrueを返す(チェックが入ってなくてもfalseは返さない!)ということになるみたいでチェックせずにログインしたときに画面上にこそ出てこないけどログで「$_POST['check']なんて変数定義されてないんじゃしばくぞ」とNoticeで怒られたので変数が空かどうかで判断するように変えました、怒られなくなったのでめでたし

6はtime()で現在時刻のUNIXタイムスタンプと3で取得したデータベースのデータのidをセッションに保存すればいいだけです

<?php
$_SESSION['login_date'] = time();
$_SESSION['user_id'] = $result['id'];

場合によっては5で直接セッションに保存するのではなく変数に格納だけしといて、こっちで改めてセッションに保存する場合もあるやも

<?php
<?php
if (!empty($_POST['check'])) {
  $limit = 60 * 60 * 24 * 30;
} else {
  $limit = 60 * 60;
}
$_SESSION['login_limit'] = $limit;

7は普通にheader()で遷移するだけです、省略

ログイン状態によってページを遷移させる機能

会員制サイト、会員じゃないと見れないページ(大半のページはそれですね)や逆にログイン状態では表示させる必要がないページ(新規登録ページやログインページ等)が存在するのでそれを判断する仕組みが必要です

独立したphpファイルにしてrequire()でページ頭に読み込んであげるのが一番楽そう

以下のフローチャートが大体の流れだと思いますが若干勘違いした状態で作った図なのでちょっと文章がおかしいです、でも作り直すのも面倒

f:id:mi28xider:20181203235242p:plain

ちなみにここで作りました
Flowchart Maker & Online Diagram Software

セッション変数に'login_date'の値がある

すなわち最後にサイトを訪問していた時点ではログインしていたということになります
ここが図の勘違いポイントでした、「過去にログインをしたことが」ってなんだよ
ここから更に2つのパターンに分岐します

現在時刻がセッション期限内

ここが唯一「現在進行系でログイン中」にあたるパターンです
こういうの考えるのがすごく苦手なので自分用に書いておきますが、期限内の場合は現在時刻は期限より小さくなります
ちなみに期限の求め方は'login_date'+'login_limit'です

<?php
if (time() < $_SESSION['login_date'] + $_SESSION['login_limit']) {

実際の記述はこうなります

現在進行系でログイン中の場合にやることは

  • 最終ログイン日時を更新すること
  • 非ログイン状態でのみ表示させるページを表示しようとした場合強制的にトップページ等に遷移させること

の2つです

最終ログイン日時の更新は普通に

<?php
$_SESSION['login_date'] = time();

でおわり

で、もう1つのほうがここの肝ですが、単にheader()でマイページに遷移させるだけだと何でもかんでもマイページに遷移する全く使えないゴミサイトが出来上がりますし何よりこれをマイページから読み込んだ場合楽しい無限ループが開催されます
じゃあどうすればいいかというとこういうときに使えるのが$_SERVER['PHP_SELF']basename()です

$_SERVER['PHP_SELF']

有り体に言うと「現在のURL」が格納されている変数です
要はこれを使って特定のURLからアクセスしたときだけページ遷移を発動させればいいやん、という寸法です
ですがこの変数に格納されているのはドメイン以下全てのアドレスなので直下ファイルならともかくディレクトリを挟んでる場合は予想外の挙動をしてしまったり記述がやや冗長で面倒、ということが起こります
そこで使うのがもう1つ

basename()

記述の仕方としてはbasename($address)
アドレスのディレクトリやら何やらを削いで、単純な「ファイル名のみ」に加工します
こうすることでかなり扱いやすくなりますね

これらを使って「signin.phpまたはlogin.phpにアクセスしようとしたときだけトップページに遷移する」ようにしたのが以下です

<?php
if (basename($_SERVER['PHP_SELF']) === 'signin.php' || basename($_SERVER['PHP_SELF']) === 'login.php') {
  debug('ログイン中のため、トップページに遷移します');
  header("Location:index.php");
}

basename($_SERVER['PHP_SELF']、記述としては長めなので何ならどっかしらで変数に代入してそっちを使ってもいいと思います、僕はそうしました
以下は$phpself = basename($_SERVER['PHP_SELF']);という前提でコード記述します

現在時刻がセッション期限外

やることは簡単で、セッションを破棄してログインページに戻す、これだけです
セッションを破棄するので無限ループについても考えなくてOK

<?php
debug('セッション期限切れです');
// セッションを破棄する
session_destroy();
debug('ログインページに遷移します');
header('Location:login.php');

セッション変数に'login_date'の値がない

サイトからログイン済み(或いはそもそもログインをしたことがない)ということになります
やることは「ユーザー登録の必要なページにアクセスしようとした場合にログインページヘ強制的に飛ばす」だけなのですが無限ループのこととか考えるとやや迷ってしまう感じが個人的にはします

敢えて雑にまとめるとすれば「現在のURLとログイン不要なページが等しくないかをひたすらAND(&&)でつなげていく」ということになるかと思います

<?php
debug('未ログインユーザーです');
if ($phpself !== 'login.php' && $phpself !== 'signin.php') {
 debug('ログインページに遷移します');
 header('Location:login.php');
}

あとはログインが必要か不要かに応じてディレクトリを分けてどのディレクトリに属してるかで判定するとか、phpファイルごとにそれを判定するフラグをつけてそれで振り分けるとかも考えたが試してない

ログアウト機能

これは本当にシンプルです、セッションを破棄してログインページヘ遷移するだけです ログイン判定機能の期限切れのとこでやってることと全く同じことをファイル単体でやってるだけや

<?php 
// 共通関数
require('function.php');

// 画面処理開始
debug('');
debug('==================================');
debug(' ログアウト|logout.php');
debug('==================================');

// セッション情報とか
debugLogStart();

// セッションの配列データを削除する→ログアウト
session_destroy();

// ログインページヘ戻す
debug('セッション変数の値を消去しました');
debug('ログインページヘ戻ります');
header("Location:login.php");

ファイル全体記述してこれだけだからな 本当に単純