Nội dung bài viết
Video học lập trình mỗi ngày
Bài viết được đưa vào: Chuyên mục cuối tuần-dev
Sau khi chức năng Login và Register được mô phỏng trong Member Nestjs Backend thì có một số vấn đề được đặt ra, mặc dùng triển khai chỉ là mức dành cho các lập trình viên cơ bản cũng như các bạn từ FE chuyển qua Full-Stack
.
Khuyến nghị: Hai ngã rẽ của Lập Trình Viên mông lung
Để làm rõ thêm một số quy trình chuẩn dành cho việc Login thì tôi và anh chị đề cập ở đây một chút, thay vì mất thời gian nói dài, nói dai trong thời gian diễn ra quá trình thực hành.
Tất nhiên tôi sẽ sơ lược nhanh...
Thiết kế table user_login
Nếu các bạn làm demo chơi chơi thì không cần phải thiết kế thêm một table user_login
này để làm gì? Chỉ cần checkLog là đủ rồi.
Còn thực tế thì ngoài các table như user
, ip_list
... thì sẽ có thêm bảng user_login
vì sao thì tôi sẽ nói sau, tất nhiên là không phải những khái niệm cơ bản... Đầu tiên bảng user_login
có những fields thế này:
CREATE TABLE `user_login` (
`id` VARCHAR(40) NOT NULL COMMENT 'Primary Key (as per original image)',
`user_code` VARCHAR(50) NULL DEFAULT NULL COMMENT 'The username entered during the login attempt',
`user_id` VARCHAR(40) NULL DEFAULT NULL COMMENT 'User ID (if determined, e.g., after successful login or for failed attempts on known users)',
`token` VARCHAR(100) NULL DEFAULT NULL COMMENT 'Login token generated (if successful)',
`action` TINYINT(1) NULL DEFAULT NULL COMMENT 'Action type: 1=Login, 2=Logout, 3=Forced Logout',
`result` TINYINT(1) NULL DEFAULT NULL COMMENT 'Result of the action: 1=Success, 0=Failure',
`ip_address` VARCHAR(45) NULL DEFAULT NULL COMMENT 'IP address used for the action',
`user_agent` VARCHAR(255) NULL DEFAULT NULL COMMENT 'User agent string (browser/device information)',
`cust_id` VARCHAR(40) NULL DEFAULT NULL COMMENT 'Customer ID (if applicable, for multi-tenant systems)',
`create_time` DATETIME NULL DEFAULT NULL COMMENT 'Timestamp when the log record was created',
`update_time` DATETIME NULL DEFAULT NULL COMMENT 'Timestamp when the log record was last updated',
`is_deleted` TINYINT(1) NULL DEFAULT NULL COMMENT 'Soft delete flag (e.g., 1=deleted, 0=not deleted)',
PRIMARY KEY (`id`),
KEY `idx_log_user_id` (`user_id`), -- Index for faster lookups by user_id
KEY `idx_log_user_code` (`user_code`), -- Index for faster lookups by user_code
KEY `idx_log_cust_id` (`cust_id`), -- Index for faster lookups by cust_id
KEY `idx_log_create_time` (`create_time`) -- Index for faster lookups/sorting by creation time
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Stores the history of login/logout events';
Tiếp đến bàn đến việc lợi ích của nó là gì? Chả phải có checkLog
rồi hay sao? Thêm mớ này tốn thêm lưu lượng của Storage... Bàn tiếp
Hệ thống vững chắc bởi user_login
Nhìn vào thì tôi đoán rằng ai cũng rõ vì sao lại sử dụng đúng không? Nó cho chúng ta biết? Thằng nào vào nhà mình, đi bằng cửa nào? đi bằng phương tiện gì? Lúc mấy giờ và để lại dấu vân tay hay không?
Điều đó rất tốt cho quá trình lấy dữ liệu để cung cấp cho police điều tra phá án rằng: Thằng nào vào nhà mình thăm dò, mà không phải là hợp pháp?
Mục đích chính của nó là vậy. Ngoài ra, có thể áp dụng cho việc user này ở lại nhà ta bao nhiêu thời gian
để đo lường hệ thống của chúng ta được user yêu mến hay không.
Bảng user_login
trong ví dụ đã bao gồm các trường khá tốt (user_id, user_code, ip_address, user_agent, timestamp, result, action)
Không đi sâu vào nhiều, nói chung mục đích của nó là vậy. Có lưu ý gì về cách triển khai không. Là CÓ...
Lưu ý khi triển khai login trong hệ thống
Hỏi: Có nên ghi đè lên record khi mỗi lần user login or logout hay không?
Theo bạn thì thế nào?
Vì sao hỏi vậy vì tôi đã gặp nhiều trường hợp trong đó là khi kẻ tấn công cố gắng đăng nhập vào một tài khoản bằng cách thử liên tiếp rất nhiều mật khẩu khác nhau (hoặc đôi khi là cả tên người dùng khác nhau) cho đến khi tìm ra thông tin đăng nhập đúng.
Đặc điểm nhận dạng chính là số lượng lớn các lần đăng nhập THẤT BẠI (result = 0) trong một khoảng thời gian ngắn. Vì vậy ở đây dữ liệu nên tổ chức ra sao khi lập trình. Giả sử tôi mô phỏng quá trình login
try {
// 2. Lấy thông tin đăng nhập từ body request
const { user_code, password } = req.body;
// 3. Validation: chỗ này check mạnh vào anh em...
if (!user_code || !password) {
return res.status(400).json({ success: false, message: 'Vui lòng cung cấp tên đăng nhập và mật khẩu.' });
}
// 4. Kết nối Database -> Đây chỉ là ví dụ cho các bạn nắm rõ -> không nên connect tạid dây..
connection = await getDbConnection();
// 5. Tìm người dùng dựa trên user_code
const [rows] = await connection.execute(
'SELECT user_id, user_code, password_hash, status FROM users WHERE user_code = ? LIMIT 1',
[user_code]
);
// --- Phân tích tình huống 1: Không tìm thấy người dùng ---
if (rows.length === 0) {
console.log(`Login attempt failed: User not found - ${user_code}`);
// Trả về lỗi chung chung để chống User Enumeration
return res.status(401).json({ success: false, message: 'Tên đăng nhập hoặc mật khẩu không chính xác.' });
}
const user = rows[0];
// --- Phân tích tình huống 2: Tài khoản bị khóa/không hoạt động ---
if (user.status !== 1) {
console.log(`Login attempt failed: Account inactive/locked - ${user_code}`);
return res.status(403).json({ success: false, message: 'Tài khoản của bạn đã bị khóa hoặc chưa được kích hoạt.' });
}
// 6. So sánh mật khẩu đã hash
const isPasswordMatch = await bcrypt.compare(password, user.password_hash);
// --- Phân tích tình huống 3: Sai mật khẩu ---
if (!isPasswordMatch) {
console.log(`Login attempt failed: Incorrect password - ${user_code}`);
// Ghi log thất bại ở đây nếu cần (ví dụ: vào bảng login_log)
// Trả về lỗi chung chung
return res.status(401).json({ success: false, message: 'Tên đăng nhập hoặc mật khẩu không chính xác.' });
}
// --- Phân tích tình huống 4: Đăng nhập thành công ---
console.log(`Login successful: ${user_code}`);
// Ghi log thành công ở đây nếu cần
// 7. Tạo JWT Token
const payload = {
userId: user.user_id,
userCode: user.user_code,
// Thêm các thông tin khác cần thiết (ví dụ: roles)
};
const secret = process.env.JWT_SECRET;
const options = { expiresIn: '1h' }; // Token hết hạn sau 1 giờ
const token = jwt.sign(payload, secret, options);
// 8. Trả về token cho client
return res.status(200).json({ success: true, token });
} catch (error) {
// --- Phân tích tình huống 5: Lỗi hệ thống/Database ---
console.error("Login API Error:", error);
return res.status(500).json({ success: false, message: 'Lỗi hệ thống, vui lòng thử lại sau.' });
} finally {
}
Nếu là "Ma" thì chúng ta không cần tiếp đón tử tế, hay thêm một phần và tống nó vào đó... Họ hay gọi là RiskUserPplicy
.
Tiếp nếu vào nhà rồi mà PHÁ quá thì cũng check lại, có lịch sử phá nhà thì thôi, cũng không tiếp đón...
Rất tường minh đúng không? Hãy xem thử dữ liệu user_login
sẽ được lưu trữ thế nào? Và nó nằm ở đâu thì hợp lý...
Và quan trọng vì sao lại phải tách brcypt
lại nằm cuối...
JWT - Login được thiết kế ra làm sao để tốt nhất
Ngoài ra, hệ thống sẽ có nhiều cách cho phép "JWT- Cho phép user sử dụng trên nhiều thiết bị thì sẽ quản lý JWT như thế nào?" tiếp đến sẽ xảy ra trường hợp nếu user
có thể truy cập trên device X
và trên device Y
thì bây giờ hành động của user trên X sẽ là -> logout
thì các thiết bị còn lại vẫn còn hoạt động đúng không?
Đúng vậy, vậy thì làm cách nào để đảm bảo hiệu suất NHANH và ĐƠN GIẢN. Thì tôi đã cung cấp giải pháp ở đây, các bạn có thể áp dụng nó, phương pháp này sẽ nói lên sự kình nghiệm của bạn: JWT Logout đơn giản - mà không làm ảnh hưởng đến các thiết bị khác cùng tài khoản.
Kịch bản thứ 2 bạn sẽ gặp đó là một trường hợp khi user sử dụng nhiều devices cho một tài khoản đó là, khi User muốn thay đổi password or muốn vô hiệu hoá tất cả các thiết bị đã login vào, cụ thể là thu hồi token jwt thì sao? Ở trên công ty mà lỡ quên logout tài khoản zalo
ở nhà thì TEO đời...
Vì vậy giải pháp tôi cũng đã đưa ra, nó đơn giản hiệu quả và thực tế các ứng dụng đã áp dụng rất tốt. Đây là phương pháp: Thu hồi JWT - Cách hiệu quả vô hiệu hoá devices (iPad, iMac) khi iPhone thay đổi password!
Tóm lại
Thông qua kế hoạch triển khai chi tiết của bài viết này, bạn có thể xây dựng một hệ thống xác thực đăng nhập an toàn, đáng tin cậy và hiệu suất cao hơn. Nên chọn giải pháp quản lý log nào phù hợp dựa trên nhu cầu kinh doanh thực tế và liên tục theo dõi các chỉ số bảo mật của hệ thống đó là mỗi cách bạn có thể xử lý nó.
Cảm ơn anh em đã đọc...