ネットワークプログラミング 演習 第 14 回 Cookie
前回の出席確認 なぜ Web サーバ上ではチャットを作りにくいのか? 他者の発言を Web サーバに取りに行かねばならない 頻繁に更新すれば可能だが そうするとサーバに負荷がかかる Web サーバには負荷をかけてはいけなくて チャットのサーバには負荷をかけてよいのか?/ チャットのサーバには負荷がかからないのか? かからない なぜか?Web サーバとチャットサーバの違いは何か Web サーバは 情報を渡すと切断する 他者が発言していなくても更新を要求する必要がある チャットサーバは ( 会話が続く限り ) 接続したまま そもそも 負荷 って何? これが本質的な解答 他者が発言したことをサーバからクライアントに通知できる
前回の出席確認 push 型情報と pull 型情報 Web ページの閲覧は pull Web サーバは pull されることしか考えていない構成になっている チャットの場合 他者の発言は push で受け取るべき 双方向の通信が必要 リアルタイム性 チャットはリアルタイムだから負荷が大きい という回答では 説明不足 なぜ (Web サーバ上で実現しようとすると ) 負荷が大きいのか その負荷はチャットサーバなら許容されるのか といった議論をしないと説得力がない
余談 : 負荷 負荷には 必要な負荷と不要な負荷がある 例 ) ネットショッピングのサイトが 人気商品の発売当日に注文が殺到して 高負荷になる 必要な負荷 例 ) 新しい情報が無いにもかかわらず 更新要求が頻繁にやってきて 高負荷になる 不要な負荷に思える
今日のお題 Cookie
Cookie とは Web サーバからクライアント ( ブラウザ ) に情報を保存しておくように要求する この情報を Cookie という ブラウザは Cookie を保存しないことを選択できる ブラウザは Web サーバに閲覧要求を出すときに 過去に受け取った Cookie を ( あれば ) 送る ブラウザ 保存を要求 Web サーバ 閲覧時に Cookie があれば URL と一緒に送る
Cookie に設定する値 名前 値 有効期限 ブラウザは有効期限を過ぎた情報を送らないと考えてよい ブラウザを閉じるまで を指定することも可能 domain 設定するとブラウザはそのドメインのサブドメインにも情報を送る 省略すると Cookie を送ったサーバだけが対象となる path 設定するとブラウザはそのパスの子孫パスにあるページにも情報を送る 省略すると Cookie を送ったページと同じパスにあるページだけが対象となる
PHP における Cookie の扱い Cookie の受け取り ブラウザからの要求に Cookie が設定されていれば 連想配列 $_COOKIE に格納される Cookie の設定 setcookie を呼び出して設定する ただし 他に何かを表示する前に設定しなければならない
プログラム例 : おみくじ ( 再掲 ) http://sun.ac.jp/prof/yamagu/2017np/omikuji.php ソースコード : <HTML> <HEAD><TITLE>Fortune</TITLE></HEAD> <BODY> <?PHP $i = rand(1,4); if ($i == 1) { echo ' 大吉 <BR/>' ; } else if ($i == 2) { echo ' 中吉 <BR/>' ; } else if ($i == 3) { echo ' 吉 <BR/>' ; } else { echo ' 凶 <BR/>' ; }?> </BODY> 1 から 4 までの整数をランダムに生成して 変数 $i に代入 $i の値によって表示する内容を変える このサンプルは そのうちまた使います </HTML> 2017.12.20
おみくじの改造 問題点 : 再読込すると結果が変わる 今日の運勢は にしたいので 24 時間は結果を変えたくない おみくじはユーザごとに違う結果にしたい しかし サーバ側でユーザ管理をするのは面倒 Cookie を使う ユーザのブラウザごとに 結果を記録できる
Cookie を使ったおみくじのソース <?PHP define('cookie_name', 'omikuji2'); if (isset($_cookie) && (array_key_exists(cookie_name,$_cookie))) { $kuji = $_COOKIE[COOKIE_NAME]; } else { $kuji = rand(1,4); setcookie(cookie_name,$kuji,time()+60*60*24); }?> <HTML> <HEAD><TITLE>Fortune</TITLE></HEAD> <BODY> 今日の運勢は...<BR/> <?PHP if ($kuji == 1) { echo ' 大吉 <BR/>' ; } else if ($kuji == 2) { echo ' 中吉 <BR/>' ; } else if ($kuji == 3) { echo ' 吉 <BR/>' ; } else if ($kuji == 4) { echo ' 凶 <BR/>' ; }?> </BODY> </HTML> この部分を次ページで解説 後半は改造前とほぼ同じ
Cookie おみくじの解説 <?PHP define('cookie_name', 'omikuji2'); if (isset($_cookie) && (array_key_exists(cookie_name,$_cookie))) { $kuji = $_COOKIE[COOKIE_NAME]; } else { }?> $kuji = rand(1,4); 何度か使うので 定数に定義 おみくじの番号を設定 Cookie が設定されていたら Cookie から おみくじの番号を読む setcookie(cookie_name,$kuji,time()+60*60*24); おみくじの番号を Cookie に設定する 有効期限は 24 時間後
24 時間後だとテストしづらいので Cookie の有効期限を 10 秒後にしてテスト : setcookie(cookie_name,$kuji,time()+60*60*24); を setcookie(cookie_name,$kuji,time()+10); に変更 再読込しても 10 秒間は結果が変わらないことを確認してください
蛇足 :24 時間後ではなく今日中にしたい 有効期限は UNIX time で指定する 1970 年 1 月 1 日 0 時 0 分 0 秒からの ( ほぼ ) 経過秒数 GMT で 日本の時間 (JST) は 9 時間ずれている 86400(=60 60 24) の倍数が 0 時 0 分 0 秒 を setcookie(cookie_name,$kuji,time()+60*60*24); $day = 60*60*24; $t = time(); $today = ($t+60*60*9) % $day; setcookie(cookie_name,$kuji,$t-$today+$day); に変更 24 時間の秒数 何度か使うので 変数に入れておく 現在時刻を取得 今日になってからの経過秒数 明日の 0 時 0 分 0 秒
Cookie を確認しよう ブラウザに保存された Cookie を確認する : Firefox の場合 メニュー オプション プライバシーとセキュリティ Cookie を個別に削除 Chrome の場合 GoogleChrome の設定 設定 設定項目を検索 に Cookie と入力 コンテンツの設定 Cookie すべての Cookie とサイトデータを表示
余談 : ユーザの手元のデータの情報を隠す Cookie を見ると おみくじの番号がそのまま見える これを隠すには? 暗号化 半分正解 サーバだけが知っているパスワードで暗号化すると 1 QWlpWWdRa0RlMWxTMjRLSUZNdzF0QT09 2 NUpFQ1l3QUJ5RGRQNlF2R2JEdzlYUT09 3 OFV6UUlqUStWY0FoREtadk1ONGIyQT09 4 R1BvYmVzY3dsbGlLZFEzYllNVzhLdz09 単なる暗号化だと ここが毎回同じ
余談 : ユーザの手元のデータの情報を隠す ランダムな bit 列 ( 固定長 ) を平文につけて それごと暗号化する 復号したら ランダム bit 列の部分を無視する 簡単に無視できるように 固定長にしておく こういうランダム bit 列を salt と呼ぶ 同じ情報 ( 例えば大吉を表す 1) に対して暗号文が 2 salt の bit 長種類できる 暗号文から平文を推測することが難しくなる
暗号化された番号を Cookie に保存するおみくじ <?PHP define('cookie_name', 'omikuji3'); define('crypt', 'AES-256-CBC'); define('key', 'Naisho'); define('iv', '_INITIAL_VECTOR_'); define('saltlen',10); $kuji = null; if (isset($_cookie) && (array_key_exists(cookie_name,$_cookie))) { $txt = openssl_decrypt(base64_decode($_cookie[cookie_name]), CRYPT,KEY,0,IV); if ($txt!= null) { $kuji=(int)substr($txt,saltlen); } } if ($kuji == null) { $kuji = rand(1,4); $txt = openssl_random_pseudo_bytes(saltlen). $kuji; setcookie(cookie_name, base64_encode(openssl_encrypt($txt,crypt,key,0,iv)), time()+60*60*24); }?>
情報の隠し方の効果 隠していない場合 2 中吉, 4 凶 1 が大吉, 3 が吉と推測できる 暗号化 未出のおみくじを推測するのは困難 一度出たおみくじは そのときの Cookie を記録しておけば Cookie からおみくじを判別できる salt 付きの暗号化 Cookie からおみくじを判別するのは困難 一度出たおみくじは 同じ Cookie を送ることで出すことができる
サーバがユーザを管理する場合の Cookie の利用
ユーザとセッションの管理 サーバ側でユーザを管理する : ユーザ名とパスワードをサーバの DB に登録 サーバはユーザのログイン時にユーザ名とパスワードを突合する ログアウトするまで いちいちパスワードを入れずに作業をしたい : Cookie にユーザ名やユーザ ID を設定しておく Cookie がなければ ユーザにログインさせる ログアウト時には Cookie を削除する 削除 : 有効期限を過去に設定する
ユーザ管理と Cookie Cookie にパスワードを記録しているわけではない Cookie にユーザ名やユーザ ID を ( 暗号化して ) 記録しておき Cookie を持っていればパスワードの入力を省略できるようにしていることが多い Cookie が漏洩すると なりすまされる危険がある Cookie に REMOTE_ADDR や HTTP_USER_AGENT を記録しておいてチェックすれば なりすまされにくいシステムになる
複数クライアントからの同時アクセスを 検知する アクセスのたびに ( 毎回 ) 異なる値を Cookie を設定する Cookie に設定した値を ( ユーザごとに ) サーバ側にも記録しておく サーバに記録されたものと アクセス時の Cookie が異なれば 前回アクセスと今回アクセスの間に 他のクライアントからアクセスしていると分かる
次回予告 振り返りと関連する話題