この記事では主に、PHP セッションのロックと同時実行に関する現象について説明します。興味のある方は参考にしていただければ幸いです。
ログインできません
ある日、バグを解決するためにバックエンド システムの 1 つにログインしようとしましたが、アカウントのパスワード確認コードを正確に入力したところ、ログインできませんでした。多くの実験を行った結果、主なエラー メッセージが 2 つあることがわかりました:
csrf 検証に失敗しました
検証コード エラー [コードの神に誓って、私が使用した検証コードを入力したことを確認します。半角、順序は一貫していました、いいえ さらに文字を追加]
私たちのシステム私たちのシステムは、ご覧のとおり、Palcon 2.0.8に基づいて開発されています。フォームフィールドでの CSRF 攻撃。キャプチャも有効になっています。
<input type="hidden" name="{{ security.getTokenKey() }}" value="{{ security.getToken() }}"/> <img src="/login/getCaptcha" id="img-captcha"/>
最初にこれら 2 つのコンポーネントをチェックしたところ、どちらもセッションにデータを保存していることがわかりました。
# phalcon/security.zep # Security::getToken() let session = <SessionInterface> dependencyInjector->getShared("session"); session->set(this->_tokenValueSessionID, token); $this->session->set('admin_get_captcha_action', $captcha);
次に、セッションの実装をチェックしたところ、データが Redis に保存されていることがわかりました。
探して検索 ログインできない問題は何ですか?データの検証に問題があるため、テスト環境の redis マシンにログインし、redis-cli モニターを実行して、ログイン プロセスを実行したところ、出力は次のようになりました。 ):
GET sessionId
GET sessionId
SETEX sessionId 3600 csrf=xxxx
SETEX セッション ID 3600 captcha=abcd
次のことがわかります:
1. ここには 2 つのリクエストがあり、1 つはフォームをロードすること、もう 1 つは検証コードを生成することです。
2. 「同時実行」状況が発生します。これらの 2 つのリクエストは、フォームがロードされて表示された後に検証コードを要求する必要があります。つまり、セッション シーケンスは get->set->get->set になります。同時リクエストが行われたように見えますか?
3. 後者の SETEX には csrf コンテンツがありません。つまり、以前のデータが上書きされます。しかし、問題が何であるかは少しわかりました。何が問題なのでしょうか? PHP のセッション データ アクセスから始まる長い話です。
PHP セッション データ アクセス セッション データは文字列にエンコードされ、メモリ [ファイル、データベース、redis、memcache など] に保存されます。セッションを使用するとき、いつメモリにアクセスしますか?データを取得していますか?データはいつメモリに書き込まれますか?
この質問に対する答えは、一部の友人の考えとは異なる場合があります。リクエストでは、PHP は session_start 中に 1 回だけメモリを読み取り、その後、リクエストの終了時に 1 回だけメモリに書き込みます。 session_write_close を呼び出す 時間が来たら、データをメモリにフラッシュして戻し、セッションを閉じます。 そして次の質問が来ます:1、如果一个会话,同时出现两个读写session请求,没有保证获取1-写入1-获取2-写入2,同时没有cas版本管理机制的情况下,这些并发请求就会彼此读取不到对方的写入,最后写入的会把前面请求写入的session覆盖掉。
2、如果请求是串行的,像登录页面的表单和验证码,也有可能前面的请求已经输出内容了,但是session还没写入,后面的请求就已经发起了。
锁与不锁
解决这种资源的并发一般会通过锁或版本管理来处理。但是版本管理我看不到好的方法。就聊聊锁吧。
其实锁是不大适合,有弊端的。
php的session,默认是用文件存储的,在打开session的时候,会对文件加独占锁,这样,其它请求就无法获取锁了,只能等待直到前面的锁解了。
这样保证了 读取-写入,读取-写入的顺序。
其它存储器,例如mysql,可以借助select for update进行行锁。redis可以通过一个自增键,返回1的获取到锁等来实现。
这个实现的话,对数据流来说很理想,但是,对于目前这种页面大量应用ajax的情况,所有请求排队处理,将大大加大页面展现的耗时,甚至出现请求超时等不可用故障。
没有解决的解决不建议过多使用session,其一次读取一次写入的机制所引发的问题,会造成坑的存在。
在模版渲染前,或请求输出前调用session_write_close
# 立刻回写session,避免session覆盖 $eventManager = $this->view->getEventsManager(); if (!$eventManager) { $eventManager = new Manager(); $this->view->setEventsManager($eventManager); } $eventManager->attach("view:afterRender",function(){ session_write_close(); }); return $this->view; if($login) { # 立刻回写session,避免session读取不到 $eventManager = $this->dispatcher->getEventsManager(); if (!$eventManager) { $eventManager = new Manager(); $this->dispatcher->setEventsManager($eventManager); } $eventManager->attach('dispatch:afterDispatchLoop',function(){ session_write_close(); }); return $this->response->setHeader('Location', '/'); }
相关推荐:
以上がPHP のセッション ロック、同時実行性、カバレッジの詳細な説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。