Câu lệnh CREATE USER trong MySQL
Xem thửTình huống mở đầu
Ứng dụng ShopBook đang kết nối vào MySQL bằng tài khoản root, và mật khẩu root nằm trong file cấu hình mà 12 lập trình viên đều đọc được. Nhóm bảo mật yêu cầu: trước thứ Hai phải có tài khoản riêng cho ứng dụng, chỉ làm đúng việc của ứng dụng.
Bạn tạo tài khoản shop_rw chỉ trong hai phút. Bạn đăng nhập thử bằng đúng tên và mật khẩu đó, nhưng máy này vào được, còn lệnh gần giống ở máy kia lại báo Access denied. Tên đúng, mật khẩu đúng, vậy MySQL đang so sánh cái gì nữa?
Hãy đoán trước: một tài khoản MySQL gồm mấy thành phần? Cuối bài mình sẽ đối chiếu lại với câu trả lời của bạn.
Ôn nhanh bài trước: khi dùng pool kết nối trong Node.js, vì sao mỗi kết nối mượn ra phải được trả lại bằng release()?
Đáp án: để kết nối quay về pool cho request khác dùng. Quên trả thì pool cạn dần. Bài này nói về chuyện đứng sau kết nối đó: server nhận diện ai đang gõ cửa.
Mục tiêu bài học
Sau bài này bạn có thể:
- Viết lệnh
CREATE USERđúng dạng'user'@'host'kèm mật khẩu, và kiểm tra tài khoản vừa tạo bằngmysql.uservàSHOW GRANTS. - Dự đoán một kết nối (qua socket, qua
127.0.0.1, qua mạng) khớp tài khoản nào hoặc bị từ chối. - Giải thích vì sao tài khoản mới tạo chưa đọc được bảng nào cho đến khi có
GRANT. - Đọc lỗi 1396 và 1045, và phân biệt
CREATE USERvớiCREATE USER IF NOT EXISTS.
Bức tranh lớn
Xuyên suốt chương 8, MySQL vẫn là tòa nhà văn phòng ở chương trước. Tài khoản là thẻ ra vào ghi hai thứ: tên người và cổng mà người đó được phép đi vào. CREATE USER phát một thẻ trắng: thẻ qua được cổng nhưng chưa mở phòng nào. GRANT mới ghi thêm quyền vào các phòng. Dự án xuyên suốt là ShopBook, với hai tài khoản: shop_rw cho ứng dụng web và shop_ro cho báo cáo.
Hình dưới trả lời một câu hỏi: khi có người gõ cửa, server làm những bước nào trước khi cho vào?
Đang tải sơ đồ…
Cách đọc hình: đọc từ ô xanh dương trên cùng xuống. Ô cam là câu hỏi quan trọng nhất: sổ có tài khoản đúng cặp user và host không. Nhánh đỏ là bị từ chối. Nhánh tím và xanh lá chỉ xảy ra khi đã tìm ra tài khoản. Màu chỉ là tín hiệu phụ, chữ trong ô mới là nội dung chính.
Chỗ ẩn dụ không còn đúng: thẻ ra vào thật thường không đổi theo hướng bạn đến. Trong MySQL, cùng một người đến từ socket hay từ mạng có thể bị coi như hai người khác nhau.
Khái niệm cốt lõi
Bài này có bốn khái niệm.
1. Tài khoản là cặp user và host
Trong MySQL, tên tài khoản viết là 'user'@'host'. Hai tài khoản 'shop_rw'@'localhost' và 'shop_rw'@'%' là hai tài khoản khác nhau, có thể có mật khẩu và quyền khác nhau. Nếu bạn chỉ viết 'shop_rw', MySQL hiểu host là '%'.
2. Host quyết định ai được vào
Phần host nói tài khoản này chấp nhận kết nối đến từ đâu: localhost (kết nối qua socket Unix trên cùng máy), một địa chỉ IP như 127.0.0.1 hoặc 10.0.0.5, một mẫu như 10.0.0.%, hoặc % (mọi nơi). Server tra sổ bằng host mà nó thấy ở kết nối thật sự, không phải bằng cái bạn nghĩ.
3. IDENTIFIED BY và plugin xác thực
IDENTIFIED BY 'mật khẩu' đặt mật khẩu. Plugin xác thực là thuật toán kiểm tra mật khẩu. Trên MySQL 8.0, nếu bạn không chọn, plugin mặc định là caching_sha2_password.
4. CREATE USER không cấp quyền
Tài khoản mới chỉ có quyền USAGE, nghĩa là đăng nhập được nhưng không đọc hay ghi dữ liệu nào. Muốn có quyền phải GRANT. Cả hai lệnh có hiệu lực ngay, bạn không cần FLUSH PRIVILEGES (lệnh đó chỉ cần khi sửa trực tiếp bảng trong database mysql).
Ví dụ đầu tiên
Ví dụ nhỏ nhất: tạo tài khoản cho ứng dụng, đọc lại từ mysql.user, rồi xem quyền của nó. Database shop_demo của ShopBook đã có sẵn bảng books và customers.
CREATE USER 'shop_rw'@'localhost' IDENTIFIED BY 'ShopRw!2026';
SELECT user, host, plugin FROM mysql.user WHERE user = 'shop_rw';
SHOW GRANTS FOR 'shop_rw'@'localhost';
Dự đoán trước khi xem kết quả: cột plugin là gì, và SHOW GRANTS trả về mấy dòng?
Kết quả [Đã chạy: MySQL 8.0.44]:
+---------+-----------+-----------------------+
| user | host | plugin |
+---------+-----------+-----------------------+
| shop_rw | localhost | caching_sha2_password |
+---------+-----------+-----------------------+
+---------------------------------------------+
| Grants for shop_rw@localhost |
+---------------------------------------------+
| GRANT USAGE ON *.* TO `shop_rw`@`localhost` |
+---------------------------------------------+
| Dòng code | Máy làm gì | Vì sao viết như vậy |
|---|---|---|
CREATE USER 'shop_rw'@'localhost' | Thêm một tài khoản vào sổ với tên shop_rw và host localhost | Chỉ cho ứng dụng chạy cùng máy với server, hẹp hơn % |
IDENTIFIED BY 'ShopRw!2026' | Đặt mật khẩu, MySQL lưu dạng băm chứ không lưu chữ thật | Mật khẩu trong bài chỉ là giá trị lab, ngoài đời hãy lấy từ kho bí mật |
SELECT user, host, plugin FROM mysql.user | Đọc lại dòng của tài khoản trong bảng hệ thống | Xác nhận tài khoản có thật và plugin là caching_sha2_password |
SHOW GRANTS FOR 'shop_rw'@'localhost' | Liệt kê quyền của đúng tài khoản đó | GRANT USAGE ON *.* nghĩa là chưa có quyền gì |
Bây giờ thử đăng nhập bằng chính tài khoản này, theo hai đường khác nhau và một lần cố ý gõ sai mật khẩu. Lệnh mysql không có -u chạy bằng root của lab, mật khẩu root đã được đặt sẵn qua biến môi trường. Phần MYSQL_PWD=... đặt mật khẩu cho riêng lệnh đứng sau nó.
MYSQL_PWD='ShopRw!2026' mysql -t -u shop_rw -h localhost -e "SELECT CURRENT_USER() AS account" 2>&1
MYSQL_PWD='ShopRw!2026' mysql -t -u shop_rw -h 127.0.0.1 -e "SELECT CURRENT_USER() AS account" 2>&1
MYSQL_PWD='WrongPass!1' mysql -t -u shop_rw -h localhost -e "SELECT CURRENT_USER() AS account" 2>&1
mysql -t -e "SELECT @@skip_name_resolve AS skip_name_resolve" 2>&1
+-------------------+
| account |
+-------------------+
| shop_rw@localhost |
+-------------------+
ERROR 1045 (28000): Access denied for user 'shop_rw'@'127.0.0.1' (using password: YES)
ERROR 1045 (28000): Access denied for user 'shop_rw'@'localhost' (using password: YES)
+-------------------+
| skip_name_resolve |
+-------------------+
| 1 |
+-------------------+
| Dòng code | Máy làm gì | Vì sao viết như vậy |
|---|---|---|
-u shop_rw -h localhost | Đăng nhập qua socket Unix, host mà server thấy là localhost | Khớp đúng tài khoản 'shop_rw'@'localhost' |
-h 127.0.0.1 | Đăng nhập qua TCP, host mà server thấy là 127.0.0.1 | Cùng tên và mật khẩu nhưng không có tài khoản nào host 127.0.0.1 |
MYSQL_PWD='WrongPass!1' | Cố ý dùng mật khẩu sai | Để thấy lỗi 1045 có cùng dạng, dù nguyên nhân khác |
2>&1 | Gộp thông báo lỗi vào cùng dòng kết quả | Để lỗi hiện đúng thứ tự của từng lệnh |
@@skip_name_resolve | Cho biết server có bỏ qua việc đổi IP thành tên không | Giá trị 1 giải thích vì sao 127.0.0.1 không bị đổi thành localhost |
Trên server lab, skip_name_resolve là 1, nên server không đổi 127.0.0.1 thành tên localhost. [Suy luận] Trên một server không bật tùy chọn này, kết nối từ 127.0.0.1 thường được đổi thành localhost và có thể khớp tài khoản localhost, vì mình không có server như vậy để chạy thử. Điều chắc chắn là: đừng đoán, hãy kiểm tra bằng SELECT CURRENT_USER().
Xem từng bước
Animation dưới đây chạy lại hai lần đăng nhập đầu của lệnh trên. Hãy trả lời câu dự đoán trước khi bấm bước đầu tiên.
Đang tải Animation…
Bảng trace bằng chữ [Đã chạy: MySQL 8.0.44, chỉ có tài khoản 'shop_rw'@'localhost']:
| Lệnh đăng nhập | Host server thấy | Kết quả |
|---|---|---|
-u shop_rw -h localhost, mật khẩu đúng | localhost | Vào được |
-u shop_rw -h 127.0.0.1, mật khẩu đúng | 127.0.0.1 | Lỗi 1045, không có tài khoản khớp |
-u shop_rw -h localhost, mật khẩu sai | localhost | Lỗi 1045, có tài khoản nhưng sai mật khẩu |
Hai lỗi 1045 trông giống nhau nên rất khó đoán nguyên nhân. Chỉ cần nhìn kỹ phần trong ngoặc: 'shop_rw'@'127.0.0.1' cho biết host server đã thấy.
Bạn vừa làm được: tạo một tài khoản, kiểm tra nó, và dự đoán chính xác lần đăng nhập nào vào được, lần nào không.
Concept Check
Server có đúng một tài khoản: 'shop_rw'@'localhost'.
Câu 1. Bạn gõ mysql -u shop_rw -p -h 127.0.0.1 với mật khẩu đúng, trên server lab. Kết quả?
- A. Vào được, vì
127.0.0.1luôn chính làlocalhost. - B. Lỗi 1045, vì host
127.0.0.1không khớp tài khoản nào trong sổ. - C. Vào được nhưng không thấy bảng nào do thiếu quyền.
- D. Lỗi 1396, vì tài khoản đã tồn tại.
Câu 2. Bạn chạy CREATE USER 'shop_tmp' IDENTIFIED BY 'x' mà không ghi host. Tài khoản tạo ra là gì?
- A.
'shop_tmp'@'localhost', vì đó là host an toàn nhất. - B.
'shop_tmp'@'', vì host để trống nghĩa là chưa giới hạn. - C.
'shop_tmp'@'%', vì MySQL điền%khi bạn không ghi host. - D. Lệnh báo lỗi cú pháp vì bắt buộc phải có host.
Câu 3. Vừa CREATE USER xong, shop_rw chạy SELECT COUNT(*) FROM shop_demo.books. Kết quả?
- A. Trả về số hàng, vì mọi tài khoản đều đọc được database của mình.
- B. Trả về 0 hàng, vì chưa có dữ liệu nào cho tài khoản này.
- C. Lỗi 1142 SELECT command denied, vì tài khoản mới chưa có quyền gì.
- D. Lỗi 1045, vì cần
FLUSH PRIVILEGEStrước.
Đáp án và giải thích:
- Câu 1: B. A sai vì server lab tắt phân giải tên nên
127.0.0.1không bị đổi thànhlocalhost. C sai vì kết nối còn chưa tạo được. D sai vì 1396 là lỗi khi tạo tài khoản trùng. - Câu 2: C. Kết quả thật ở ví dụ thử phá bên dưới. A sai vì MySQL không tự chọn host hẹp. B sai vì host rỗng không phải cách viết đúng. D sai vì host có giá trị mặc định.
- Câu 3: C. A sai vì quyền không tự có theo tên database. B sai vì lỗi xảy ra trước khi đọc dữ liệu. D sai vì lỗi 1045 là lỗi đăng nhập, ở đây đăng nhập đã thành công.
Làm cùng mình
ShopBook cần thêm tài khoản chỉ đọc shop_ro cho nhóm báo cáo, kết nối từ mạng nội bộ nên host là %.
Bước 1. Vẽ ra giấy
Vẽ hai ô: một ô ghi tên đầy đủ của tài khoản, một ô ghi quyền mà nhóm báo cáo thật sự cần. Ghi dự đoán: nhóm báo cáo thử INSERT thì chuyện gì xảy ra?
Bước 2. Tạo tài khoản và cấp quyền
Điền chỗ trống trong đoạn dưới:
-- TODO 1: tạo tài khoản shop_ro ở host % với mật khẩu 'ShopRo!2026'
-- TODO 2: cấp quyền chỉ đọc SELECT trên mọi bảng của shop_demo cho tài khoản đó
-- TODO 3: xem lại quyền của shop_ro@%
Gợi ý: quyền trên mọi bảng của một database viết dạng shop_demo.*.
Bước 3. Đăng nhập và thử phá
Đăng nhập bằng shop_ro qua 127.0.0.1, đọc bảng books rồi thử INSERT một dòng. Hãy dự đoán trước: lệnh nào thành công, lệnh nào bị từ chối, và thông báo lỗi nhắc đến host nào? Đối chiếu ở mục Lời giải và đáp án.
Bước 4. Vẽ lại ra giấy
Ghi một câu: vì sao dùng shop_ro cho báo cáo an toàn hơn dùng root.
Thử phá nó đi
Ba lỗi người mới hay gặp [Đã chạy: MySQL 8.0.44]. Chữ trong cột thông báo lấy trực tiếp từ kết quả chạy.
Hai chuyện đầu: tạo trùng tài khoản, và tạo tài khoản mà không ghi host. Số dòng trong thông báo lỗi (ví dụ at line 26) tính cả các lệnh dựng sẵn database mà mình chạy trước, bạn có thể bỏ qua nó.
CREATE USER IF NOT EXISTS 'shop_rw'@'localhost' IDENTIFIED BY 'ShopRw!2026';
SHOW WARNINGS;
CREATE USER 'shop_rw'@'localhost' IDENTIFIED BY 'ShopRw!2026';
+-------+------+--------------------------------------------------------+
| Level | Code | Message |
+-------+------+--------------------------------------------------------+
| Note | 3163 | Authorization ID 'shop_rw'@'localhost' already exists. |
+-------+------+--------------------------------------------------------+
ERROR 1396 (HY000) at line 26: Operation CREATE USER failed for 'shop_rw'@'localhost'
CREATE USER 'shop_tmp' IDENTIFIED BY 'ShopTmp!2026';
SELECT user, host FROM mysql.user WHERE user = 'shop_tmp';
+----------+------+
| user | host |
+----------+------+
| shop_tmp | % |
+----------+------+
Lỗi thứ ba: tài khoản mới đọc dữ liệu khi chưa có GRANT, rồi đọc lại sau khi có.
MYSQL_PWD='ShopRw!2026' mysql -t -u shop_rw -e "SELECT COUNT(*) AS books FROM shop_demo.books" 2>&1
mysql -e "GRANT SELECT ON shop_demo.* TO 'shop_rw'@'localhost'" 2>&1
MYSQL_PWD='ShopRw!2026' mysql -t -u shop_rw -e "SELECT COUNT(*) AS books FROM shop_demo.books" 2>&1
ERROR 1142 (42000) at line 1: SELECT command denied to user 'shop_rw'@'localhost' for table 'books'
+-------+
| books |
+-------+
| 3 |
+-------+
| Dòng code | Máy làm gì | Vì sao viết như vậy |
|---|---|---|
CREATE USER IF NOT EXISTS ... | Thấy tài khoản đã có nên bỏ qua, chỉ để lại một ghi chú | SHOW WARNINGS ngay sau đó hiện ghi chú 3163 để bạn biết lệnh đã bị bỏ qua |
CREATE USER 'shop_rw'@'localhost' ... lần hai | Báo lỗi 1396 vì cặp user và host đã có | Lệnh không có IF NOT EXISTS thì trùng là lỗi |
CREATE USER 'shop_tmp' ... | Tạo tài khoản với host mặc định | Dòng mysql.user cho thấy host là % |
SELECT COUNT(*) trước và sau GRANT | Lần đầu bị từ chối, lần sau trả về số hàng | GRANT là thứ biến danh tính thành quyền đọc |
| Code lỗi | Thông báo lỗi | Nguyên nhân | Cách sửa |
|---|---|---|---|
CREATE USER cho tài khoản đã tồn tại | ERROR 1396 (HY000): Operation CREATE USER failed for 'shop_rw'@'localhost' | Cặp user và host đã có trong sổ | Dùng CREATE USER IF NOT EXISTS hoặc ALTER USER nếu muốn đổi |
SELECT trước khi có GRANT | ERROR 1142 (42000): SELECT command denied to user 'shop_rw'@'localhost' for table 'books' | Tài khoản mới chỉ có USAGE | GRANT SELECT ON shop_demo.* TO ... |
Đăng nhập qua 127.0.0.1 khi tài khoản là localhost | ERROR 1045 (28000): Access denied for user 'shop_rw'@'127.0.0.1' (using password: YES) | Host server thấy khác host của tài khoản | Tạo thêm tài khoản đúng host hoặc đổi cách kết nối |
Hiểu lầm quan trọng nhất của bài là coi tạo tài khoản và cấp quyền là một việc. Hình dưới đối chiếu mô hình sai với mô hình đúng.
Đang tải sơ đồ…
Cách đọc hình: hai chuỗi độc lập. Chuỗi đỏ là niềm tin sai, dẫn đến lỗi 1142 ngay trong ứng dụng. Chuỗi xanh là thực tế: tạo danh tính trước, rồi mới cấp quyền.
Tự tay làm
Dùng chung database shop_demo và hai tài khoản của dự án ShopBook.
Cơ bản
Bài 1. Điền bảng trace. Đầu vào: chỉ có tài khoản 'shop_rw'@'localhost'. Điền bảng cho ba lệnh đăng nhập sau (mật khẩu đúng, trừ dòng cuối):
| Lệnh | Vào được không | Thông báo hoặc kết quả |
|---|---|---|
-u shop_rw -h localhost | ? | ? |
-u shop_rw -h 127.0.0.1 | ? | ? |
-u shop_rw -h localhost, mật khẩu sai | ? | ? |
Tiêu chí đạt: điền đúng cả sáu ô. Gợi ý: nhìn host trong ngoặc của thông báo lỗi.
Vận dụng
Bài 2. Tài khoản chỉ cho TCP. Đầu vào: tạo 'shop_report'@'127.0.0.1' với mật khẩu Report!2026 và cấp SELECT trên shop_demo.*. Đăng nhập một lần qua -h 127.0.0.1 và một lần không ghi -h (socket), in CURRENT_USER(). Tiêu chí đạt: bạn dự đoán đúng lần nào vào được và lỗi báo host nào. Gợi ý: socket cho host là localhost.
Thử thách (tùy chọn)
Bài 3. Script tạo tài khoản chạy lại được. Đầu vào: tài khoản 'shop_rw'@'localhost' đã có mật khẩu ShopRw!2026. Chạy CREATE USER IF NOT EXISTS cùng tài khoản nhưng với mật khẩu Another!2027, rồi thử đăng nhập bằng cả hai mật khẩu. Tiêu chí đạt: bạn nói được mật khẩu nào còn dùng được và vì sao. Gợi ý: IF NOT EXISTS làm gì khi tài khoản đã có?
Tự kiểm tra
Trả lời bằng lời của bạn, không nhìn lại bài.
- (Mục tiêu 1, nhớ lại) Một tài khoản MySQL gồm hai thành phần nào? Viết dạng đầy đủ.
- (Mục tiêu 2, dự đoán) Chỉ có
'shop_rw'@'localhost'. Đăng nhập bằng-h 127.0.0.1trên server lab thì sao? - (Mục tiêu 3, giải thích) Vì sao tài khoản mới tạo đăng nhập được mà vẫn chưa đọc được bảng nào?
- (Mục tiêu 4, tìm lỗi) Lệnh
CREATE USER 'shop_rw'@'localhost' ...chạy lần hai báo lỗi gì, và sửa thế nào? - (Mục tiêu 4, so sánh)
IF NOT EXISTScó đổi mật khẩu nếu tài khoản đã có không?
Điều cần nhớ
- Tài khoản MySQL là cặp
'user'@'host'. Không ghi host thì host là%. - Server tra sổ bằng host thật của kết nối:
localhostcho socket, địa chỉ IP cho TCP. CREATE USERchỉ cấp quyềnUSAGE. Muốn đọc ghi phải cóGRANT.CREATE USER IF NOT EXISTSbỏ qua nếu tài khoản đã có và không đổi mật khẩu.
Cheat sheet:
| Muốn | Viết | Kết quả |
|---|---|---|
| Tạo tài khoản | CREATE USER 'u'@'h' IDENTIFIED BY '...' | Tài khoản có quyền USAGE |
| Tạo nếu chưa có | CREATE USER IF NOT EXISTS 'u'@'h' ... | Bỏ qua kèm ghi chú 3163 nếu đã có |
| Xem tài khoản | SELECT user, host, plugin FROM mysql.user | Danh sách tài khoản và plugin |
| Xem quyền | SHOW GRANTS FOR 'u'@'h' | Các dòng GRANT |
| Cấp quyền đọc | GRANT SELECT ON db.* TO 'u'@'h' | Đọc được mọi bảng của db |
Hình tóm tắt của bài là sơ đồ ở mục Bức tranh lớn.
Câu hỏi để dành cho ngày mai: nếu bạn tạo nhầm tài khoản shop_legacy và muốn xóa nó, bạn có chắc mình xóa đúng tài khoản không?
Đào sâu
Phần này dành cho bạn tò mò.
- Cơ chế bên trong. Server chọn tài khoản khớp theo thứ tự ưu tiên, host cụ thể trước host có ký tự đại diện. Bài
USER()sẽ chạy thật chuyện này với hai tài khoản trùng tên. - So sánh với lựa chọn khác. PostgreSQL không có host trong tên người dùng, nó quản lý nơi được kết nối bằng file
pg_hba.conf. Trong MySQL, host là một phần của danh tính. - Hiểu lầm nâng cao. Có sách ghi
FLUSH PRIVILEGESsau mọiGRANT. VớiCREATE USERvàGRANT, lệnh đó không cần, vì lần đăng nhập ngay sau trong bài này đã dùng được quyền mới mà không có nó. - Giới hạn của bài. Bài này chưa nói đến chính sách mật khẩu (
validate_password), khóa tài khoản, hay giới hạn tài nguyên. Chúng thuộc phần quản trị nâng cao. - Đọc thêm. MySQL 8.0 Reference Manual, trang
CREATE USERStatement: https://dev.mysql.com/doc/refman/8.0/en/create-user.html
Bài tiếp theo
Bài sau là DROP USER: xóa đúng tài khoản mà không làm hỏng những thứ phụ thuộc vào nó. Mini-dự án nối tiếp: dọn tài khoản shop_legacy của hệ thống báo cáo cũ.
Lời giải và đáp án
Làm cùng mình, Bước 2. Ba câu cần điền:
CREATE USER 'shop_ro'@'%' IDENTIFIED BY 'ShopRo!2026';
GRANT SELECT ON shop_demo.* TO 'shop_ro'@'%';
SHOW GRANTS FOR 'shop_ro'@'%';
+------------------------------------------------+
| Grants for shop_ro@% |
+------------------------------------------------+
| GRANT USAGE ON *.* TO `shop_ro`@`%` |
| GRANT SELECT ON `shop_demo`.* TO `shop_ro`@`%` |
+------------------------------------------------+
Làm cùng mình, Bước 3. Đăng nhập qua 127.0.0.1 rồi đọc và ghi:
MYSQL_PWD='ShopRo!2026' mysql -t -u shop_ro -h 127.0.0.1 -e "SELECT title, price FROM shop_demo.books ORDER BY id" 2>&1
MYSQL_PWD='ShopRo!2026' mysql -t -u shop_ro -h 127.0.0.1 -e "INSERT INTO shop_demo.books VALUES (4, 'Extra', 1)" 2>&1
+-------------------+--------+
| title | price |
+-------------------+--------+
| SQL Basics | 120000 |
| MySQL in Practice | 180000 |
| Data Modeling | 150000 |
+-------------------+--------+
ERROR 1142 (42000) at line 1: INSERT command denied to user 'shop_ro'@'127.0.0.1' for table 'books'
| Dòng code | Máy làm gì | Vì sao viết như vậy |
|---|---|---|
CREATE USER 'shop_ro'@'%' ... | Tạo tài khoản cho mọi host | Nhóm báo cáo kết nối từ mạng nội bộ |
GRANT SELECT ON shop_demo.* ... | Cho đọc mọi bảng của shop_demo | Quyền hẹp nhất đủ cho báo cáo |
SHOW GRANTS FOR ... | In hai dòng: USAGE và SELECT | Xác nhận tài khoản chỉ có quyền đọc |
Đọc thành công. INSERT bị từ chối vì shop_ro chỉ có SELECT. Thông báo lỗi nhắc đến host 127.0.0.1, vì đó là host server thấy, dù tài khoản khớp là shop_ro@%.
Bài 1.
| Lệnh | Vào được không | Thông báo hoặc kết quả |
|---|---|---|
-u shop_rw -h localhost | Có | shop_rw@localhost |
-u shop_rw -h 127.0.0.1 | Không | Lỗi 1045, host trong ngoặc là 127.0.0.1 |
-u shop_rw -h localhost, mật khẩu sai | Không | Lỗi 1045, host trong ngoặc là localhost |
Bài 2.
MYSQL_PWD='Report!2026' mysql -t -u shop_report -h 127.0.0.1 -e "SELECT CURRENT_USER() AS account" 2>&1
MYSQL_PWD='Report!2026' mysql -t -u shop_report -e "SELECT CURRENT_USER() AS account" 2>&1
+-----------------------+
| account |
+-----------------------+
| shop_report@127.0.0.1 |
+-----------------------+
ERROR 1045 (28000): Access denied for user 'shop_report'@'localhost' (using password: YES)
Qua -h 127.0.0.1 vào được và CURRENT_USER() là shop_report@127.0.0.1. Không ghi -h thì dùng socket, host là localhost nên không khớp tài khoản nào.
Bài 3.
mysql -e "CREATE USER IF NOT EXISTS 'shop_rw'@'localhost' IDENTIFIED BY 'Another!2027'" 2>&1
MYSQL_PWD='ShopRw!2026' mysql -t -u shop_rw -e "SELECT 'old password still works' AS result" 2>&1
MYSQL_PWD='Another!2027' mysql -t -u shop_rw -e "SELECT 'new password works' AS result" 2>&1
+--------------------------+
| result |
+--------------------------+
| old password still works |
+--------------------------+
ERROR 1045 (28000): Access denied for user 'shop_rw'@'localhost' (using password: YES)
| Dòng code | Máy làm gì | Vì sao viết như vậy |
|---|---|---|
CREATE USER IF NOT EXISTS ... 'Another!2027' | Thấy tài khoản đã có nên bỏ qua toàn bộ lệnh | Nó chỉ đảm bảo tài khoản tồn tại, không cập nhật gì |
MYSQL_PWD='ShopRw!2026' | Thử mật khẩu cũ, vẫn vào được | Chứng minh mật khẩu không bị đổi |
MYSQL_PWD='Another!2027' | Thử mật khẩu mới, bị từ chối | Muốn đổi mật khẩu phải dùng ALTER USER, bài cuối chương |
Tự kiểm tra.
- User và host, viết
'user'@'host', ví dụ'shop_rw'@'localhost'. - Bị từ chối với lỗi 1045, vì host server thấy là
127.0.0.1, không khớplocalhost. - Vì
CREATE USERchỉ phát danh tính với quyềnUSAGE. Muốn đọc bảng phảiGRANT. - Lỗi 1396, thông báo
Operation CREATE USER failed. Sửa bằngCREATE USER IF NOT EXISTS, hoặcALTER USERnếu muốn thay đổi tài khoản. - Không. Nó bỏ qua cả lệnh nếu tài khoản đã có, mật khẩu cũ vẫn dùng được.