2018年11月12日 星期一

《面試官別再問》PHP Laravel 如何使用JWT api authentication

 使用 JWT 實作 Laravel API 驗證與刷新機制

在現代 Web 開發中,API 驗證扮演著核心角色。當我們需要一個無狀態、可擴展且能與多種前端(如 SPA、行動應用)協作的驗證方案時,JWT(JSON Web Tokens)是個極佳的選擇。本文將深入探討如何在 Laravel 專案中,透過第三方套件實作一套完整且安全的 JWT API 驗證機制,並詳細說明如何處理 Token 刷新,以兼顧便利性與安全性。


為什麼選擇 JWT 進行 API 驗證?

JWT 的核心優勢在於其無狀態性(Stateless)。與傳統基於 Session 的驗證方式不同,JWT 讓伺服器無需儲存任何使用者狀態。所有必要的資訊(如使用者 ID、過期時間)都打包在 Token 內部,並透過伺服器獨有的密鑰進行簽名。每次 API 請求時,客戶端只需在 Header 中攜帶這個 Token,伺服器便能獨立驗證其有效性,大幅簡化了擴展性和伺服器資源管理。

這使得 JWT 特別適用於以下場景:

  • 分散式系統與微服務架構:多個服務可以共享同一個密鑰來驗證 Token,無需共用 Session 儲存。

  • 單頁應用程式(SPA)與行動應用:這些前端應用不需要依賴瀏覽器的 Cookie,可以直接在 HTTP Header 中傳送 Token。


在 Laravel 中實作 JWT 驗證

由於 Laravel 並未原生支援 JWT,我們通常會使用功能成熟的第三方套件,例如 tymon/jwt-auth。以下是實作的基本步驟:

1. 安裝與設定

首先,透過 Composer 安裝 tymon/jwt-auth 套件:

Bash
composer require tymon/jwt-auth

接著,發布套件設定檔並產生一組獨一無二的 JWT 密鑰,這組密鑰將用於 Token 的簽署與驗證。

Bash
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
php artisan jwt:secret

2. 模型配置

讓你的使用者模型 App\Models\User 實作 Tymon\JWTAuth\Contracts\JWTSubject 介面,並實作兩個方法:

PHP
use Tymon\JWTAuth\Contracts\JWTSubject;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable implements JWTSubject
{
    // ...

    public function getJWTIdentifier()
    {
        return $this->getKey();
    }

    public function getJWTCustomClaims()
    {
        return [];
    }
}

getJWTIdentifier() 會告訴 JWT 套件要將哪個欄位作為 Token 的唯一識別碼,通常是使用者的主鍵(ID)。

3. 登入與保護路由

建立一個登入路由,讓使用者提供帳號密碼。驗證成功後,使用 auth()->attempt() 方法產生一個新的 Token 並回傳。

PHP
// 在控制器中
public function login(Request $request)
{
    $credentials = $request->only(['email', 'password']);

    if (!$token = auth()->attempt($credentials)) {
        return response()->json(['error' => 'Unauthorized'], 401);
    }

    return response()->json([
        'access_token' => $token,
        'token_type' => 'bearer',
        'expires_in' => auth()->factory()->getTTL() * 60
    ]);
}

對於需要驗證的路由,只需將其放入 auth:api 中介層即可。

PHP
Route::group(['middleware' => 'auth:api'], function () {
    // 這裡的路由需要帶上有效的 JWT 才能存取
    Route::get('me', 'UserController@me');
});

Token 刷新機制:兼顧安全與用戶體驗

由於 JWT 在過期前無法被「登出」,設定一個短效期的 Token(例如 15 分鐘)是個好的安全實踐。但這也帶來了另一個問題:使用者可能需要頻繁重新登入。為了解決這個問題,我們需要建立一個 Token 刷新機制。

策略一:使用內建的 auth()->refresh()

tymon/jwt-auth 套件提供了一個簡單的刷新方法。當 Access Token 過期時,客戶端可以帶上舊的 Token,發送刷新請求。

PHP
// 在控制器中
public function refresh()
{
    // 換取一個新的 Access Token
    $newToken = auth()->refresh();

    return response()->json([
        'access_token' => $newToken,
        'token_type' => 'bearer',
        'expires_in' => auth()->factory()->getTTL() * 60
    ]);
}

此方法會自動將舊的 Token 列入黑名單,確保其無法再次被使用,提升了安全性。

策略二:使用 Refresh Token 旋轉機制

這是一種更為進階和安全的策略。登入時,伺服器會發送兩個 Token:一個短效期的 Access Token 和一個長效期的 Refresh Token。

  • Access Token:用於每次 API 請求,有效時間短,即使被盜用,風險也較低。

  • Refresh Token:用於換取新的 Access Token。

當 Access Token 過期時,客戶端會用 Refresh Token 發送刷新請求。伺服器驗證 Refresh Token 後,會生成一個全新的 Access Token 和一個全新的 Refresh Token,並將舊的 Refresh Token 註銷。這種「旋轉」機制能有效防止 Refresh Token 被重放攻擊,是業界推薦的最佳實踐。


安全性與最佳實踐

為了打造更強健的 API,我們還需考慮以下幾點:

  • Token 存放策略:為防禦 XSS 攻擊,不要將 Token 存放在瀏覽器的 localStorage。將其存放在 HttpOnly 和 Secure 的 Cookie 中,可有效防止 JavaScript 腳本竊取 Token。

  • Token 盜用偵測:在 Token 的 Payload 中加入如使用者 IP 位址或 User Agent 等資訊,刷新時進行比對,若發現不一致則拒絕請求。

  • Logout 機制:實作一個登出路由,當使用者登出時,手動將其 Token 列入黑名單,使其立即失效,而非等待其自然過期。

綜合運用上述策略,我們不僅能利用 JWT 的優勢快速建立可擴展的 API 服務,也能確保其在各種應用場景下的安全性與穩定性。

2018年11月6日 星期二

《面試官別再問》PHP Coding Style指南PSR-2

這個指南是在PSR-1上的延伸和擴展,基本的編碼標準

本指南的目的是減少認知摩擦(風格不統一而造成不便),當瀏覽不同作者的代碼時。它做的這些關於如何格式化PHP代碼通過列舉一個共享的規則設置和期望

此處樣式規則衍生均來自各成員項目間的共性。當不同作者在多個項目間合作時,有一個設置的指南,它有助於被用來在所有這些項目中使用。因此,本指南的好處不在規則本身,而是這個規則的共享

PSR是PHP Standards Recommendations(PHP標準建議) 的簡寫,是由PHP-FIG組織(PHP Framework Interop Group-PHP框架交互操作組織)提出。 PHP-FIG的工作是尋找項目之間的共性,以及讓開發者能更好協同工作的方式。讀者可能在瀏覽一些PHP技術文章的時候,可能會看到PSR-1,PSR-2,PSR-4,PSR-7等,這些是PHP-FIG的標準建議,這些標準建議的命名構成是以' PSR-'+'序號',每個PHP_FIG標準建議都是為了解決大部分框架頻繁遇到的一個特定的問題,而與此同時框架不需要自己再去重複解決問題,而是遵循PSR標準建議,採納共享的解決方案.

在此介紹一項工具

PHP Code Sniffer 簡稱 phpcs,可以用來檢查你寫的PHP是否符合PSR-2我們可以用Composer 來安裝他

安裝
composer require squizlabs/php_codesniffer

測試
phpcs --version

檢查程式

phpcs --standard=PSR2 專案資料夾

OverView (概覽)

PHP文件(MUST)必須只使用<?php 和<?= 標籤.
PHP文件(MUST)必須只能使用無BOM頭的UTF-8編碼格式.
PHP文件(SHOULD)應該要么聲明classe,functions。 constants 等等,或者general ouput(通用輸出?),改變php.ini的配置等等,但是(SHOULD Not)不應該兩個都有.
命名空間和類(MUST)必須要遵循自動加載的PSR規範[PSR-0,PSR-4];
類名明明(MUST)必須以大駝峰式.
類中的常量的命名(MUST)必須為以下劃線分割的大寫字母.
方法名(MUST)必須為小駝峰式.
PHP代碼(MUST)必須只使用無BOM頭的UTF-8編碼格式.

Side Effects

一個PHP文件(SHOULD)必須要聲明一些class,function,constant等,並且沒有side effects (執行邏輯),或者(SHOULD)應該有side effects(執行邏輯),但是(SHOULD NOT)不應該兩個都同時存在.

   'side effects'代表的就是和聲明class,function,constant等不直接相關聯的執行邏輯,僅僅來自這個包含的文件。

  'Side effects' 包含以下操作,但不僅限於此:general output(通用輸出), 'require'和'include'的使用,連接外部服務,修改php.ini的設置,觸發錯誤或者拋出異常,修改全局或者靜態變量,讀取或者寫入文件操作等等.

  以下是一個我們要避免的例子,就是一個具有'declarations'和side effects的PHP文件。

<?php
// side effect: change ini settings
ini_set('error_reporting', E_ALL);

// side effect: loads a file
include "file.php";

// side effect: generates output
echo "<html>\n";

// declaration
function foo()
{
    // function body
}
以下是一個是正確的例子,只包含了'declaraion'沒有'side effects'.

<?php
// declaration
function foo()
{
    // function body
}

General - 約定

基本編碼標準
代碼必須遵循PSR-1中的所有規範

文件
所有PHP文件必須使用Unix LF(linefeed)作為行的結束符

所有PHP文件必須以一個空白行作為結束

只包含PHP代碼的文件結束?>標記必須被忽略

行
行的長度一定不能有硬性約束

行長度的軟限制必須在120字符內;自動樣式檢查必須警告但一定不能提示錯誤在軟限制上

行不應該超過80個字符;超過80個字符的長度的行應當拆分成多個不超過80個字符的子行

非空行後一定不能尾隨空格符

空行可以改善代碼閱讀,並且表明相關的代碼塊

每行一定不能存在多於一條語句

縮進
編碼必須使用4個空格,並且一定不能使用tab縮進

僅使用空格,並且不要混淆空格用tab,有助於避免比較差異,打補丁,重讀和註解問題。使用空格也使插入細粒度的子縮進為了跨線對齊變得容易

關鍵字:True,False,Null
PHP 關鍵字必須小寫

PHP常量true,false,null必須小寫

Namespace and Use Declarations - 命名空間和使用聲明
在目前,namespace聲明之後必須有一個空行

在目前,所有use聲明必須在namespace聲明之後

每個聲明必須有一個use關鍵字

use 塊之後必須有一個空行

<?php
namespace Vendor \ Package ;

use FooClass ;
use BarClass as Bar ;
use OtherVendor \ OtherPackage \ BazClass ;

// ... additional PHP code ...

Classes,Properties,and Methods - 類,屬性和方法
術語class指的是所有的class,interface,trait

繼承和實現
extends和implements關鍵字必須被聲明在作為類名的同一行

類的開花括號必須獨佔一行; 類的閉花括號必須在類體之後獨佔一行

implements的列表可以被分隔成多行,每個子行要縮進。當這樣做,列表中的第一個條目必須獨佔一行,並且必須是僅僅一個接口獨佔一行

<?php
namespace Vendor \ Package ;

use FooClass ;
use BarClass as Bar ;
use OtherVendor \ OtherPackage \ BazClass ;

class ClassName extends ParentClass implements
     \ ArrayAccess ,
    \ Countable ,
    \ Serializable
{ // constants, properties, methods }

控制結構(Control Structures)

控制結構的通用編碼風格規範:
控制結構的關鍵字之後應該有一個空格
左括號之後不應該有空格
右括號之前不應該有空格
右花括號和左花括號之間應該有一個空格
結構體應該縮印一次
右花括號應該在結構體的下一行
每一個結構體應該被花括號包圍;這樣子,結構看起來很標準,並且當加入新行的時候,gi 可以減少引入錯誤的可能性。

if, elseif, else
一個if結構看起來像下面那樣。注意圓括號的位置,空格和花括號;並且else和elseif與來自之前的結構體閉花括號在相同的行

<?php
if ($expr1) { // if body } elseif ($expr2) { // elseif body } else { // else body; }

關鍵字elseif應該被使用而不是else if以至於所有的控制關鍵字看起來像單個單詞

switch, case
一個switch結構看起來像下面那樣。注意圓括號的位置,空格和花括號。 case語句必須被縮進一次在switch中,並且break關鍵字(或者其它終止關鍵字)必須被縮進相同的級別在case體內。當故意落空一個非空case體時必須有一個註釋,例如:// no break
<?php
switch ($expr) { case 0 : echo 'First case, with a break' ; break ; case 1 : echo 'Second case, which falls through' ; // no break case 2 : case 3 : case 4 : echo 'Third case, return instead of break' ; return ; default : echo 'Default case' ; break ; }

2018年11月4日 星期日

《面試官別再問》PHP實現Websocket

WebSocket是一個持久化的協議

http1.0的生命週期是以request作為界定的,也就是一個request,一個response,對於http來說,本次client與server的會話到此結束;而在http1.1中,稍微有所改進,即添加了keep-alive,也就是在一個http連接中可以進行多個request請求和多個response接受操作。然而在實時通信中,並沒有多大的作用,http只能由client發起請求,server才能返回信息,即server不能主動向client推送信息,無法滿足實時通信的要求。而WebSocket可以進行持久化連接,即client只需進行一次握手,成功後即可持續進行數據通信,值得關注的是WebSocket實現client與server之間全雙工通信,即server端有數據更新時可以主動推送給client端。

client與server建立socket時握手的會話內容,即request與response

a、client建立WebSocket時向服務器端請求的信息

GET /chat HTTP/1.1

Host: server.example.com

Upgrade: websocket //告訴服務器現在發送的是WebSocket協議

Connection: Upgrade

Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== //是一個Base64 encode的值,這個是瀏覽器隨機生成的,用於驗證服務器端返回數據是否是WebSocket助理

Sec-WebSocket-Protocol: chat, superchat

Sec-WebSocket-Version: 13

Origin: http://example.com

b、服務器獲取到client請求的信息後,根據WebSocket協議對數據進行處理並返回,其中要對Sec-WebSocket-Key進行加密等操作

HTTP/1.1 101 Switching Protocols

Upgrade: websocket //依然是固定的,告訴客戶端即將升級的是Websocket協議,而不是mozillasocket,lurnarsocket或者shitsocket

Connection: Upgrade

Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= //這個則是經過服務器確認,並且加密過後的Sec-WebSocket-Key,也就是client要求建立WebSocket驗證的憑證

PHP中建立socket的過程講解


1.client與server之間建立socket通訊,首先在PHP中建立socket並監聽Port

//傳相應的IP與端口進行創建socket操作
function WebSocket($address,$port){
$server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($server, SOL_SOCKET, SO_REUSEADDR, 1);//1表示接受所有的數據包
socket_bind($server, $address, $port);
socket_listen($server);
return $server;
}

2.設計一個迴圈掛起WebSocket通道,進行資料的接收

//對建立的socket迴圈進行監聽,處理數據
function run(){
//死循環,直到socket斷開
while(true){
$changes=$this->sockets;
$write=NULL;
$except=NULL;
         
/*
//這個函數是同時接受多個連接的關鍵,我的理解它是為了阻塞程序繼續往下執行。
socket_select ($sockets, $write = NULL, $except = NULL, NULL);
$sockets可以理解為一個數組,這個數組中存放的是文件描述符。當它有變化(就是有新消息到或者有客戶端連接/斷開)時,socket_select函數才會返回,繼續往下執行。
$write是監聽是否有客戶端寫數據,傳入NULL是不關心是否有寫變化。
$except是$sockets裡面要被排除的元素,傳入NULL是”監聽”全部。
最後一個參數是超時時間
如果為0:則立即結束
如果為n>1: 則最多在n秒後結束,如遇某一個連接有新動態,則提前返回
如果為null:如遇某一個連接有新動態,則返回
*/
socket_select($changes,$write,$except,NULL);
foreach($changes as $sock){
             
//如果有新的client連接進來,則
if($sock==$this->master){

//接受一個socket連接
$client=socket_accept($this->master);

//給新連接進來的socket一個唯一的ID
$key=uniqid();
$this->sockets[]=$client; //將新連接進來的socket存進連接池
$this->users[$key]=array(
'socket'=>$client, //記錄新連接進來client的socket信息
'shou'=>false //標誌該socket資源沒有完成握手
);
//否則1.為client斷開socket連接,2.client發送信息
}else{
$len=0;
$buffer='';
//讀取該socket的信息,注意:第二個參數是引用傳參即接收數據,第三個參數是接收數據的長度
do{
$l=socket_recv($sock,$buf,1000,0);
$len+=$l;
$buffer.=$buf;
}while($l==1000);

//根據socket在user池裡面查找相應的$k,即健ID
$k=$this->search($sock);

//如果接收的信息長度小於7,則該client的socket為斷開連接
if($len<7){
//給該client的socket進行斷開操作,並在$this->sockets和$this->users裡面進行刪除
$this->send2($k);
continue;
}
//判斷該socket是否已經握手
if(!$this->users[$k]['shou']){
//如果沒有握手,則進行握手處理
$this->woshou($k,$buffer);
}else{
//走到這裡就是該client發送信息了,對接受到的信息進行uncode處理
$buffer = $this->uncode($buffer,$k);
if($buffer==false){
continue;
}
//如果不為空,則進行消息推送操作
$this->send($k,$buffer);
}
}
}

}

3.伺服器端完成的WebSocket的前期工作後,就等著client連接進行,client建立WebSocket很簡單

var ws = new WebSocket("ws://IP:端口");
//握手監聽函數
ws.onopen=function(){
//狀態為1證明握手成功,然後把client自定義的名字發送過去
if(so.readyState==1){
//握手成功後對服務器發送信息
so.send('type=add&ming='+n);
}
}
//錯誤返回信息函數
ws.onerror = function(){
    console.log("error");
};
//監聽服務器端推送的消息
ws.onmessage = function (msg){
    console.log(msg);
}
//斷開WebSocket連接
ws.onclose = function(){
    ws = false;
}

《面試官別再問》PHP中$_FILES檔案上傳漏洞

當僅允許上傳圖片的時候,開發者通常使用PHP的getimagesize函數來檢測圖片的頭部信息.該函數在被調用時將會返回圖片的尺寸,如果圖片經驗證為無效的,也就是說圖片頭部信息不正確,則會返回false值.因此一個開發者一般會檢查該函數是否返回true或false,並且通過該信息來驗證上傳的文件.所以,如果一個惡意用戶試著上傳一個內嵌有簡單PHP shell的jpg文件的話,該函數會返回false然後他將不允許上傳此文件.然而,即使這種方式也能被很容易的繞過.如果一個圖片在一個圖片編輯器內打開,就如Gimp ,用戶就可以編輯圖片的註釋區,那兒就能插入PHP代碼,就如下圖所示.
該圖片仍然有一個有效的頭部; 因此就繞過了getimagesize 函數的檢查. 從下面截圖中可以看到, 當一個普通的web 瀏覽器請求該圖的時候, 插入到圖片註釋區的PHP 代碼仍然被執行了:


檔案上傳漏洞(File Upload Attack)是由於檔案上傳功能實現代碼沒有嚴格限制用戶上傳的檔案後綴以及文件類型,導致允許攻擊者向某個可通過Web 訪問的目錄上傳任意PHP檔案,並能夠將這些檔案傳遞給PHP 解釋器,就可以在遠程服務器上執行任意PHP腳本。

一套web應用程序,一般都會提供文件上傳的功能,方便來訪者上傳一些文件。
 下面是一個簡單的文件上傳表單

<form action="upload.php" method="post" enctype="multipart/form-data" name="form1">

<input type="file" name="file1" /><br />

<input type="submit" value="上傳文件" />

<input type="hidden" name="MAX_FILE_SIZE" value="1024" />

</form>


php的配置文件php.ini,其中選項upload_max_filesize指定允許上傳的文件大小,默認是2M
$_FILES數組變量
PHP使用變量$_FILES來上傳文件,$_FILES是一個數組。
如果上傳test.txt,那麼$_FILES數組的內容為:

$FILES

Array
{

  [file] => Array
   {

     [name] => test.txt //文件名稱
     [type] => text/plain //MIME類型
     [tmp_name] => /tmp/php5D.tmp //臨時文件
     [error] => 0 //錯誤信息
     [size] => 536 //文件大小,單位字節
   }
}

如果上傳文件按鈕的name屬性值為file
<input type="file" name="file" />
那麼使用$_FILES['file']['name']來獲得客戶端上傳文件名稱,不包含路徑。使用$_FILES['file']['tmp_name']來獲得服務端保存上傳文件的臨時文件路徑


存放上傳文件的文件夾

PHP不會直接將上傳文件放到網站根目錄中,而是保存為一個臨時文件,名稱就是$_FILES['file']['tmp_name']的值,開發者必須把這個臨時文件複製到存放的網站文件夾中。

$_FILES['file']['tmp_name']的值是由PHP設置的,與文件原始名稱不一樣,開發者必須使用$_FILES['file']['name']來取得上傳文件的原始名稱。
上傳文件時的錯誤信息 $_FILES['file']['error']變量用來保存上傳文件時的錯誤信息,它的值如下:

如果提供給網站訪問者上傳圖片的功能,那必須小心訪問者上傳的實際可能不是圖片,而是可以指定的PHP程序。如果存放圖片的目錄是一個開放的文件夾,則入侵者就可以遠程執行上傳的PHP文件來進行攻擊。
下面是一個簡單的文件上傳例子:
<?php
// 設置上傳文件的目錄
$uploaddir = "D:/www/images/";

// 檢查file是否存在
if (isset($_FILES['file1']))

{
// 要放在網站目錄中的完整路徑,包含文件名
$uploadfile = $uploaddir . $_FILES['file1']['name'];

// 將服務器存放的路徑,移動到真實文件名 move_uploaded_file($_FILES['file1']['tmp_name'], $uploadfile);

}
?>
……
<form method="post" enctype="multipart/form-data" name="form1">
<input type="file" name="file1" /><br />
<input type="submit" value="上傳文件" />
<input type="hidden" name="MAX_FILE_SIZE" value="1024" />
</form>

這個例子沒有檢驗文件後綴,可以上傳任意文件,很明顯的上傳漏洞。
利用此漏洞黑客克制自由上床任意的木馬文件而導致網站失陷。


漏洞防護措施

解決上面所述問題的一種方法是通過檢查上傳文件的類型來限制用戶的文件上傳,如以下代碼所示。


<?php

if(isset($_POST["form"]))


{
if($_FILES['upfile']['type'] == 'image/pjpeg') //檢查文件類型是否為JPEG

{
$uploadfile = "upfiles/".$_FILES['upfile']['name'];
//上傳後文件所在的文件名和路徑


move_uploaded_file($_FILES['upfile']['tmp_name'], $uploadfile);

//上傳文件



print_r($_FILES);
die();
}


else
   {
     die("上傳文件的格式不正確!");
   }
}
?>

上面的代碼要求用戶上傳的文件必須是JPEG類型的圖片文件,徹底地避免了終端用戶通過上傳PHP腳本危害服務器的行為。

2018年11月1日 星期四

《面試官別再問》PHP中Session與Cookie有何不同

會話(Session)跟踪是Web程序中常用的技術,用來跟踪用戶的整個會話。常用的會話跟踪技術是Cookie與Session。 Cookie通過在客戶端記錄信息確定用戶身份,Session通過在服務器端記錄信息確定用戶身份。

Session是一種服務器端的信息管理機制,它把這些文件信息以文件的形式存放在服務器的硬盤空間上(這是默認情況,可以用memcache把這種數據放到內存裡面)當客戶端向服務器發出請求時,要求服務器端產生一個session時,服務器端會先檢查一下,客戶端的cookie裡面有沒有session_id,是否過期。如果有這樣的session_id的話,服務器端會根據cookie裡的session_id把服務器的session檢索出來。如果沒有這樣的session_id的話,服務器端會重新建立一個。 PHPSESSID是一串加了密的字符串,它的生成按照一定的規則來執行。同一客戶端啟動二次session_start的話,session_id是不一樣的。

區別:Cookie 保存在客戶端瀏覽器中,而Session 保存在服務器上。 Cookie 機制是通過檢查客戶身上的“ 通行證” 來確定客戶身份的話,那麼Session 機制就是通過檢查服務器上的“ 客戶明細表” 來確認客戶身份。 Session 相當於程序在服務器上建立的一份客戶檔案,客戶來訪的時候只需要查詢客戶檔案表就可以了。


Cookie是存儲在客戶端瀏覽器中的數據,我們通過Cookie來跟踪與存儲用戶數據。

一般情況下,Cookie通過HTTP headers從服務端返回到客戶端。多數web程序都支持Cookie的操作,因為Cookie是存在於HTTP的標頭之中,所以必須在其他信息輸出以前進行設置,類似於header函數的使用限制。

PHP通過setcookie函數進行Cookie的設置,任何從瀏覽器發回的Cookie,PHP都會自動的將他存儲在$ _COOKIE的全局變量之中,因此我們可以通過$ _COOKIE['key']的形式來讀取某個Cookie值。

PHP中的Cookie具有非常廣泛的使用,經常用來存儲用戶的登錄信息,購物車等,且在使用會話Session時通常使用Cookie來存儲會話id來識別用戶,Cookie具備有效期,當有效期結束之後,Cookie會自動的從客戶端刪除。同時為了進行安全控制,Cookie還可以設置域跟路徑。

很多網站都會使用Cookie。例如,Google會向客戶端頒發Cookie,Baidu也會向客戶端頒發Cookie。那瀏覽器訪問Google會不會也攜帶上Baidu頒發的Cookie呢?或者Google能不能修改Baidu頒發的Cookie呢?

答案是否定的。 Cookie具有不可跨域名性。根據Cookie規範,瀏覽器訪問Google只會攜帶Google的Cookie,而不會攜帶Baidu的Cookie。 Google也只能操作Google的Cookie,而不能操作Baidu的Cookie。

Cookie在客戶端是由瀏覽器來管理的。瀏覽器能夠保證Google只會操作Google的Cookie而不會操作Baidu的Cookie,從而保證用戶的隱私安全。瀏覽器判斷一個網站是否能操作另一個網站Cookie的依據是域名。 Google與Baidu的域名不一樣,因此Google不能操作Baidu的Cookie。

需要注意的是,雖然網站images.google.com與網站www.google.com同屬於Google,但是域名不一樣,二者同樣不能互相操作彼此的Cookie。


注意:用戶登錄網站www.google.com之後會發現訪問images.google.com時登錄信息仍然有效,而普通的Cookie是做不到的。這是因為Google做了特殊處理。本章後面也會對Cookie做類似的處理。

Unicode編碼:保存中文


中文與英文字符不同,中文屬於Unicode字符,在內存中佔4個字符,而英文屬於ASCII字符,內存中只佔2個字節。 Cookie中使用Unicode字符時需要對Unicode字符進行編碼,否則會亂碼。


提示:Cookie中保存中文只能編碼。一般使用UTF-8編碼即可。不推薦使用GBK等中文編碼,因為瀏覽器不一定支持,而且JavaScript也不支持GBK編碼。

BASE64編碼:保存二進製圖片


Cookie不僅可以使用ASCII字符與Unicode字符,還可以使用二進制數據。例如在Cookie中使用數字證書,提供安全度。使用二進制數據時也需要進行編碼。

%注意:本程序僅用於展示Cookie中可以存儲二進制內容,並不實用。由於瀏覽器每次請求服務器都會攜帶Cookie,因此Cookie內容不宜過多,否則影響速度。 Cookie的內容應該少而精。

【AI 開發工具推薦】RTK 實戰指南:自動過濾冗長日誌,讓 Claude Code 跑得又快又省

🚀 【實戰教學】用 RTK 幫 AI 程式碼助理瘦身!大幅節省 60%~90% Token 消耗 在日常使用 Claude Code、 Cursor、 Trae 或 Gemini CLI 等 AI 輔助開發時, 終端機(CLI)輸出的落落長訊息 (例如: git status ...