CakePHP5入門【CakePHP5認証編③】ログインステータス

A子
(つまり、「users」テーブルに項目を追加するよ)

C菜

A子
(名前を考えるのが面倒くさい)

B美
項目名は「session」で…

A子
セッション?
そこには何を格納するのさ?

C菜
「セッション」の仕組みって、どうなってるんですか~?

B美
1.ブラウザがWebサーバにアクセス
2.Webサーバは重複しない「セッションID」を作成し、ブラウザへ送信 3.ブラウザはその「セッションID」をCookieとして保存 4.同じWebサーバの別ページへアクセスする際、そのCookieをWebサーバへ自動送信 5.Webサーバは「セッションID」を照合して、さっきと同じユーザであることを認識 6.無操作の状態が一定時間続くと、Webサーバは「セッションID」を無効化する(これが「セッションタイムアウト」) 7.操作している限り、「セッションID」は無効化されない |

A子
よく聞く言葉だけど、あまり分かってないんだよね(苦笑)

C菜
あるWebサーバから受け取ったCookieは、再び同じWebサーバにアクセスするときに自動送信されるって仕組みのはずです~

A子
それによって、Webサーバは「アクセス元が同じユーザかどうか」を見極めることができるってわけか…

B美
一つ付け加えておくなら「Sessionはサーバ側に、Cookieはクライアント側に保存される」…ってことね
んで、Webサーバはファイル名として「セッションID」の付いたものを保存してるんだけど、タイムアウトしたらそのファイルは削除されるの

C菜
でしたら、そのファイルがあるか否かを「file_exists」関数で調べることで、「セッションタイムアウト」になっているかを判別することができますね~

B美
だから、データベーステーブル内に現在の「セッションID」を格納しておきたいのよ

A子

B美
「session_id」という関数を呼び出すだけよ
(これは普通にPHPの関数です)
まぁ、CakePHPで推奨される書き方としては、下記のようになるけどね
$this->request->getSession()->id() |
ただ
session_id() |
という、単なる関数呼出しを使っても全く問題ないわよ
(得られる結果は同じだし…)

A子
それじゃ、さっそくやっていこう
まずはデータベーステーブルを変更するよ
あ、でも「テーブル定義を一度削除してから、あらためて新規作成」するのではなく、「テーブル定義を修正」する形でやりたいんだよね
(すでにユーザ登録してるし…)

B美
ググれば簡単に調べられるから…

A子
・・・
うん、こんな感じかな
ALTER TABLE users
ADD status int AFTER password, ADD session varchar(255) DEFAULT NULL AFTER status; |
これで「password」と「created」の間に「status」と「session」が挿入されるはず…
(あ、三行で書いてるけど、これって一文だからね)

C菜


A子

C菜
あ、でも一応キャッシュクリアだけは、やっておいたほうが良いかもです~

A子
cd html/authapp[Enter]
bin/cake cache clear_all[Enter] |
まぁ、あくまでも念のため…だけどね


C菜
「login」と「logout」メソッドのことですけど~

A子
$user = $this->Authentication->getIdentity();
$loginUser = $this->Users->get($user->id); $loginUser->status = 1; //ログイン $loginUser->session = session_id(); $this->Users->save($loginUser); |
次に「logout」メソッドの追加分はこうだね
$user = $this->Authentication->getIdentity();
$loginUser = $this->Users->get($user->id); $loginUser->status = 0; //ログアウト $loginUser->session = null; $this->Users->save($loginUser); |
ログインの有無を表す「status」の値は、ログイン状態が「1」で、ログアウト状態が「0」(または「null」)ってことにしよう



B美
良い線いってるんだけどねぇ

C菜

B美
問題は「login」メソッドのほうね
何が問題か…って、そのタイミングでは「セッションID」をきちんと取得できないのよ
(要するに、topページへリダイレクトしたあと「セッションID」を取得するように!…ってこと)

A子
でも、そこまで分かれば何とかなるかな
「src/Controller」の中にある「TopController.php」を書き換えるよ
まずは「index」メソッドね
(そのユーザの「session」項目の値が「null」だったら、セッションIDを取得してデータベーステーブルを更新します)
public function index()
{ $user = $this->Authentication->getIdentity(); $loginUser = $this->Users->get($user->id); if (is_null($loginUser->session)) { // データベース上でログイン状態にする $loginUser->status = 1; //ログイン $loginUser->session = session_id(); $this->Users->save($loginUser); } } |
もちろん、「users」テーブルを使う準備も追加するよ
private $Users;
public function initialize(): void { parent::initialize(); $this->Users = $this->fetchTable('Users'); } |
で、どうよ


A子


B美
「session」の値をnullにすることだけは「login」メソッド内でやっておきなさい

C菜

B美

A子
ログイン状態のまま、topページが表示されるんじゃないの?

B美

C菜

B美
そして「ブラウザ再起動を伴う再ログインを行った場合、セッションIDは再取得(つまり、さっきのとは異なる値)になる」のよ

A子
さっきのコードだと、データベーステーブルの「session」項目の値が更新されないことになるね
(だって「session」項目の値がnullじゃないし…)
…ってことは、最初のコードから「status = 1;」の行を削除して、「session」には「null」を代入すれば良いのかぁ


B美
まぁ、良いんじゃないかしら
とりあえずテストしてみなさいな

C菜


A子
お、「users」テーブルの中身はこうなったよ
もっとも、セッションIDの値が正しいのかどうかは知らんけど(笑)


C菜

B美
rootにsuしてから確かめてみなさい

A子
su -[Enter]
cd /var/lib/php/sessions[Enter] ls -l[Enter] |
で、どう?


C菜

B美
で、このセッションファイルは、「ログアウト」または「セッションタイムアウト」で自動的に削除されるってわけ

A子
まずは「ログアウト」して…っと
データベース内とセッションファイルを見てみると、こうなったよ



C菜

B美
ファイルサイズ(37バイト)でも分かる通り、意味のないファイルだから…

A子

B美
タイムアウトの場合、セッションファイルがすぐに削除されるわけではないの
一定時間ごとに「gc(ガーベージコレクション)」が働くんだけど、そのタイミングで削除されることになるのよ

A子
また意味不明なことを…(苦笑)

C菜

B美
余談だけど、カタカナ表記する場合、「ガーベージ」「ガベージ」「ガーベジ」の三通りを見かけるわ
(ネット上や書籍で…)
正しい英語の発音的には「ガーベージ」だけどね

A子
「ゴミ収集」ってことは、不要なファイルを削除する仕組みってこと?

B美
メモリ内に動的に(プログラム実行中に)生み出された確保領域って、使い終わったら消していかないとメモリを圧迫することになるの
言い換えれば、消し忘れがあるとメモリの空き容量が減っていくのよ
(これを「メモリリーク」と呼びます)
ガーベージコレクションというのは、それ(不要なオブジェクトの消去)を自動的に行ってくれる仕組みってわけ
(ほとんどのオブジェクト指向言語には備わっている仕組みよ…いや、この仕組みを持たないプログラム言語もあるんだけど(苦笑))

A子
何分おきとか決まってるの?

B美
気にしたことは無いし、気にする必要もないわ
(いつの間にか勝手に動いてる…って認識でOK)
なぜなら、OS(Debian)側でもcronによって無効なセッションファイルの削除を自動実行してるから(30分ごとに)
(gcよりはcronジョブで削除されることのほうが多いんじゃないかしら?)

A子
だったら安心だね

B美
Commandクラスを作って、それをcronで定期的に(例えば、1時間に1回)実行することで、「status」が「1(ログイン)」だったレコードを「0(ログアウト)」に戻すようにね

C菜
もしもファイルが存在するのならば何もしないで~
ファイルが無かったら(「セッションタイムアウト」なので)「ログアウト」処理を行えば良いんじゃないでしょうか~?

A子
あっ、そっか
それでうまくいきそうだね(多分)

C菜
cd html/authapp[Enter]
bin/cake bake command cleanup[Enter] |
名前は「cleanup」にしましたけど、良いですよね~?


A子
以前コマンドクラスを作ったときのコードをコピペして修正するよ
(【CakePHP5応用編⑫】を参照)
private $Users;
public function initialize(): void { parent::initialize(); $this->Users = $this->fetchTable('Users'); } |
これで「users」テーブルを使う準備はOKね


B美
(ちょっと難しいからね)
public function execute(Arguments $args, ConsoleIo $io)
{ //処理がタイムアウトしないように set_time_limit(0); //ログイン中のユーザ一覧を取得 $users = $this->Users->find()->where(['status' => 1]); //セッションファイルのディレクトリ $sessionPath = ini_get('session.save_path'); foreach ($users as $user) { //セッションファイルの有無を調査 $sessionFile = $sessionPath.'/sess_'.$user->session; $sessionFileExists = file_exists($sessionFile); //セッションファイルが存在しなければログアウト状態に変更 if (!$sessionFileExists) { $this->Users->updateAll( ['status' => 0, 'session' => null], ['id' => $user->id] ); } } return null; } |


A子
それって何なのさ?

B美
てか、それこそが経験値の獲得につながるんだから

A子
(ググってみる)
「set_time_limit」は引数にゼロを渡すことで処理がタイムアウトしないようにするってやつだね
(言い換えれば、時間のかかる処理ってタイムアウトすることがあるみたい…)
「ini_get」は「php.ini」の設定値を取得する関数みたい
(「ini_get('session.save_path')」では「/var/lib/php/sessions」という文字列が得られる…ってことだね)
最後の「updateAll」はレコード更新を行うCakePHPのメソッドで、最初の引数が更新内容を記述した連想配列、次の引数が条件を記述した連想配列ってこと
(つまり、さっきのコードだと、このユーザの「status」と「session」を更新する…って意味))

B美
まぁ、「set_time_limit(0)」は多分いらないんだけどね(苦笑)
(念のため…って感じ)

C菜
まずはログインしてから30分くらい放置しますね~
(ちなみに、B美部長のアカウントです~)



A子
bin/cake cleanup[Enter] |
うわっ、エラーだ!
B美の書いたコードなのに何でよ?

B美
キャッシュファイルのパーミッションよ

C菜
cd[Enter]
cd html/authapp/tmp/cache/models[Enter] su[Enter](一時的にrootになって) chmod 666 myapp_cake_model_default_users[Enter] exit[Enter](一般ユーザに戻る) |
のあと、もう一度
bin/cake cleanup[Enter] |
を実行です~

A子
んじゃ、次はセッションファイルが削除されるのを待とう
(待ち時間がムダに思えるけど、自動的にセッションファイルが削除されることの確認も含めてね)
うーん、なかなか削除されない…(苦笑)
(結局、約50分ほどで削除されたよ)


C菜
bin/cake cleanup[Enter] |
すごいです~
ばっちり「users」テーブルが更新されましたよ~

↓


B美
…ってのをやってみなさい

A子
こんな感じでどうかな?
テストでは、私(ako@friction-river.jp)とC菜(cina@friction-river.jp)の二人がログイン状態ってことにしてみたよ

↓


C菜


B美
じゃあ、最後にcron設定まで終わらせておきましょうか
ちゃんと覚えてるわよね?

A子

C菜
crontab -e[Enter] |
のあと
0 * * * * /home/bimi/html/authapp/bin/cake cleanup |
ですよ~
(【コラム⑨】を参照)

A子
(要するに、1時間おきに実行)

B美
0 * * * * cd /home/bimi/html/authapp && bin/cake cleanup |
まぁ、どっちでも構わないんだけど…

C菜


B美
そういうところ、面倒だからって手を抜かないように!

A子