Cookieをクリアする

こんにちは、Habr! 最近登場した大規模ポータルのアカウントのセキュリティに関する情報に照らして、私は自分のプロジェクトのCookie認証をわずかに修正することにしました。 まず第一に、彼は既製のソリューションのトピックに関するGoogleの好みに疑問を呈しました。 賢明なことは何もありませんでしたが、検索の使用方法がわからない可能性もあります。 その後、私は彼らがクッキーを噛む方法について書いているものを見ることにしました。 バルクが「悪いアドバイス」のカテゴリーの記事であり、5年以上前に読んだものであるとき、私の驚きは際限がありませんでした。

この記事は、状況を修正する試みです。



多くの人にとって、次のことは明らかなように思えますが、この情報が役立つ人はかなりいると思います。 「サブドメインのない単なる人間として何をすべきか」というトピックに関する調査と考察の過程で、 自転車のアプローチが発明されました。 。 これは私の中で最も人気のある言語であるため、PHPは例で使用されますが、精巧な精神組織を持つ人は、何が起こっているのかを理解するのに問題はないはずです。 それでは始めましょう。



クッキーはフィラデルフィアの最高の家、美しいブリキの箱に保管します。 つまり、認証中に、Cookieはドキュメントルート以外の単一のディレクトリに設定されます。 将来、このCookieは、セッションが確立されない場合、またはデータ(ユーザーの被害者の IPアドレス)が有効でない場合にのみ使用されます。 Cookie確認ページに動的なコンテンツを含めることはできません。



次に、アルゴリズムをさらに詳しく見てみましょう。



まず、次の構造を持つSQLテーブルが必要です。

CREATE TABLE `user_auth_cookies` ( `key` char(32) NOT NULL, `user_id` int(10) unsigned NOT NULL, `logged_in` datetime NOT NULL, PRIMARY KEY (`key`), KEY `user_id` (`user_id`), KEY `logged_in` (`logged_in`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
      
      





いくつかのプリセットと小さなユーザークラス:

 $db = new mysqli('localhost', 'test', '', 'test'); if ($db->connect_errno) die('    MySQL: ('.$db->connect_errno.') '.$db->connect_error); //     define('DOMAIN', ($_SERVER['HTTP_HOST'] !== 'localhost' ? $_SERVER['HTTP_HOST'] : false)); //   .  localhost,  false (       ) define('AUTO_AUTH_URL', '/auth/auto'); //      define('AUTH_URL', '/auth'); //     define('AUTH_COOKIE_DURATION', 20); //    ,      define('USE_HTTPS', false); //  HTTPS    (    ) class User { private $_id; public $isGuest = true; public $name = ''; public function __construct() { GLOBAL $db; if (!isset($_SESSION['user']) || $_SESSION['user']['ip'] !== $_SERVER['REMOTE_ADDR']) return; $query = 'SELECT * FROM users WHERE `id` = '.(int)$_SESSION['user']['id']; if (($res = $db->query($query)) !== false && $res->num_rows) { $user = $res->fetch_assoc(); $this->_id = $user['id']; $this->name = $user['name']; $this->isGuest = false; if (isset($_SESSION['last_request'])) { $_POST = $_SESSION['last_request']['data']; unset($_SESSION['last_request']); } } else { unset($_SESSION['user']); } } public function getId() { return $this->_id; } }
      
      





ご覧のように、2つのセッション変数が使用されます。1つ(ユーザー)は直接認証に使用され、2つ目(last_request)は承認要求が必要なURIを保存します。



承認には、4つの静的メソッドを含むAuthクラスが使用されます。

 class Auth { public static function loginRequired() { $_SESSION['last_request'] = array( 'url' => $_SERVER['REQUEST_URI'], 'data' => $_POST ); header('Location: '.AUTO_AUTH_URL); die('...'); } public static function login($login, $password, $remember = false) { GLOBAL $db; $query = "SELECT * FROM users WHERE `login` = '".$db->real_escape_string($login)."';"; if (($res = $db->query($query)) === false || !$res->num_rows) return false; $user = $res->fetch_assoc(); //   ,      if ($user['password'] !== md5($login.md5($password))) return false; if ($remember) { do { $key = md5(mcrypt_create_iv(30)); $query = "SELECT COUNT(*) AS `cnt` FROM user_auth_cookies WHERE `key` = '".$key."';"; $count = 0; if (($res = $db->query($query)) !== false && $res->num_rows) { $row = $res->fetch_assoc(); $count = (int)$row['cnt']; } else die('   .'); } while ($count > 0); $db->query("INSERT INTO user_auth_cookies VALUES ('".$key."', ".$user['id'].", NOW());"); setcookie('key', $key, strtotime('+'.AUTH_COOKIE_DURATION.' days'), AUTO_AUTH_URL, DOMAIN, USE_HTTPS, true); } $_SESSION['user'] = array( 'id' => $user['id'], 'ip' => $_SERVER['REMOTE_ADDR'], ); return true; } public static function loginByCookie() { GLOBAL $db; $location = AUTH_URL; if (isset($_COOKIE['key'])) { $query = "SELECT user_id FROM user_auth_cookies WHERE `key` = '".$db->real_escape_string($_COOKIE['key'])."';"; if (($res = $db->query($query)) !== false && $res->num_rows) { $row = $res->fetch_assoc(); $_SESSION['user'] = array( 'id' => $row['user_id'], 'ip' => $_SERVER['REMOTE_ADDR'] ); $location = '/'; if (isset($_SESSION['last_request'])) $location = $_SESSION['last_request']['url']; } } header('X-Frame-Options: DENY'); //     FRAME/IFRAME header('Location: '.$location); die('...'); } public static function logout() { if (!isset($_SESSION['user'])) return; if (mb_strlen($_SESSION['user']['key']) === 32) $db->query("DELETE FROM user_auth_cookies WHERE `key` = '".$db->real_escape_string($_SESSION['user']['key'])."';"); setcookie('key', '', 0, AUTO_AUTH_URL, DOMAIN, USE_HTTPS, true); unset($_SESSION['user']); header('Location: /'); die('...'); } }
      
      





Auth :: loginRequired()は、ユーザーがログインしていない場合に呼び出され、認証が必要なページに移動します。 このメソッドは、POSTリクエストの現在のURIとパラメーターをセッション変数に保存し(ユーザーが長い怒りの投稿を書いて、その時点でIPが変更された場合はPOSTデータを保存する必要があります)、自動Cookie認証ページにリダイレクトします。

Userクラスのコンテキストでは:

 …… $user = new User(); if ($user->isGuest) Auth::loginRequired(); ……
      
      





承認フォームが受信されると、Auth :: login($ login、$ password、$ remember = false)が呼び出されます。 $ loginパラメータには、突然受信したログイン、$ passwordが突然含まれます-パスワード、$ remember-Cookieの設定を担当するフラグ。

使用例:

 …… if (isset($_POST['login']) && Auth::login($_POST['login'], $_POST['password'], !!$_POST['remember_me'])) { $location = '/'; if (isset($_SESSION['last_request'])) $location = $_SESSION['last_request']['url']; header('Location: '.$location); die('...'); } ……
      
      





Auth :: loginByCookie()は、自動ログインページで呼び出されます。 このページでの不愉快な状況を避けるために、動的な出力があってはならず、他のディレクトリ、特にドメインから何かをロードすべきではないことを思い出させてください。 一般に、スクリプトディレクトリにRewriteRuleをインストールし、すべてのリクエストをこのスクリプトに絶対にリダイレクトするとよいでしょう。 これを言ってみましょう:

.htaccess

 <ifModule mod_rewrite.c> RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-U RewriteRule ^.*$ index.php [L,QSA] </ifModule>
      
      





Auth :: logout()は、ユーザーを「ログアウト」するために呼び出されます。 Cookieとセッション変数をクリアし、データベースからキーを不要なものとして削除し、メインキーにリダイレクトします。



最後の仕上げがあります。 古くなったキーのテーブルを定期的に(cronに従って)クリアする必要があります。

 …… $db->query("DELETE FROM user_auth_cookies WHERE `logged_in` < DATE_SUB(NOW(), INTERVAL ".AUTH_COOKIE_DURATION." DAYS);"); ……
      
      





「すべてのデバイスでログアウトする」などのボタンをサイトに追加することもできます。クリックすると、user_idによってuser_auth_cookiesテーブルからすべての認証キーが削除されます。 これは、たとえば、ユーザーが他のユーザーのコンピューターで[ログアウト]をクリックするのを忘れた場合、またはユーザーがサイトにアクセスしたモバイルデバイスを盗まれた場合に必要です。

 …… if (isset($_POST['signout_all']) { $db->query("DELETE FROM user_auth_cookies WHERE `user_id` = ".$user->getId()); Auth::logout(); } ……
      
      







アマチュアの場合、ユーザーエージェントチェックなどを追加できますが、私の意見では、これは役に立たないでしょう。Cookieを盗むことにした場合、ユーザーエージェントや他の類似の属性と一緒に行うからです。



それだけです、あなたの肝臓がいつも新鮮でサクサクしていることを願っています。



All Articles