發現問題 => 嘗試改一些東西 => 如何進行修正
大原則:永遠不要相信 client 的資料
- client 端資料都是可以改的,永遠抱持懷疑心態
- 假設程式碼被偷,如何維持資料安全性 ?
發現問題:偽造身份
cookie 是可以被改的,所以我們可以藉由竄改 cookie 的 value 偽造他人身分。
修正問題:通行證機制簡介與實作
本來在 cookie 裡面的東西都可以隨便改,我們沒辦法讓他不能被改。但,我們可以利用通行證的機制,發給他一張很難被偽造的通行證。
可以把要給他的東西換成 token,token 裡的值隨機產生,並將他傳給瀏覽器,因此瀏覽器下次再帶就會帶這個上來。也就是將原本的對應關係存到 server去,在 server 刻意給他這個 token,誰拿到這個 token 就可以代表這個身分。
為了要完成這個機制,在 server 端需要一個表格,去存我發的 token、這個 token 對應的 username。當下一次使用者帶了這個 token,我就去資料庫查表,查這個 username。
因為這個 token 是亂數隨機產生,通行者要破解他人的 token 必須猜非常多次,組合可能有好幾兆,因此形同給了一張很難被偽造的通行證。
第一步、建立 DB
建立資料表tokens,3 個欄位:id, token, username
第二步、改登錄機制 handle_login.php
本來存 username,現在我要進行修改。
利用 rand() 隨機產生數字、char()可以將數字轉成字母
// 實作一組隨機 16 碼的 token
function generateToken() {
$s = '';
for($i=1; $i<=16; $i++) {
$s .= chr(rand(65,90));
}
return $s;
}
echo generateToken();
對於比較常用/其他地方也會用到的 funct,可以獨立成一個 php 檔,再將它引入。require_once('utils.php');
給 token,將對應關係存到資料庫
$token = generateToken();
$sql = sprintf("INSERT INTO tokens(token, username) Values('%s', '%s')",
$token, $username
);
$result = $conn -> query($sql);
if (!$result) {
die($conn -> error);
}
在首頁,調整原本從 cookie 直接拿 username 的機制。變成先檢查 cookie 內有沒有 token,如果有將它拿出來,然後去資料庫查這個 token 對應到的 username。
$username = NULL;
if (!empty($_COOKIE['token'])) {
$token = $_COOKIE['token'];
$sql = sprintf(
"select username from tokens where token = '%s'",
$token
);
$result = $conn -> query($sql);
$row = $result -> fetch_assoc();
$username = $row['username'];
}
修改登出的程式碼,原本是將 username 清空。現在除了要將 token 清空,還要將資料庫裡的資料刪掉。
<?php
// 引入資料庫
require_once('conn.php');
// 刪除 token
$token = $_COOKIE['token'];
$sql = sprintf('DELETE FROM tokens WHERE token="%s"', $token);
$conn -> query($sql);
$expire = time() - 3600;
setcookie("token", '', $expire);
header('Location: index.php');
?>
調整 index.php
<?php
require_once('conn.php');
$username = NULL;
if (!empty($_COOKIE['token'])) {
$token = $_COOKIE['token'];
$sql = sprintf(
"select username from tokens where token = '%s'",
$token
);
$result = $conn -> query($sql);
$row = $result -> fetch_assoc();
$username = $row['username'];
}
$result = $conn->query("SELECT * FROM comments ORDER BY id DESC");
if (!$result) {
die('錯誤訊息' . $conn->error);
}
?>
調整新增留言的部分,原本從 cookie 抓 username 出來查,再去串他的 nickname。現在,要從 token 串 username,再從 username 串 nickname。這個流程很多地方都用的到,可以將他包成一個 funct。
要在 funct 中用 conn,要特別宣告為 global
// utils.php
function getUserFromToken($token) {
global $conn;
$sql = sprintf(
"select username from tokens where token = '%s'",
$token
);
$result = $conn -> query($sql);
$row = $result -> fetch_assoc();
$username = $row['username'];
$sql = sprintf(
"SELECT * FROM users WHERE username = '%s'",
$username
);
$result = $conn -> query($sql);
$row = $result -> fetch_assoc();
return $row; // username, id, nickname
}
現在,index.php 可以利用這個 funct 進行變身
// index.php
<?php
require_once('conn.php');
require_once('utils.php');
$username = NULL;
if (!empty($_COOKIE['token'])) {
$user = getuserFromToken($_COOKIE['token']);
$username = $user['username'];
}
$result = $conn->query("SELECT * FROM comments ORDER BY id DESC");
if (!$result) {
die('錯誤訊息' . $conn->error);
}
?>
修改新增留言的部分,用 require_once 引入
// handle_add_comment.php
$user = getUserFromToken($_COOKIE['token']);
$nickname = $user['nickname'];
小結
用通行證的方式,在 server 中存對應關係。username 只會在 db 看到,token 對外。以上機制的專有名詞:session 機制。php 也有自己的 session 機制。
(上面的還不太熟)
PHP 內建 session 機制
要用內建的 session 都要在第一行加上 session_start()
//handle_login.php
if ($result -> num_rows) {
$_SESSION['username'] = $username;
/*
1. 產生 session id(token)
2. 把 username 寫入檔案
3. set-cookie: session-id
*/