Bài viết hướng dẫn tạo, cập nhật và kiểm tra tài khoản quản lý trong file .htpasswd bằng PHP.
Nếu từng làm việc với Apache HTTP Server hẳn là ai cũng có lúc sử dụng .htpasswd
để tạo mật khẩu ngăn chặn truy cập trái phép. Việc tạo file .htpasswd
tương đối nhiêu khê, vì vậy cũng có không ít công cụ cho phép tạo file .htpasswd
trực tuyến.
Trong bài viết này, tôi sẽ sử dụng .htpasswd
như một kiểu quản lý tài khoản thay vì dùng database theo cách thông thường. Việc thêm/cập nhật tài khoản sẽ được thực hiện bằng PHP, tất nhiên là sau khi đăng nhập thành công rồi.
Mục lục
Cấu trúc file .htpasswd
.htpasswd
là một file text, thường được tạo bằng lệnh htpasswd
trong gói Apache. Mỗi dòng trong file .htpasswd
là thông tin về tài khoản (user name) và mật khẩu của user đó (được mã hóa một chiều hoặc không), phân cách bằng dấu hai chấm ‘:’.
Ví dụ tài khoản icreativ
có mật khẩu 12345678
được mã hóa bcrypt
như sau:
icreativ:$2y$05$Z6JReuEu1JrC8YphmdlUFuJhg4gLbvMHgJTw9wa6SWc5wrukN4JQS
Các user lưu trong .htpasswd
không được trùng nhau, chiều dài tối đa 255 ký tự và không được chứa dấu hai chấm ‘:’.
Mật khẩu có thể được mã hóa bằng những thuật toán khác nhau như MD5, SHA-1, crypt, bcrypt hoặc thuật toán mã hóa của hệ thống. Hãy tham khảo man
của lệnh htpasswd
để biết thêm chi tiết.
Chỉnh sửa .htpasswd bằng PHP
Bản chất .htpasswd
là một file text, mỗi dòng lại là thông tin của một user nên đây đơn thuần là xử lý text file theo từng dòng (line-by-line).
Các user không trùng nhau nên ta sẽ đọc lần lượt từ đầu đến cuối tìm user tương ứng (trước dấu hai chấm ‘:’) để tìm mật khẩu hoặc chỉnh sửa.
Phần tưởng chừng phức tạp nhất là mã hóa một chiều thì lại cũng thật đơn giản vì PHP hỗ trợ hết các thuật toán mã hóa đó bao gồm thuật toán mạnh nhất là bcrypt
.
Các hàm routine dưới đây xử lý file .htpasswd
có mật khẩu hoàn toàn được mã hóa bằng bcrypt
. Các hàm PHP được sử dụng là password_hash
với kiểu mã hóa PASSWORD_BCRYPT
để mã hóa mật khẩu và kiểm tra bằng hàm password_verify
.
Lưu ý, vì lý do bảo mật, file
.htpasswd
không nên đặt trong thư mục web.
Các hàm PHP xử lý file .htpasswd
Kiểm tra tài khoản
/** * @param $username The user id * @param $password The entered password * @param $htpasswd The absolute path to the .htpasswd file * @return true if the user is found and the password matches */ function htpasswd_verify($username, $password, $htpasswd) { $lines = explode("\n", file_get_contents($htpasswd)); foreach ($lines as $line) : $line = preg_replace('/\s+/', '', $line); // remove spaces if ($line) : list($user, $pass) = explode(":", $line, 2); if ( $user === $username ) : // found the user return ( password_verify($password, $pass) ); endif; endif; endforeach; return false; }
Tạo/cập nhật tài khoản
/** * @param $username The user id * @param $password The entered password * @param $htpasswd The absolute path to the .htpasswd file */ function htpasswd_update($username, $password, $htpasswd) { $password = password_hash($password, PASSWORD_BCRYPT); //read the file into an array $lines = explode("\n", file_get_contents($htpasswd)); $new_file = ""; $found = false; foreach ($lines as $line) : $line = preg_replace('/\s+/', '', $line); // remove spaces if ($line) : list($user, $pass) = explode(":", $line, 2); if ( $user === $username ) : // found the user $new_file .= $username . ':' . $password . "\n"; $found = true; else : $new_file .= $user . ':' . $pass . "\n"; endif; endif; endforeach; if (!$found) : $new_file .= $username . ':' . $password; endif; //save the information $fd = fopen( $htpasswd, 'w' ); fwrite($fd, $new_file); fclose($fd); }
Xóa tài khoản
/** * @param $username The user id * @param $password The entered password * @param $htpasswd The absolute path to the .htpasswd file */ function htpasswd_remove($username, $password, $htpasswd) { $lines = explode("\n", file_get_contents($htpasswd)); $new_file = ""; $removed = false; foreach ($lines as $line) : $line = preg_replace('/\s+/', '', $line); // remove spaces if ($line) : list($user, $pass) = explode(":", $line, 2); if ( $user === $username && password_verify($password, $pass) ) : $removed = true; else : $new_file .= $user . ':' . $pass . "\n"; endif; endif; endforeach; //save the information if ( $removed ) : $fd = fopen( $htpasswd, 'w' ); fwrite($fd, $new_file); fclose($fd); endif; return $removed; }
Ví dụ ta tạo cấu trúc thư mục Web root như dưới.
\_ index.php \_ htpasswd.php \_ ... \_ .htaccess
Lưu ý trong đây không chứa file .htpasswd
, thay vào đó là một đường dẫn ngoài ví dụ /path/to/.htpasswd
. File .htpasswd
này cần phân quyền phù hợp để PHP có thể chỉnh sửa được.
File cấu hình .htaccess
sử dụng file .htpasswd
để bảo vệ thư mục hiện hành.
.htaccess
AuthType Basic AuthName "Password Protected Area" AuthUserFile /path/to/.htpasswd Require valid-user
File htpasswd.php
là giao diện cho phép tạo hoặc cập nhật tài khoản đăng nhập bằng .htpasswd
. Đoạn mã này đương nhiên cũng được bảo vệ bởi chính file .htpasswd
.
Sau khi đổi mật khẩu thành công, Apache lập tức phát hiện sự thay đổi của file .htpasswd
và sẽ yêu cầu đăng nhập lại ngay lập tức.
htpasswd.php
<?php // The absolute path to the .htpasswd file define( 'HTPASSWD', '/path/to/.htpasswd' ); if ( isset($_POST['username']) && isset($_POST['password']) ) : htpasswd_update($_POST['username'], $_POST['password'], HTPASSWD); // redirect to the main page header('Location: index.php'); exit; endif; ?> <!DOCTYPE html> <html> <head> <title>.htpasswd</title> </head> <body> <form method="POST"> <label for="username">User Name</label> <input type="text" id="username" name="username" required> <label for="password">Password</label> <input type="password" id="password" name="password" placeholder="Password" required> <label for="confirm">Confirm</label> <input type="password" id="confirm" name="confirm" required> <input type="submit" name="submit" value="Add or update ›"> </form> <script> var password = document.getElementById("password"), confirm = document.getElementById("confirm"); // Validate whether password and confirmation password matches function validatePassword() { if(password.value != confirm.value) { confirm.setCustomValidity("Password mismatched"); } else { confirm.setCustomValidity(''); } } password.onchange = validatePassword; confirm.onkeyup = validatePassword; </script> </body> </html>