National Yunlin University of Science and Technology
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try a Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

1

Local AI

--

Uedu Code

--

雲林科技大學 AQI 56 27°C PM2.5 16
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 16

Session 和 Cookie

HTTP 無狀態,用 Cookie 與 Session 記住使用者:登入、登出與安全設定
Interaction實作 30 minute(s)

Learning Objectives

  • 說明 HTTP 是無狀態的,伺服器要靠 Cookie 才能認出同一個瀏覽器
  • 用 setcookie 設定 Cookie(到期時間、路徑、HttpOnly、Secure、SameSite),說明 $_COOKIE 要到下一次請求才讀得到
  • 刪除 Cookie,並說明為什麼 Cookie 不能存放敏感資料或用來判斷權限
  • 用 session_start 與 $_SESSION 保存登入狀態,說明 PHPSESSID 只是編號、資料存在伺服器
  • 寫出登入、檢查登入、登出的流程,登入成功後用 session_regenerate_id 換編號
  • 說明「headers already sent」的原因,以及 session_start、setcookie、header 為什麼要在任何輸出之前

HTTP 記不住你

你登入一個網站,點到下一頁,網站還記得你是誰,這件事其實不簡單:HTTP 是無狀態的(stateless)。 每一次請求都是獨立的,伺服器上的 PHP 執行完就結束,變數全部消失;下一次請求進來,PHP 從頭執行, 它不知道這個請求和上一個是不是同一個人送的。

要讓伺服器「記得」,需要瀏覽器配合,每次請求都帶上一點識別資料。這就是 Cookie:

第 1 次請求   瀏覽器 → 伺服器   GET /index.php
     回應     伺服器 → 瀏覽器   Set-Cookie: theme=dark      (請瀏覽器記住)
第 2 次請求   瀏覽器 → 伺服器   GET /index.php
                               Cookie: theme=dark          (瀏覽器自動帶回)

PHP 把請求帶來的 Cookie 放在超全域變數 $_COOKIE。登入狀態、購物車則多半用 Session: 資料存在伺服器,Cookie 裡只放一個編號。本課兩種都會做,並用模擬的瀏覽器看 Cookie 怎麼一來一往。

設定 Cookie:這次請求還讀不到

模擬瀏覽器第一次請求 /index.php?theme=dark,瀏覽器還沒有任何 Cookie。全域框架有 $_GET,但沒有 $_COOKIE。執行完兩次 setcookie,看最後的輸出:$_COOKIE 仍然是空陣列。

下一次請求:讀取與刪除 Cookie

模擬下一次請求:瀏覽器帶著 theme 和 visited 兩個 Cookie。這次 theme 的值是 neon,因為使用者自己用瀏覽器的開發者工具改了它,所以第 18 行要檢查值是不是允許的選項。

動手操作:用 Cookie 記住偏好

這個網站只有一個 index.php,用 Cookie theme 記住你選的主題。每次點連結都是一次新的請求,下方的「瀏覽器的 Cookie」顯示瀏覽器目前保存的 Cookie,「HTTP 回應」可以看到 Set-Cookie 標頭。

想一想:Cookie

1.

瀏覽器第一次造訪,還沒有任何 Cookie。下面的程式會印出什麼?

<?php
setcookie('lang', 'en');
echo $_COOKIE['lang'] ?? '沒有', ' ', count($_COOKIE);
2.

某網站登入成功後執行 setcookie('role', 'member');,之後每一頁用 $_COOKIE['role'] === 'admin' 判斷是不是管理員。這樣做的問題是什麼?

第 1 次作答

Session:資料存在伺服器,Cookie 只放編號

Session(工作階段)把資料存在伺服器上,每份資料有一個很長的隨機編號,瀏覽器的 Cookie 只保存這個編號:

瀏覽器的 Cookie                       伺服器上的 Session 資料
PHPSESSID=a6b1eec23cf1…    ───→     a6b1eec23cf1… : ['user_id' => 1, 'username' => 'amy']
session_start();                    // 讀取 Cookie 裡的 PHPSESSID,找回這個人的資料;沒有就建立新的
$_SESSION['user_id'] = 1;           // 存
echo $_SESSION['user_id'] ?? '';    // 讀(這次和之後的請求都讀得到)
unset($_SESSION['cart']);           // 刪除其中一項
  • 每一頁要用 Session 都先呼叫 session_start(),之後才有 $_SESSION
  • 第一次 session_start() 時,PHP 產生新的編號,並在回應加上 Set-Cookie: PHPSESSID=…
  • 資料不會送到瀏覽器,使用者改不了;Cookie 名稱 PHPSESSID 可以用 session_name() 查詢
  • 執行結束時 PHP 自動把 $_SESSION 存回伺服器(預設存成檔案)
Cookie Session
資料存在 使用者的瀏覽器 伺服器
使用者看得到、改得了 可以 不行(只看得到編號)
適合 語言、主題這類偏好 登入狀態、購物車
何時讀得到 下一次請求 存進去之後立刻可以讀

登入、檢查、登出

用 Session 做登入的三個步驟:

// 登入(login.php):密碼正確才存進 Session
if ($user !== null && password_verify($_POST['password'], $user['hash'])) {
    session_regenerate_id(true);
    $_SESSION['user_id'] = $user['id'];
    header('Location: member.php');
    exit;
}

// 每一頁檢查(member.php)
session_start();
if (!isset($_SESSION['user_id'])) {
    header('Location: login.php');
    exit;
}

// 登出(logout.php)
session_start();
session_unset();                                       // 清空 $_SESSION
session_destroy();                                     // 刪除伺服器上的資料
setcookie(session_name(), '', time() - 3600, '/');     // 請瀏覽器刪除 PHPSESSID
  • 密碼不能直接存,要存 password_hash() 產生的雜湊,登入時用 password_verify() 比對(第 18 課說明)。 雜湊字串裡有 $,寫在 PHP 程式裡要用單引號,否則 $2y 會被當成變數
  • session_regenerate_id(true):登入成功時換一個新的編號,並刪除舊的。如果攻擊者事先讓你的瀏覽器用了一個他知道的編號 (session fixation,固定工作階段攻擊),你登入後他就能用同一個編號冒充你;換掉編號,他知道的那個就失效了
  • 登出時 session_destroy() 刪除伺服器上的資料,但不會清空這次請求的 $_SESSION,也不會刪除瀏覽器的 Cookie,所以三件事都要做

登入成功:把使用者存進 Session

模擬用 POST 送出帳號 amy、密碼 uedu2026,瀏覽器還沒有任何 Cookie。執行 session_start() 之後,全域框架多出 $_SESSION(一開始是空陣列);密碼驗證成功後,看右邊的 Session 陣列多了兩個鍵。程式最後轉址並結束,所以沒有輸出,回應是 302。

動手操作:登入、會員頁、登出

這個網站有四個檔案:index.php(登入表單)、login.php(驗證密碼)、member.php(會員專區,沒登入就轉回首頁)、logout.php(登出)。測試帳號 amy、密碼 uedu2026。每做一步就看看「瀏覽器的 Cookie」和「伺服器的 Session」兩個面板,請求紀錄會標出哪一次回應設定或刪除了 Cookie。

想一想:Session

1.

瀏覽器第一次造訪,還沒有任何 Cookie。下面的程式會印出什麼?

<?php
session_start();
$_SESSION['n'] = ($_SESSION['n'] ?? 0) + 1;
$_SESSION['n']++;
echo $_SESSION['n'], ' ', isset($_COOKIE['PHPSESSID']) ? '有' : '沒有';
2.

下面的程式會印出什麼?

<?php
session_start();
$_SESSION['user'] = 'amy';
session_destroy();
echo $_SESSION['user'] ?? '已清空';
3.

登入成功後呼叫 session_regenerate_id(true) 的主要目的是什麼?

第 1 次作答

headers already sent:標頭要在內容之前

HTTP 回應先送標頭(狀態碼、Set-Cookie、Location…),空一行之後才是內容(HTML)。 PHP 遇到第一個輸出時,就必須先把標頭送出去;送出之後,header()、setcookie()、session_start() 都沒辦法再加標頭了:

Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/index.php:3)

訊息裡的 output started at …:3 告訴你第一個輸出在第 3 行,從那裡找原因。常見的「看不見的輸出」:

  • <?php 前面有空白行或空格
  • 檔案存成「UTF-8 with BOM」,開頭多了三個看不見的位元組
  • 被 include 的檔案結尾 ?> 後面多了換行(所以只有 PHP 的檔案,慣例是不寫結尾的 ?>)

解決方法:把 session_start()、setcookie()、header() 移到檔案最前面,處理完邏輯再輸出 HTML。 另一個方法是 輸出緩衝(output buffering):ob_start() 之後的輸出先存在緩衝區,標頭還沒送出,最後 ob_end_flush() 才一起送出。

很多伺服器的 php.ini 預設開啟 output_buffering = 4096(官方的 php.ini-development 與 php.ini-production 都是這個值), 輸出不多時不會出現這個警告;但換到沒有開啟的環境(例如命令列,或 PHP 本身的預設值 Off)就會出錯,所以不要依賴它。 本課的模擬環境沒有開啟輸出緩衝。

先輸出再設定標頭

模擬一次 GET 請求(網頁情境的錯誤訊息是 HTML 格式)。第 3 行先輸出了 <h1>,之後的三行都會出現警告,而且沒有作用。

用 ob_start 先暫存輸出

同樣是先 echo 再 session_start(),但第 3 行先呼叫了 ob_start():輸出先存在緩衝區,標頭還沒送出,所以這次都沒有警告。

動手做:記住使用者選的語言

模擬的請求是 /index.php?lang=ja,瀏覽器帶著上次存的 Cookie lang=en。請依下面的順序決定語言 $lang 與來源 $from:

  1. 網址有 lang 而且是支援的語言:用它,並用 setcookie 記住 30 天;來源是「網址」
  2. 否則,Cookie 有 lang 而且是支援的語言:用它;來源是「Cookie」
  3. 都沒有:用 zh;來源是「預設」

注意:剛用 setcookie 存的值,這次請求的 $_COOKIE 讀不到,要直接用變數。

你的輸出
預期輸出

重點回顧

  • HTTP 是無狀態的:每次請求都是獨立的,伺服器靠瀏覽器帶回的 Cookie 認出同一個人
  • setcookie(名稱, 值, [選項]) 在回應加上 Set-Cookie;常用選項 expires、path、secure、httponly、samesite
  • $_COOKIE 是這次請求帶來的 Cookie:剛設定的值要到下一次請求才讀得到
  • 刪除 Cookie:值給空字串、到期時間設在過去
  • Cookie 存在使用者的瀏覽器,看得到也改得了:不放密碼、權限等敏感資料,讀取時要驗證
  • Session 的資料存在伺服器,瀏覽器只有 PHPSESSID 編號;每頁先 session_start() 再用 $_SESSION
  • 登入成功後 session_regenerate_id(true);登出要 session_unset()、session_destroy(),並刪除 PHPSESSID 這個 Cookie
  • session_start()、setcookie()、header() 要在任何輸出之前;出現 headers already sent 時,看訊息指出的第一個輸出位置,或用 ob_start()
這一課完成了!
Next lesson:資料庫連接
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。