永遠保持懷疑來自 client 的資料---->因為可以改
確保資料安全性---->因為程式碼可能被偷
問題修正
問題一: 偽造身分,竄改 cookie 資料
因為 cookie 可以被改,所以才會需要 session 機制,確保當下 cookie 裡的 token 跟當前登入的使用者存起來的 token 是同一個
解決方法:通行證機制,帶 token,用 token 來確認身分
cookie 的 token 是對外的,若竄改 cookie,就會發現與存起來的 token 不是同一個
自己寫 session 機制
1. 建立 token 的 table
2. 產生 token:寫個 function 產生一組隨機字串
3. token、username 寫進 database
4. setCookie 存成 token,把 token 存到 client 端
5. 驗證 token:
- 檢查 cookie 內是否存在 token,有的話去 token 的 table 查對應的 username,把 username 取出
select username from tokens where token = %s $username = $row[username]
- 用 username 去 users 的 table 查對應的資料,把資料取出
select * from users where username = %s
6. 刪除 token:刪除資料庫的 token、清空 cookie 的 token,登出後 token 就被刪除了,下次再登入後又會產生一組新的 token
補充:
- 在 function 內用 $conn 要用 global
global $conn
使用 php 內建 session 機制
1. 第一行加上 sesseion_start(),告訴 php 要使用 session
<?php
session_start();
...
?>
2. 產生 token:在登入成功的時候加上 $_SESSION['username'] = $username
,設定 session
- 產生 session id (token)
- 把 session id 跟 usename 寫入檔案(檔案會自動建立存在某個地方)
- set Cookie:session id(token)
if ($result->num_rows) {
$_SESSION['username'] = $username;
}
3. 驗證 token:用 $_SESSION['username']
取出對應資料
- 在 cookie 裡讀取 PHPSSID(token)
- 從檔案讀取 session id 對應的內容
- 把內容放到
$_SESSION
$user = $_SESSION['username'];
4. 刪除 token:加上 session_destroy() 清除 session 的內容
<?php
session_start();
session_destroy();
...
?>
問題二:明文密碼,資料庫被偷密碼就被看光了
加密 Encryption:可以解開(一對一)
雜湊 Hash:不可解開(多對一) ex. SHA256
解決方法:hash 密碼
使用 php 內建 hash
1. hash password:把 password 傳進去 password_hash(),讓 password 經過 hash 之後存進資料庫
$password = password_hash($_POST['password'], PASSWORD_DEAFULT);
2. 驗證 password:用 verify_password 驗證輸入的 password 跟資料庫經過 hash 的 password 是否對應
password_verify(輸入的密碼, 資料庫的密碼);
問題三:Cross Site Scripting(xss),在別人網站執行 JS
輸入的東西被當作程式碼而不是文字,利用這個就可以偷別人資料 ex. cookie 或可以導到釣魚網站
解決方法:跳脫字串
使用 php 內建轉換
把字串帶入 htmlspecialchars(),所有使用者可以自己調控的地方都要跳脫
註:在顯示的時候才跳脫,不要再輸入的時候跳脫,保持資料庫內輸入的原貌
function escape($str) {
return htmlspecialchars($str, ENT_QUOTES)
}
問題四:SQL Injection
注入惡意字串改變 SQL 原有的意思
ex.
// 原本的 SQL 語法
SELECT * FORM users WHERE username = '%s' and password ='%s'
//惡意字串
username:' or 1=1#(# 會被當作註解)
password: 123
// 呈現
SELECT * FORM users WHERE username = '' or 1=1#' and password ='123'
// 原本的 SQL
INSERT INTO comments(nickname, comment) VALUES(%s, %s)
// 惡意字串
nickname: aa
comment:'), ('admin', 'test
//呈現
INSERT INTO comments(nickname, comment) VALUES('aa', ''), ('admin', 'test')
解決方法: Prepared Statement
$sql = "insert into comments(nickname, content) values(?, ?)";
$stmt = $conn->prepare($sql);
$stmt->bind_param('ss', $nickname, $content);
$result = $stmt->execute();
$result = $stmt->get_result(); // 拿結果
新增功能
資料正規化
SQL JOIN
SELECT * FORM comments LEFT JOIN users ON comments.username = users.username
join 後,欄位名稱會被覆蓋,加上別名,指定要使用哪個 table 的欄位
SELECT
C.id AS id, C.content AS content,
C.created_at AS created_at,
U.nickname AS nickname
FORM comments AS C LEFT JOIN users AS U ON C.username = U.username
編輯留言
1. 新增編輯留言按鈕,href 帶 update_comment.php,帶上留言的 id
<a href="update_comment.php?id=<?php echo $row['id']>">編輯留言
2. 驗證如果留言的 username 跟使用者 username 一樣,再顯示編輯留言按鈕
<?php if ($row['username'] === $username) {
<a href="update_comment.php?id=<?php echo $row['id']>">編輯留言
}
?>
3. 新增 upadate_comment.php 的檔案,form action 寫 handle_update_comment.php
4. 用 GET 取得留言 id,用 id 找到 comment
<?php
...
$id = $_GET['id']
$stmt = $conn->prepare('select * from comments where id = ?')
$stmt->bind_param("i", $id)
...
?>
5. 把 comment 抓出來放進編輯框裡
<textarea name="content" row="5"><?php echo $row['content'] ?></textarea>
6. 加一行隱藏的 input 把 id 帶著
<input type="hidden" name="id" value="<?php $row['id'] ?>"
7. 新增 handle_update_comment.php 的檔案
8. 確認 POST content 是否有東西,沒有的話就導回去 update_comment.php 的頁面
9. 編輯留言用 update
$sql = "update comments set content=? where id=?"
10. 最後導回 index.php
刪除留言
Hard DELETE
資料會直接從資料庫裡被刪掉,無法復原
1. 新增刪除按鈕,href 帶 delete_comment.php
2. 新增 delete_comment.php 的檔案
3. 用 GET 取得留言 id,用 delete 刪除 id
$sql = "delete from comments where id =?";
Soft DELETE
不會真的把資料刪掉,避免資料不小心誤刪的時候,還有機會可以復原
1. 在資料庫新增一個欄位 is_deleted
- 欄位名稱:is_deleted
- 型態:TINYINT
- 預設值:NULL
2. 用 update 改變 id_deleted 的狀態
$sql = "update comments set is_deleted=1 where id=?"
3. 在 index.php 選取 is_deleted 是 NULL
WHERE C.is_deleted IS NULL //加在 left join 的後面
分頁功能
1. 用 limit ... offset ... 決定一頁顯示的量
limit:限制數量 offset:跳過數量
// 一次選擇五個,跳過前三個
SELECT * FROM comments ORDER BY id LIMIT 5 OFFSET 3
2. 計算出所有 comment 的量
// count 可以計算數量
SELECT count(id) FORM comments WHERE is_deleted is NULL
$count = $row['count']
3. 計算總共幾頁
$total_page = ceil($count / $items_per_page)
4. 用 GET 取得當前頁碼
// intval() 把字串轉成數字
$page = intval($_GET['page'])
5. 用<a href=""></a>
導到各分頁,如果不是首頁就出現上一頁的按鈕,不是最後一頁就出現下一頁的按鈕
<?php if ($page != 1) { ?>
<a href="index.php?page=1">首頁</a>
<a href="index.php?page=<?php echo $page - 1; ?>">上一頁</a>
<?php } ?>
<?php if ($page != 1) { ?>
<a href="index.php?page=<?php echo $page + 1; ?>">下一頁</a>
<a href="index.php?page=<?php echo $total_page; ?>">最後一頁</a>
<?php } ?>
權限管理問題:即便按鈕被隱藏起來,還是有機會可以連進去
當沒有按鈕可以導到該網址時,只要直接在網址列輸入已知的網址,就算沒有登入也可以導到該網址進行刪除或編輯別人的留言
解決方法:做權限檢查,確保執行動作的是本人
$sql = update comments set is_deleted=1 where id=? and username=?