เทคนิคการจัดเก็บรูปภาพหรือไฟล์อย่างปลอดภัย
การจัดเก็บรูปภาพหรือไฟล์บน Server เมื่อต้องการเรียกดูหรือดาวน์โหลด เราจะสามารถเรียกผ่าน URL ได้ เช่น domain.tld/path/to/images/image.jpg ซึ่งหมายความว่า ไฟล์ image.jpg จัดเก็บอยู่ที่ไดเร็คทอรี่ /path/to/images/ จากตัวอย่างนี้ ใครก็ตามที่รู้เส้นทางนี้ สามารถเข้าถึงไฟล์ที่ต้องการได้โดยตรง
ข่าวร้ายก็คือ ถ้าเรารู้ ผู้ไม่ประสงค์ดีก็รู้ได้เช่นกัน อาจด้วยการสุ่มหรือคาดเดาก็ได้ วันนี้เราจะมาพูดถึงการป้องกันการเข้าถึงไฟล์โดยตรงด้วย PHP กัน
ว่าด้วยเรื่องวิธีการจัดเก็บกันก่อน
1. จัดเก็บไฟล์นอกโฟลเดอร์ที่เข้าถึงได้จากเว็บเซิร์ฟเวอร์
คุณสามารถจัดเก็บไฟล์ในไดเร็กทอรีที่อยู่นอกโฟลเดอร์ที่เว็บเซิร์ฟเวอร์สามารถเข้าถึงได้โดยตรง ตัวอย่างเช่น
/project
/public_html <- โฟลเดอร์ที่เว็บเซิร์ฟเวอร์เข้าถึงได้
/images <- โฟลเดอร์ที่ใช้เก็บรูปภาพ
คุณสามารถเก็บไฟล์ไว้ที่ images/ ได้ ซึ่งไฟล์ที่อยู่ในโฟลเดอร์นี้จะไม่สามารถเรียกใช้งานได้ผ่าน URL
2. ใช้ .htaccess เพื่อป้องกันการเข้าถึง
ถ้าคุณต้องการเก็บไฟล์ในไดเร็กทอรีที่สามารถเข้าถึงได้จากเว็บเซิร์ฟเวอร์ คุณสามารถใช้ไฟล์ .htaccess เพื่อป้องกันการเข้าถึงโดยตรงได้ เช่น
สร้างไฟล์ .htaccess ในไดเร็กทอรีที่เก็บไฟล์รูปภาพ
Order Allow,Deny
Deny from all
วิธีนี้จะป้องกันการเข้าถึงไฟล์ในไดเร็กทอรีนั้นโดยตรงจาก URL ได้
3. เข้ารหัสชื่อไฟล์
อีกวิธีหนึ่งคือการเข้ารหัสชื่อไฟล์หรือเก็บไฟล์ในโฟลเดอร์ที่มีชื่อแบบสุ่ม ซึ่งจะทำให้การเดา URL ของไฟล์ทำได้ยากขึ้น ตัวอย่างเช่น
<?php
// สร้างชื่อไฟล์สุ่ม
$file_name = md5(uniqid()) . '.jpg';
// บันทึกไฟล์ลงในโฟลเดอร์ที่ตั้งชื่อสุ่ม
move_uploaded_file($_FILES['image']['tmp_name'], '/path/to/images/' . $file_name);
?>
การสุ่มชื่อไฟล์จะทำให้การคาดเดาชื่อของไฟล์ทำได้ยากขึ้นและเสียเวลามากขึ้น เช่น idcard.jpg หรือ ased2236445w.jpg ชื่อแบบหลังจะทำให้การสุ่มเรียกชื่อไฟล์ทำได้ยากกว่า
การนำไฟล์ที่มีการป้องกันมาใช้งาน
การจัดเก็บไฟล์ตามวิธีที่ 1 และ 2 จะทำให้เราสูญเสียการเข้าถึงไฟล์โดยตรงผ่าน URL ส่วนในวิธีที่ 3 อาจต้องอาศัยการจัดเก็บทีอยู่ของไฟล์จริงๆลงฐานข้อมูล เพื่อไว้ใช้อ้างอิงเมื่อต้องการเรียกใช้ไฟล์ ซี่งเมื่อได้ที่อยู่จริงของไฟล์มาแล้ว เราจะยังคงเรียกใช้ไฟล์ผ่าน URL ได้ตามปกติ หรือ อาจใช้วิธีจัดเก็บไฟล์แบบสุ่มลงในโฟลเดอร์ที่เกี่ยวข้องไว้ก็ได้ เช่นการสร้างไดเร็คทอรี่ที่เก็บไฟล์เป็น ID และเก็บไฟล์ที่เกี่ยวข้องแบบสุ่มลงในโฟลเดอร์นั้นๆ การเรียกใช้งานก็ใช้วิธีแสกนโฟลเดอร์เอาไฟล์ออกมาเลย ซึ่งผมใช้วิธีนี้กับการจัดเก็บไฟล์ทั่วๆไป เช่นไฟล์รูปของสินค้า (หนึ่งสินค้ามีหลายรูป) ซึ่งประเด็นหลักก็คือมันสามารถเรียกไฟล์ผ่าน URL ได้ตามปกติ แต่มีการป้องกันโดยการตั้งชื่อไฟล์แบบสุ่ม
ในกรณีของไฟล์ที่มีความอ่อนไหว เช่นบัตรประชาชน การจัดเก็บตามวิธีที่ 3 อาจไม่ค่อยปลอดภัยนัก วิธีที่ 1 และ 2 จะปลอดภัยกว่า เพราะมันจะทำให้เราสามารถให้สิทธิ์รวมถึงควบคุมการใช้งานไฟล์ได้ โดยการเรียกใช้ไฟล์ผ่าน PHP ดังโค้ดตัวอย่างด้านล่าง
<?php
session_start();
// เช็คสิทธิ์ผู้ใช้ (ปรับให้เหมาะสมกับระบบของคุณ)
if (!isset($_SESSION['login'])) {
die('Access denied.');
}
// รับชื่อไฟล์รูปภาพจากพารามิเตอร์ GET
$image = basename($_GET['image']);
// กำหนดเส้นทางไปยังโฟลเดอร์ที่เก็บรูปภาพ
$file = '/path/to/protected/images/' . $image;
if (file_exists($file)) {
// กำหนด Content-Type ตามประเภทของรูปภาพ
$mime_type = mime_content_type($file);
header('Content-Type: ' . $mime_type);
header('Content-Length: ' . filesize($file));
readfile($file);
exit;
} else {
// ถ้าไฟล์ไม่พบ
header("HTTP/1.0 404 Not Found");
echo "File not found.";
}
?>
ตัวอย่างโค้ดด้านบนมีชื่อไฟล์ว่า image.php เมื่อมีการเรียกไฟล์นี้ จะมีการตรวจสอบสิทธิ์โดย SESSION ก่อน จากนั้นจะอ่านชื่อรูปภาพจาก $_GET['image'] นำไปรวมกันกับ path จริงที่จัดเก็บไฟล์ (นอก Server หรือที่ได้รับการป้องกันไว้) ซึ่ง PHP จะสามารถโหลดไฟล์และนำมาแสดงผลเหมือนกับการโหลดไฟล์หรือรูปภาพตรงๆได้
ใน HTML คุณสามารถใช้ <img> แท็กเพื่อแสดงรูปภาพโดยการเรียกใช้ไฟล์ image.php พร้อมกับพารามิเตอร์ GET ที่มีชื่อไฟล์รูปภาพ เช่น
<!DOCTYPE html>
<html>
<head>
<title>Protected Image</title>
</head>
<body>
<h1>Protected Image</h1>
<img src="image.php?image=ased2236445w.jpg" alt="Protected Image">
</body>
</html>
โค้ดต่างๆในบทความนี้เป็นเพียงตัวอย่างพื้นฐานเท่านั้นนะครับ ในการนำไปใช้งานจริงอาจต้องปรับปรุงโค้ดให้เหมาะสมกับโปรเจ็คของตัวเองเพิ่มเติม