摘要:本文詳細敘述了MySQL權限系統的原理,包括MySQL存取證實的完整過程。另外一個重要的知識是授權表中的user和host字段。了解MySQL服務器用戶匹配的順序以及身份驗證的過程,有利於你配置一個安全的系統。
MySQL有一套先進的但非標准的安全/授權系統,掌握其授權機制是開始操作MySQL數據庫必須要走的第一步,對於一個熟悉SQL基本操作的人來說,也是MySQL所有的知識中比較難以理解的一個部分。本節通過揭開其授權系統的運作機制,希望大家能夠可以更好地操作和使用這個優秀的數據庫系統。
MySQL的安全系統是很靈活的,它允許你以多種不同方式設置用戶權限。一般地,你可使用標准的SQL語句GRANT和REVOKE語句做,他們為你修改控制客戶訪問的授權表,然而,你可能由一個不支持這些語句的老版本的MySQL(在3.22.11之前這些語句不起作用),或者你發覺用戶權限看起來不是以你想要的方式工作。對於這種情況,了解MySQL授權表的結構和服務器如何利用它們決定訪問權限是有幫助的,這樣的了解允許你通過直接修改授權表增加、刪除或修改用戶權限,它也允許你在檢查這些表時診斷權限問題。
MySQL授權表的結構
通過網絡連接服務器的客戶對MySQL數據庫的訪問由授權表內容來控制。這些表位於mysql數據庫中,並在第一次安裝MySQL的過程中初始化(運行mysql_install_db腳本)。授權表共有5個表:user、db、host、tables_priv和columns_priv。
授權表user、db和host的結構和作用
表1 授權表user、db和host的結構
授權表的內容有如下用途:
·user表
user表列出可以連接服務器的用戶及其口令,並且它指定他們有哪種全局(超級用戶)權限。在user表啟用的任何權限均是全局權限,並適用於所有數據庫。例如,如果你啟用了DELETE權限,在這裡列出的用戶可以從任何表中刪除記錄,所以在你這樣做之前要認真考慮。
·db表
db表列出數據庫,而用戶有權限訪問它們。在這裡指定的權限適用於一個數據庫中的所有表。
·host表
host表與db表結合使用在一個較好層次上控制特定主機對數據庫的訪問權限,這可能比單獨使用db好些。這個表不受GRANT和REVOKE語句的影響,所以,你可能發覺你根本不是用它。
授權表tables_priv和columns_priv的結構和作用
表2 授權表tables_priv和columns_priv的結構
授權表tables_priv
授權表columns_priv
作用域列
Host
Host
Db
Db
User
User
Table_name
Table_name
Column_name
權限列
Table_priv
Column_priv
其他列
Timestamp
Timestamp
Grantor
MySQL沒有rows_priv表,因為它不提供記錄級權限,例如,你不能限制用戶於表中包含特定列值的行。如果你確實需要這種能力,你必須用應用編程來提供。如果你想執行建議的記錄級鎖定,你可用GET_LOCK()函數做到。
授權表的內容有如下用途:
·tables_priv表
tables_priv表指定表級權限,在這裡指定的一個權限適用於一個表的所有列。
·columns_priv表
columns_priv表指定列級權限。這裡指定的權限適用於一個表的特定列。
tables_priv和columns_priv表在MySQL 3.22.11版引進(與GRANT語句同時)。如果你有較早版本的MySQL,你的mysql數據庫將只有user、db和host表。如果你從老版本升級到3.22.11或更新,而沒有tables_priv和columns_priv表,運行mysql_fix_privileges_tables 腳本創建它們。
用戶的權限
權限信息用user、db、host、tables_priv和columns_priv表被存儲在mysql數據庫中(即在名為mysql的數據庫中)。在MySQL啟動時和在7.5權限修改何時生效所說的情況時,服務器讀入這些數據庫表內容。
數據庫和表的權限
下列權限運用於數據庫和表上的操作。
·SELECT
允許你使用SELECT語句從表中檢索數據。SELECT語句只有在他們真正從一個表中檢索行是才需要select權限,你可以執行某個SELECT語句,甚至沒有任何到服務器上的數據庫裡的存取任何東西的許可。例如,你可使用mysql客戶作為一個簡單的計算器:
mysql> SELECT 1+1;
mysql> SELECT PI()*2;
·UPDATE
允許你修改表中的已有的記錄。
·INSERT
允許在表中插入記錄
·DELETE
允許你從表中刪除現有記錄。
·ALTER
允許你使用ALTER TABLE語句,這其實是一個簡單的第一級權限,你必須由其他權限,這看你想對數據庫實施什麼操作。
·CREATE
允許你創建數據庫和表,但不允許創建索引。
·DROP
允許你刪除(拋棄)數據庫和表,但不允許刪除索引。
注意:如果你將mysql數據庫的drop權限授予一個用戶,該用戶能拋棄存儲了MySQL存取權限的數據庫!
·INDEX
允許你創建並刪除索引。
·REFERENCES
目前不用。
MySQL的管理權限
下列權限運用於控制服務器或用戶授權能力的操作的管理性操作。
·FILE
允許你告訴服務器讀或寫服務器主機上的文件。該權限不應該隨便授予,它很危險,見“回避授權表風險”。服務器確實較謹慎地保持在一定范圍內使用該權限。你只能讀任何人都能讀的文件。你正在寫的文件必須不是現存的文件,這防止你迫使服務器重寫重要文件,如/etc/passwd或屬於別人的數據庫的數據目錄。
如果你授權FILE權限,確保你不以UNIX的root用戶運行服務器,因為root可在文件系統的任何地方創建新文件。如果你以一個非特權用戶運行服務器,服務器只能在給用戶能訪問的目錄中創建文件。
·GRANT
允許你將你自己的權限授予別人,包括GRANT。
·PROCESS
允許你通過使用SHOW PROCESS語句或mysqladmin process命令查看服務器內正在運行的線程(進程)的信息。這個權限也允許你用KILL語句或mysqladmin kill命令殺死線程。
你總是能看到或殺死你自己的線程。PROCESS權限賦予你對任何線程做這些事情的能力。
·RELOAD
允許你執行大量的服務器管理操作。你可以發出FLUSH語句,你也能指性mysqladmin的reload、refresh、flush-hosts、flush-logs、flush-privileges和flush-tables等命令。
·SHUTDOWN
允許你用mysqladmin shutdown關閉服務器。
在user、db和host表中,每一個權限以一個單獨的列指定。這些列全部聲明為一個ENUM("N","Y")類型,所以每個權的缺省值是 “N”。在tables_priv和columns_priv中的權限以一個SET表示,它允許權限用一個單個列以任何組合指定。這兩個表比其他三個表更新,這就是為什麼它們使用更有效的表示方式的原因。(有可能在未來,user、db和host表也用一個SET類型表示。)
授權表列的內容
作用域列內容
一些范圍列要求文字值,但它們大多數允許通配符或其他特殊值。
表3 作用域列的類型
字段名
類型
Host
CHAR(60)
User
CHAR(16)
Password
CHAR(16)
Db
CHAR(64) (tables_priv和columns_priv表為CHAR(60))
·Host
一個Host列值可以是一個主機名或一個IP地址。值localhost意味著本地主機,但它只在你用一個localhost主機名時才匹配,而不是你在使用主機名時。假如你的本地主機名是pit.snake.net並且在user表中有對你的兩條記錄,一個有一個Host值或localhost,而另一個有pit.snake.net,有localhost的記錄將只當你連接localhost時匹配,其他在只在連接pit.snake.net時才匹配。如果你想讓客戶能以兩種方式連接,你需要在user表中有兩條記錄。
你也可以用通配符指定Host值。可以使用SQL的模式字符“%”和“_”並具有當你在一個查詢中使用LIKE算符同樣的含義(不允許regex算符)。 SQL模式字符都能用於主機名和IP地址。如%wisc.edu匹配任何wisc.edu域內的主機,而%.edu匹配任何教育學院的主機。類似地,192.168.%匹配任何在192.168 B類子網的主機,而192.168.3.%匹配任何在192.168.3 C類子網的主機。
%值匹配所有主機,並可用於允許一個用戶從任何地方連接。一個空白的Host值等同於%。(例外:在db表中,一個空白Host值含義是“進一步檢查host表”,該過程在“查詢訪問驗證”中介紹。)
從MySQL 3.23起,你也可以指定帶一個表明那些為用於網絡地址的網絡掩碼的IP地址,如192.168.128.0/17指定一個17位網絡地址並匹配其IP地址是192.168.128前17位的任何主機。
·User
用戶名必須是文字的或空白。一個空白值匹配任何用戶。%作為一個User值不意味著空白,相反它匹配一個字面上的%名字,這可能不是你想要的。
當一個到來的連接通過user表被驗證而匹配的記錄包含一個空白的User值,客戶被認為是一個匿名用戶。
·Password
口令值可以是空或非空,不允許用通配符。一個空口令不意味著匹配任何口令,它意味著用戶必須不指定口令。
口令以一個加密過的值存儲,不是一個字面上的文本。如果你在Password列中存儲一個照字面上的口令,用戶將不能連接!GRANT語句和 mysqladmin password命令為你自動加密口令,但是如果你用諸如INSERT、REPLACE、UPDATE或SET PASSWORD等命令,一定要用PASSWORD("new_password")而不是簡單的"new_password"來指定口令。
·Db
在columns_priv和tables_priv表中,Db值必須是真正的數據庫名(照字面上),不允許模式和空白。在db和host中,Db值可以以字面意義指定或使用SQL模式字符'%'或'_'指定一個通配符。一個'%'或空白匹配任何數據庫。
·Table_name,Column_name
這些列中的值必須是照字面意思的表或列名,不允許模式和空白。
某些范圍列被服務器視為大小寫敏感的,其余不是。這些原則總結在下表中。特別注意Table_name值總是被看作大小寫敏感的,即使在查詢中的表名的大小寫敏感性對待視服務器運行的主機的文件系統而定(UNIX下是大小寫敏感,而Windows不是)。
某些作用域列被服務器視為大小寫敏感的,其余不是。這些原則總結在下表中。特別注意Table_name值總是被看作大小寫敏感的,即使在查詢中的表名的大小寫敏感性對待視服務器運行的主機的文件系統而定(UNIX下是大小寫敏感,而Windows不是)。
表4 作用域列的大小寫敏感性
列
大小寫敏感性
Host
No
User
Yes
Password
Yes
Db
Yes
Table_name
Yes
Column_name
No
授權表User、Db和Host的權限列的內容
在user、db和host表中,所有權限字段被聲明為ENUM('N','Y')--每一個都可有值'N'或'Y',並且缺省值是'N'.
授權表tables_priv和columns_priv的權限列的內容
在tables_priv和columns_priv表中,權限字段被聲明為SET字段:
表5 授權表tables_priv和columns_priv的權限列的類型
表名
字段名
可能的集合成員
tables_priv
Table_priv
'Select', 'Insert', 'Update', 'Delete', 'Create', 'Drop', 'Grant', 'References', 'Index', 'Alter'
tables_priv
Column_priv
'Select', 'Insert', 'Update', 'References'
columns_priv
Column_priv
'Select', 'Insert', 'Update', 'References'
MySQL權限系統的工作原理
權限系統工作的一般過程
MySQL權限系統保證所有的用戶可以嚴格地做他們假定被允許做的事情。當你連接一個MySQL服務器時, 你的身份由你從那連接的主機和你指定的用戶名來決定,系統根據你的身份和你想做什麼來授予權限。
MySQL在認定身份中考慮你的主機名和用戶名字,是因為有很小的原因假定一個給定的用戶在因特網上屬於同一個人。例如,用戶從 whitehouse.gov連接的bill不必和從mosoft.com連接bill是同一個人。 MySQL通過允許你區分在不同的主機上碰巧有同樣名字用戶來處理它:你可以對從whitehouse.gov連接授與bill一個權限集,而為從 microsoft.com的連接授予一個不同的權限集。
MySQL存取控制包含2個階段:
·階段1:服務器檢查你是否允許連接。
·階段2:假定你能連接,服務器檢查你發出的每個請求。看你是否有足夠的權限實施它。例如,如果你從數據庫中一個表精選(select)行或從數據庫拋棄一個表,服務器確定你對表有select權限或對數據庫有drop權限。
服務器在存取控制的兩個階段使用在mysql的數據庫中的user、db和host表對存取控制的第二階段(請求證實),如果請求涉及表,服務器可以另外參考tables_priv和columns_priv表。
簡單地說,服務器使用這樣的授權表:
·user表范圍字段決定是否允許或拒絕到來的連接。對於允許的連接,權限字段指出用戶的全局(超級用戶)權限。
·db和host表一起使用:
db表范圍字段決定用戶能從哪個主機存取哪個數據庫。權限字段決定允許哪個操作。
當你想要一個給定的db條目應用於若干主機時,host表作為db表的擴展被使用。例如,如果你想要一個用戶能在你的網絡從若干主機使用一個數據庫,在用戶的db表的Host條目設為空值,然後將那些主機的每一個移入host表。
tables_priv和columns_priv表類似於db表,但是更精致:他們在表和列級應用而非在數據庫級。
注意管理權限(reload, shutdown, 等等)僅在user表中被指定。這是因為管理性操作是服務器本身的操作並且不是特定數據庫,因此沒有理由在其他授權表中列出這樣的權限。事實上,只需要請教user表來決定你是否執行一個管理操作。
file權限也僅在user表中指定。它不是管理性權限,但你讀或謝在服務器主機上的文件的的能力獨立於你正在存取的數據庫。
當mysqld服務器啟動時,讀取一次授權表內容。對授權表的更改生效在7.5權限修改何時生效。
一個有用的診斷工具是mysqlaccess腳本,由Carlier Yves 提供給MySQL分發。使用--help選項調用mysqlaccess查明它怎樣工作。注意:mysqlaccess僅用user、db和host表僅檢查存取。它不檢查表或列級權限。
存取控制, 階段1:連接證實
當你試圖聯接一個MySQL服務器時,服務器基於你的身份和你是否能通過供應正確的口令驗證身份來接受或拒絕連接。如果不是,服務器完全具結你的存取,否則,服務器接受連接,然後進入階段2並且等待請求。
你的身份基於2個信息:
·你從那個主機連接
·你的MySQL用戶名
身份檢查使用3個user表(Host, User和Password)范圍字段執行。服務器只有在一個user表條目匹配你的主機名和用戶名並且你提供了正確的口令時才接受連接。
在user表范圍字段可以如下被指定:
一個Host值可以是主機名或一個IP數字,或'localhost'指出本地主機。
你可以在Host字段裡使用通配符字符“%”和“_”。
一個Host值'%'匹配任何主機名,一個空白Host值等價於'%'。注意這些值匹配能創建一個連接到你的服務器的任何主機!
通配符字符在User字段中不允許,但是你能指定空白的值,它匹配任何名字。如果user表匹配到來的連接的條目有一個空白的用戶名,用戶被認為是匿名用戶 (沒有名字的用戶),而非客戶實際指定的名字。這意味著一個空白的用戶名被用於在連接期間的進一步的存取檢查(即,在階段2期間)。
Password字段可以是空白的。這不意味著匹配任何口令,它意味著用戶必須不指定一個口令進行連接。
非空白Password值代表加密的口令。 MySQL不以任何人可以看的純文本格式存儲口令,相反,正在試圖聯接的一個用戶提供的口令被加密(使用PASSWORD()函數),並且與存儲了user表中的已經加密的版本比較。如果他們匹配,口令是正確的。
下面的例子顯示出各種user表中Host和User條目的值的組合如何應用於到來的連接:
表6 Host和User條目的值的組合
Host值
User值
被條目匹配的連接
'host.domain.cn'
'Gwen'
Gwen, 從host.domain.cn連接
'host.domain.cn'
''
任何用戶, 從host.domain.cn連接,
'%'
'Gwen'
Gwen, 從任何主機連接
'%'
''
任何用戶, 從任何主機連接
'%.loc.gov'
'Gwen'
Gwen, 從在loc.gov域的任何主機連接
'x.y.%'
'Gwen'
Gwen, 從x.y.net、x.y.com,x.y.edu等聯接。(這或許無用)
'144.155.166.177'
'Gwen'
Gwen, 從有144.155.166.177 IP 地址的主機連接
'144.155.166.%'
'Gwen'
Gwen, 從144.
既然你能在Host字段使用IP通配符值(例如,'144.155.166.%'匹配在一個子網上的每台主機),有可能某人可能企圖探究這種能力,通過命名一台主機為144.155.166.somewhere.com。為了阻止這樣的企圖,MySQL不允許匹配以數字和一個點起始的主機名,這樣,如果你用一個命名為類似1.2.foo.com的主機,它的名字決不會匹配授權表中Host列。只有一個IP數字能匹配IP通配符值。
一個到來的連接可以被在user表中的超過一個條目匹配。例如,一個由Gwen從host.domain.cn的連接匹配多個條目如上所述。如果超過一個匹配,服務器怎麼選擇使用哪個條目呢?服務器在啟動時讀入user表後通過排序來解決這個問題,然後當一個用戶試圖連接時,以排序的順序浏覽條目,第一個匹配的條目被使用。
MySQL服務器按一種特定方式排序符授權表中的記錄,然後通過按序浏覽記錄匹配到來的連接。找到的第一個匹配決定了被使用的記錄。理解MySQL使用的排序順序很重要,特別是對user表。
當服務器讀取user表內容時,它根據在Host和User列中的值排序記錄,Host值起決定作用(相同的Host值排在一起,然後再根據 User值排序)。然而,排序不是典序(按詞排序),它只是部分是。要牢記的是字面上的詞優先於模式。這意味著如果你正從client.your.net 連接服務器而Host有client.your.net和%.your.net兩個值,則第一個先選。類似地,%.your.net優先於%.net,然後是%。IP地址的匹配也是這樣的。總之一句話,越具體越優先。
user表排序工作如下,假定user表看起來像這樣:
+-----------+----------+-
| Host | User | ...
+-----------+----------+-
| % | root | ...
| % | jerry | ...
| localhost | root | ...
| localhost | | ...
+-----------+----------+-
當服務器在表中讀取時,它以最特定的Host值為先的次序排列('%'在Host列裡意味著“任何主機”並且是最不特定的)。有相同Host值的條目以最特定的User值為先的次序排列(一個空白User值意味著“任何用戶”並且是最不特定的)。最終排序的user表看起來像這樣:
+-----------+----------+-
| Host | User | ...
+-----------+----------+-
| localhost | root | ...
| localhost | | ...
| % | jerry | ...
| % | root | ...
+-----------+----------+-
當一個連接被嘗試時,服務器浏覽排序的條目並使用找到的第一個匹配。對於由jeffrey從localhost的一個連接,在Host列的 'localhost'條目首先匹配。那些有空白用戶名的條目匹配連接的主機名和用戶名。('%'/'jeffrey'條目也將匹配,但是它不是在表中的第一匹配。)
這是另外一個例子。假定user表看起來像這樣:
+----------------+----------+-
| Host | User | ...
+----------------+----------+-
| % | jerry | ...
| host.domain.cn | | ...
+----------------+----------+-
排序後的表看起來像這樣:
+----------------+----------+-
| Host | User | ...
+----------------+----------+-
| host.domain.cn | | ...
| % | jerry | ...
+----------------+----------+-
一個由jerry從host.domain.cn的連接被第一個條目匹配,而一個由jerry從whitehouse.gov的連接被第二個匹配。
普遍的誤解是認為,對一個給定的用戶名,當服務器試圖對連接尋找匹配時,明確命名那個用戶的所有條目將首先被使用。這明顯不是事實。先前的例子說明了這點,在那裡一個由jerry從host.domain.cn的連接沒被包含'jerry'作為User字段值的條目匹配,但是由沒有用戶名的題目匹配!
如果你有服務器連接的問題,打印出user表並且手工排序它看看第一個匹配在哪兒進行。
存取控制,階段2:請求證實
一旦你建立了一個連接,服務器進入階段2。對在此連接上進來的每個請求,服務器檢查你是否有足夠的權限來執行它,它基於你希望執行的操作類型。這正是在授權表中的權限字段發揮作用的地方。這些權限可以來子user、db、host、tables_priv或columns_priv表的任何一個。授權表用GRANT和REVOKE命令操作。(你可以發覺參考第七章 權限系統怎樣工作很有幫助,它列出了在每個權限表中呈現的字段。)
user表在一個全局基礎上授予賦予你的權限,該權限不管當前的數據庫是什麼均適用。例如,如果user表授予你delete權限,你可以刪除在服務器主機上從任何數據庫刪除行!換句話說,user表權限是超級用戶權限。只把user表的權限授予超級用戶如服務器或數據庫主管是明智的。對其他用戶,你應該把在user表中的權限設成'N'並且僅在一個特定數據庫的基礎上授權, 使用db和host表。
db和host表授予數據庫特定的權限。在范圍字段的值可以如下被指定:
通配符字符“%”和“_”可被用於兩個表的Host和Db字段。
在db表的'%'Host值意味著“任何主機”,在db表中一個空白Host值意味著“對進一步的信息咨詢host表”。
在host表的一個'%'或空白Host值意味著“任何主機”。
在兩個表中的一個'%'或空白Db值意味著“任何數據庫”。
在兩個表中的一個空白User值匹配匿名用戶。
db和host表在服務器啟動時被讀取和排序(同時它讀user表)。db表在Host、Db和User范圍字段上排序,並且host表在Host 和Db范圍字段上排序。對於user表,排序首先放置最特定的值然後最後最不特定的值,並且當服務器尋找匹配入條目時,它使用它找到的第一個匹配。
tables_priv和columns_priv表授予表和列特定的權限。在范圍字段的值可以如下被指定:
通配符“%”和“_”可用在使用在兩個表的Host字段。
在兩個表中的一個'%'或空白Host意味著“任何主機”。
在兩個表中的Db、Table_name和Column_name字段不能包含通配符或空白。
tables_priv和columns_priv表在Host、Db和User字段上被排序。這類似於db表的排序,盡管因為只有Host字段可以包含通配符,但排序更簡單。
請求證實進程在下面描述。(如果你熟悉存取檢查的源代碼,你會注意到這裡的描述與在代碼使用的算法略有不同。描述等價於代碼實際做的東西;它只是不同於使解釋更簡單。)
對管理請求(shutdown、reload等等),服務器僅檢查user表條目,因為那是唯一指定管理權限的表。如果條目許可請求的操作,存取被授權了,否則拒絕。例如,如果你想要執行mysqladmin shutdown,但是你的user表條目沒有為你授予shutdown權限,存取甚至不用檢查db或host表就被拒絕。(因為他們不包含 Shutdown_priv行列,沒有這樣做的必要。)
對數據庫有關的請求(insert、update等等),服務器首先通過查找user表條目來檢查用戶的全局(超級用戶)權限。如果條目允許請求的操作,存取被授權。如果在user表中全局權限不夠,服務器通過檢查db和host表確定特定的用戶數據庫權限:
服務器在db表的Host、Db和User字段上查找一個匹配。 Host和User對應連接用戶的主機名和MySQL用戶名。Db字段對應用戶想要存取的數據庫。如果沒有Host和User的條目,存取被拒絕。
如果db表中的條目