Hệ Thống Xác Thực Đăng Nhập
Thời gian gần đây, tôi đang bận rộn với dự án đại lý trò chơi Kiếm Cuồng.
Chính vì dự án này, chúng tôi đã sớm thành lập nhóm phát triển nền tảng. Tuy nhiên nhiều phần bắt đầu khá gấp gáp, ví dụ như việc tích hợp hệ thống xác thực đăng nhập người dùng.
Dù hiện có nhiều giao thức xác thực trưởng thành như Kerberos - giao thức nổi tiếng nhất, nhưng do thời gian gấp rút, tôi đã tạm thời thiết kế một giao thức đơn giản. Vì không phải là ứng dụng web, tôi không muốn dùng trực tiếp HTTPS để gửi tên đăng nhập và mật khẩu. Thay vào đó, dựa trên HTTP, tôi xây dựng một giao thức tùy chỉnh để hoạt động trên kênh truyền không an toàn. Thiết kế tạm thời này dĩ nhiên không đạt độ bảo mật cao, nhưng vẫn đủ dùng trong tình huống hiện tại.
Cấu trúc hệ thống bao gồm 3 thực thể:
C (Client người chơi) - G (Máy chủ trò chơi) - E (Nền tảng xác thực của chúng tôi)
Quy trình đăng nhập diễn ra như sau:
- Khi G phát hiện C cố gắng đăng nhập, G sẽ tạo một salt dạng một lần (có thể dùng mã hóa đối xứng nếu chọn thời gian làm khóa). Salt này được gửi về C.
- Khi nhận salt, C sẽ sử dụng mật khẩu của mình để thực hiện quy trình mã hóa và ký số:
- Đầu tiên, mật khẩu được băm MD5 thành một chuỗi.
- Lấy nửa đầu chuỗi làm khóa DES để mã hóa salt.
- Kết hợp kết quả mã hóa với nửa sau của chuỗi MD5 mật khẩu, sau đó băm MD5 lần nữa.
- Kết quả cuối cùng (gồm mã hóa và chữ ký) tạo thành secret dùng để xác thực.
- C gửi secret, tên đăng nhập và tên trò chơi cần đăng nhập đến E.
- E kiểm tra thông tin người dùng dựa trên tên đăng nhập, dùng mật khẩu lưu trữ để đảo ngược quy trình xác minh chữ ký. Nếu thành công, E giải mã salt. Nếu thất bại, quy trình kết thúc.
- E sử dụng salt đã giải mã, thêm vào ID người dùng. Dựa trên tên trò chơi, E truy xuất mật khẩu máy chủ trò chơi đã thỏa thuận trước đó, thực hiện quy trình mã hóa tương tự và gửi kết quả về cho C.
- C chuyển tiếp gói xác thực này đến G. G dùng mật khẩu máy chủ đã thỏa thuận để kiểm tra chữ ký và giải mã, đồng thời xác minh xem salt có khớp với salt đã gửi ở bước 1 hay không, từ đó xác định tính hợp pháp của người dùng.
Đặc điểm nổi bật của hệ thống:
- G và E không cần liên lạc trực tiếp, chỉ cần thống nhất mật khẩu chung. Đây có thể xem là phiên bản rút gọn của Kerberos, dù tồn tại một số lỗ hổng an ninh chưa trình bày ở đây.
- Ưu điểm lớn là G không cần duy trì trạng thái kết nối với C, chỉ cần kiểm tra cuối cùng gói xác thực do C gửi đến.
Triển khai thực tế:
Sau khi thiết kế xong, tôi đã viết các hàm xác thực cơ bản bằng C. Đồng nghiệp Mã Lão tích hợp vào web server của anh ấy để xây dựng máy chủ xác thực, đồng thời tiến hành kiểm tra hiệu năng đạt kết quả khả quan.
Tuy nhiên, đối tác sử dụng hệ điều hành Windows nên gặp khó khăn trong việc đồng bộ phát triển. Trong giai đoạn phát triển, họ không muốn phụ thuộc vào hệ thống người dùng của chúng tôi. Ngược lại, nền tảng của chúng tôi cũng khó triển khai trên Windows trong thời gian ngắn.
Tôi bèn nảy ra ý tưởng viết một máy chủ xác thực web đơn giản chạy trên Windows. Với tôi, ngôn ngữ Lua là công cụ phù hợp nhất. Chỉ cần dùng module LuaSocket có sẵn, toàn bộ máy chủ xác thực chỉ mất chưa đầy 100 dòng code. Dữ liệu tên đăng nhập và mật khẩu được cấu hình trực tiếp trong code thông qua một bảng (table), không cần cơ sở dữ liệu.
Quá trình triển khai:
- Ban đầu, tôi chỉ đơn giản bind/listen cổng web, xử lý từng yêu cầu xác thực theo mô hình chặn (blocking). Mục tiêu lúc đó chỉ là kiểm tra luồng hoạt động, sau đó sẽ thay thế bằng máy chủ chính thức của Mã Lão.
- Nhưng khi viết xong, tôi thấy không hỗ trợ xử lý song song nên quyết định viết lại.
- May mắn thay, cải tiến này đã giúp hệ thống chống chịu được đợt kiểm tra áp lực bất ngờ từ đối tác.
Sự cố đáng nhớ:
Trong đợt test, đối tác mời 600 thành viên bang hội thử nghiệm mà không thông báo trước. Vì họ dùng hệ thống người dùng riêng (phát hành tài khoản trực tiếp), nên thay vì liên hệ Mã Lão, họ đã dùng máy chủ xác thực tạm thời của tôi.
Mặc dù đã tối ưu xử lý song song, vẫn gặp phải một sự cố nhỏ vào buổi tối. Nguyên nhân là do ban đầu tôi cấu hình máy chủ bind địa chỉ mặc định là 127.0.0.1 (loopback). Đối tác sau đó sửa thành địa chỉ IP vật lý của máy chủ, nhưng do hệ thống có 2 đường mạng (điện thoại và网通) nên tồn tại 2 IP, dẫn đến nhiều người chơi không xác thực được. Cuối cùng, tôi phải sửa lại thành 0.0.0.0 để máy chủ lắng nghe trên mọi giao diện mạng.
Cập nhật ngày 12/12 - Vấn đề chống tấn công trung gian:
Để ngăn chặn kẻ xấu thay thế người chơi sau khi xác thực thành công, cần mã hóa toàn bộ giao tiếp giữa client và server. Trong giao thức SSL, điều này được thực hiện thông qua mã hóa bất đối xứng và chuỗi chứng thực CA. Tuy nhiên, hệ thống của chúng tôi có lợi thế đặc biệt: người dùng và server đã chia sẻ một bí mật chung (mật khẩu) ngay từ đầu.
Giải pháp:
- Sau khi xác thực thành công, E tạo một số ngẫu nhiên (nonce).
- E mã hóa nonce này bằng mật khẩu người dùng và mật khẩu máy chủ trò chơi để tạo ra 2 phiên bản mã hóa khác nhau, gửi về cho C.