確認需求
- 會員註冊、登入、登出功能
- 會員個人資料修改功能
- 留言新增、編輯、刪除功能
- 留言顯示有分頁功能
- 管理員權限功能,以及只有管理員進得去的後台管理頁面,可以看到所有使用者,而管理員可以調整使用者的身份,也可以編輯各項身份的權限(註1)
- 資訊安全防護功能 (註2)
註1:
- 彈性的後台權限設計,可以任意組合「新增文章」、「刪除自己的文章」、「刪除任意文章」、「編輯自己的文章」、「編輯任意文章」這些權限(舉例來說,管理員要可以更改遭停權使用者這個身份的權限,可以變成只能編輯文章不能刪除等等。)
註2:
- 解決明碼問題、SQL Injection 攻擊防護以及 XSS 攻擊防護
資料庫規劃
紀錄所需要的 table 及內容
user :
- id :會員 id
- nickname : 會員暱稱
- username : 會員帳號 (獨一)
- password : 會員密碼
- role : 會員權限 (關聯 roles.id )
- created_at : 註冊時間
- updated_at : 最後更新時間
comments :
- id : 留言 id
- user_id : (關聯 user.id)
- content : 留言內容
- is_deleted : 是否為刪除狀態
- created_at : 新增時間
- updated_at : 最後更新時間
- deleted_at : 刪除時間
roles :
- id : 權限 id
- name : (獨一) 權限名稱
- read : 讀取文章
- create : 新增文章
- update_oneself : 編輯自己的文章
- update_all : 編輯全部的文章
- delete_oneself : 刪除自己的文章
- delete_all : 刪除全部的文章
- isAdmin:是否可進入後台
檔案路由規劃
index.php (首頁)
- 註冊按鈕 -> 導到 register.php (註冊頁面)
- 登入按鈕 -> 導到 login.php (登入頁面)
- 登出按鈕 -> 執行 handle_logout.php 登出程式 -> 導回 index.php
- 編輯會員資料按鈕 -> 導到 update_user.php (編輯會員資料頁面)
- 送出留言按鈕 -> 執行 handle_add_comment.php 新增留言程式 -> 導回 index.php
- 編輯留言按鈕 -> 導到 update_comment.php (編輯留言頁面)
- 刪除留言按鈕 -> 執行 handle_delete_comment.php 刪除留言程式 -> 導回 index.php
- 後台管理按鈕 -> 導到 backstage.php (後台管理頁面)
register.php (註冊頁面)
- 送出按鈕 -> 執行 handle_register.php 註冊程式 -> 導回 index.php
login.php (登入頁面)
- 送出按鈕 -> 執行 handle_login.php 登入程式 -> 導回 index.php
update_user.php (編輯會員資料頁面)
- 送出按鈕 -> 執行 handle_update_user.php 更改會員資料程式 -> 導回 index.php
update_comment.php (編輯留言頁面)
- 送出按鈕 -> 執行 handle_update_comment.php 編輯留言程式 -> 導回 index.php
backstage.php (後台管理頁面)
- 權限設定按鈕 -> 導到 role_manage.php (權限設定頁面)
- 送出按鈕 -> 執行 handle_user_role.php 權限變更程式 -> 導回 index.php
role_manage.php (權限設定頁面)
- 送出按鈕 -> 執行 handle_role_manage.php 權限設定程式 -> 導回 backstage.php
頁面細節規劃
把 index.php 頁面中需處理資料的部分獨立成各別檔案做處理
navbar.php (功能列)
- 判斷登入狀態
- 判斷登入者權限
form.php (表單列)
- 判斷登入狀態
- 判斷登入者權限
comment.php (留言列)
- 判斷登入狀態
- 判斷登入者權限
page.php (分頁列)
- 處理分頁資料
功能細部規劃
把相關功能分類並區分成不同的檔案
DB_conn.php
- 資料庫連線功能
- 發送 SQL 指令功能
- response 除錯功能
utils_general.php
- 取得 user 資料功能
- 權限驗證功能
utils_post.php
- 留言新增功能
- 留言編輯功能
- 留言刪除功能
utils_user.php
- 會員註冊功能
- 會員登入功能
- 會員登出功能
- 會員編輯資料功能
utils_roles.php
- 新增權限功能
- 編輯權限功能
- 設定使用者權限功能
開發流程規劃
- index.php 切版
- 「建立」顯示留言功能
- 「建立」新增留言功能
- login.php 切版
- 「建立」會員登入功能
- 「建立」會員登出功能
- register.php 註冊頁面切版
- 「建立」帳號註冊功能
- 設定 SESSION 判斷登入狀態
- 「修改」新增留言功能 - 非登入狀態不可使用
- update_user.php 切版
- 「建立」編輯會員資料功能
- update_comment.php 切版
- 「建立」編輯留言功能
- 「建立」刪除留言功能
- 「建立」留言顯示分頁功能
- backstage.php 切版
- 「建立」取得所有使用者資料功能
- 「建立」設定使用者權限功能
- role_manage.php 切版
- 「建立」新增權限功能
- 「建立」編輯權限功能
- 「修改」新增留言功能 - 加入權限驗證
- 「修改」編輯留言功能 - 加入權限驗證
- 「修改」刪除留言功能 - 加入權限驗證
權限驗證 - 規劃
驗證流程:
- 先確認使用者是否有進入此頁面的權限,沒有就導回
- 確認使用者是否有操作此行為的權限,沒有就導回
- 確認此篇留言是否屬於此使用者(若目前使用者有權限管理所有留言,則不檢驗此項目)
- 三項驗證皆符合,方可執行後續程式
執行方法:
- 所需獲取的資料:
- 目前的使用者帳號
- 目前頁面所要操作的行為
- 目前的文章編號
- 資料處理流程:
- 利用帳號,取得使用者的權限編號
- 利用此權限編號,取得所有行為的權限
- 利用文章編號,獲取文章的作者的 user_id
- 開始進行驗證:
- 若此頁為後台頁面,先判斷是否為管理員身份,不是就導回 (若此頁面非後台頁面,則跳過此步驟)
- 判斷是否具備此行為之權限
- 判斷此篇留言是否屬於此使用者(若目前使用者有權限管理所有留言,則不檢驗此項目)
權限驗證 - 使用方式:
- 先 new 一個物件準備做權限驗證
$cp = new CheckPermission($username, 'create', '9');
- 若是後台頁面要確認使用者是否為管理員,可以用 isAdmin() 驗證
if (!$cp->isAdmin()) { exit; }
- 若是跟文章相關相關,則直接用 hasPermission() 驗證
if (!$cp->hasPermission) { exit; }
實際程式碼 - 權限驗證
<?php
class CheckPermission {
public function __construct($username, $action, $comment_id) {
$this->username = $username;
$this->action = $action;
$this->comment_id = $comment_id;
$this->init();
}
private function init() {
// 先把需要的資料都先準備好
$this->userData = getUser($this->username);
$this->roleData = getRole($this->userData['role']);
$this->commentData = getComment($this->comment_id);
}
public function isAdmin() {
// 驗證是否有權限進入後台頁面,若非管理員身份,回傳 false
return !empty($this->roleData['is_admin']);
}
public function hasPermission() {
// 按照順序做權限驗證,全數通過才會回傳 true
if ( !$this->checkAction()) return false ;
if ( !$this->checkBelonging()) return false ;
return true;
}
private function checkAction() {
// 判斷使用者是否具備此行為的權限
if ($this->action === 'create') {
return !empty($this->roleData['create_comment']);
}
if ($this->action === 'update') {
return !empty($this->roleData['update_oneself']) || !empty($this->roleData['update_all']);
}
if ($this->action === 'delete') {
return !empty($this->roleData['delete_oneself']) || !empty($this->roleData['delete_all']);
}
}
private function checkBelonging() {
// 若是 comment_id 沒有值或者 action 為新增文章,則跳過此步驟
// 判斷使用者是否有權限管理別人的文章,有權限則回傳 true
// 判斷此使用者是否為文章所有人
if (empty($this->comment_id) || $this->action === 'create') return true ;
if ($this->action === 'update' && !empty($this->roleData['update_all'])) return true ;
if ($this->action === 'delete' && !empty($this->roleData['delete_all'])) return true ;
return ($this->userData['id'] === $this->commentData['user_id']);
}
}
$cp = new CheckPermission($username, 'create', '9') ;
?>
實際程式碼 - 資料庫連線 DB_conn.php
<?php
require_once('DB_config.php');
class Db {
public function __construct($servername, $username, $password, $db_name) {
$this->server = $servername;
$this->user = $username;
$this->pass = $password;
$this->db = $db_name;
$this->init();
}
private function init() {
$this->conn = mysqli_connect($this->server, $this->user, $this->pass, $this->db);
if ($this->conn->connect_error) {
die('資料庫連線錯誤:' . $this->conn->connect_error);
}
mysqli_query($this->conn, "SET NAMES utf8");
mysqli_query($this->conn, 'SET time_zone = "+8:00"');
}
public function sqlQuery($str, $type = 's', ...$List) {
$this->stmt = mysqli_prepare($this->conn, $str);
mysqli_stmt_bind_param($this->stmt, $type, ...$List);
$this->stmt_result = mysqli_stmt_execute($this->stmt);
$this->checkResult();
$this->result = $this->stmt->get_result();
}
private function checkResult() {
if (!$this->stmt_result) {
echo 'Failed' . $this->conn->error;
exit;
}
}
public function getResult() {
return $this->result ? $this->result->fetch_assoc() : '';
}
}
$db = new Db($servername, $username, $password, $db_name);
?>