<a href="#_labelContents">本文所涉及的内容(Contents)</a>
<a href="#_labelContexts">背景(Contexts)</a>
<a href="#_labelSQLCodes">實作代碼(SQL Codes)</a>
<a href="#_labelAddon">補充說明(Addon)</a>
<a href="#_labelQuestions">疑問(Questions)</a>
<a href="#_labelReferences">參考文獻(References)</a>
在MySQL的mysql.User表儲存了登陸使用者的權限資訊,Host和User字段則是關于登陸IP的限制。但是在SQL Server沒有這樣一個表,那SQL Server有什麼辦法可以實作類似的安全控制的功能呢?
登陸觸發器能為我們解決什麼問題呢?本文将為你講述5種運用登陸觸發器的場景:
1) 限制某登入名(比如sa)隻能在本機或者指定的IP中登陸;
2) 限制伺服器角色(比如sysadmin)隻能在本機或者指定的IP中登陸;
3) 限制某登入名(比如sa)隻能某時間段内登陸;
4) 限制登入名與IP的對應關系,支援多對多關系;
5) 限制某登入名可以在某IP段登入(比如192.168.1.*),如下圖;

(一) 我機器的IP是:192.168.1.48,首先我在資料庫建立一個test帳号,設定密碼為123,接着建立登陸觸發器:tr_connection_limit,它會在使用者登陸的時候觸發,通過EVENTDATA()函數傳回的用戶端的IP,使用ORIGINAL_LOGIN()函數傳回的登陸名,對IP和登入名進行判斷。
當登入名是test的時候,如果登陸的IP位址本地<local machine>或者是192.168.1.50,192.168.1.120就允許登陸,在這之外其它情況的登陸将復原。登陸失敗的如Figure1所示。
(Figure1:test使用者登陸錯誤資訊)
我在一台IP為192.168.1.115的機器上使用test登陸名登陸我的SQL Server資料庫,因為這個IP不在允許的IP清單中,是以出現了Figure1的錯誤資訊。我再使用一台IP為192.168.1.120的機器登陸我的SQL Server資料庫,成功登陸了,使用Script2傳回登陸的資訊;如Figure2,請看session_id為58的記錄:登陸名為test,登陸的IP為192.168.1.120。
(Figure2:使用者登陸資訊)
(二) 有些時候,你資料庫可能有許多個登陸帳号,而你更希望的是限制IP,而登陸名跟IP并沒有直接的關聯,那這應該怎麼實作呢?
首先建立一個Logon_DB資料庫,再建立一個ValidIP表,在表中插入<local machine>和192.168.1.195,表示允許本地和IP為192.168.1.195進行登陸,登陸的帳号屬于伺服器角色:sysadmin。
這次我們在IP為:192.168.1.120的機器上進行測試,這個IP之前是允許使用test帳号登陸的(tr_connection_limit),這次使用sa這個帳号登陸,傳回了Figure3的錯誤資訊,這是因為它違反了登陸觸發器tr_logon_CheckIP的規則。
(Figure3:sa使用者登陸錯誤資訊)
在IP為192.168.1.195的機器上使用sa這個帳号成功登陸,再次執行Script2腳本,傳回的清單如Figure4所示。
(Figure4:使用者登陸資訊)
(三) 還有一種場景,我們需要限制某些使用者隻能在指定的時間内登陸資料庫,比如業務上某些運用隻能在晚上跑的,通過這個登陸觸發器,可以防止運用修改執行時間在白天中運作。
首先我們建立一個名為nightworker的登陸名,再建立一個LogonBlockedLog的登陸攔截日志表,接着建立登陸觸發器:tr_logon_CheckTime,在早上7:00之後至晚上18:00之前(BETWEEN 7 AND 17)是不允許nightworker帳号登陸資料庫的。
現在時間是17:20左右,我使用nightworker登陸資料庫,這違反了登陸觸發器:tr_logon_CheckTime,是以提示Figure5的錯誤資訊,并且在LogonBlockedLog攔截日志表中出現了一條記錄,這個表可以幫助我們更好的了解登陸賬号的登陸資訊。
(Figure5:nightworker使用者登陸錯誤資訊)
(Figure6:攔截日志表)
(四) 我們進一步模仿MySQL的mysql.User表的用法,用表儲存使用者與IP的對應關系,這樣就可以對所有登陸使用者進行控制了。
(Figure7:登陸名與有效IP對應表)
使用者登陸名與IP對應關系表[ValidLogOn],有幾點需要注意的,BARXXX\Administrator這個是Windows 身份驗證中作業系統的帳号,你需要根據你的實際情況進行修改;IP當中你則需要注意<local machine>和127.0.0.1這些特殊的位址,我個人還是建議在這個表中加入這些資訊的。
(五) 對上面的再延伸一點,如果想類似Host like 192.168.1.* 這樣進行範圍的過濾,那這又應該怎麼實作呢?
可以使用CLR擴充函數對IP進行判斷,後面會講到這種方式。這裡使用SQL就能解決的方法,僅供參考。開放登入名nightworker在内網所有IP:192.168.1.* 通路本機的權限。

(Figure7_1:添加192.168.1.*後)
(Figure8:nightworker使用者登入通過)
(Figure9:sa使用者登入攔截)
從Figure8和Figure9的對比可以知道,在同一台機器192.168.1.208使用nightworker和sa有不同的效果,nightworker使用者登入成功,sa使用者登入被攔截了。
建立程式集(引用一個寫好的SQLCLR.dll檔案)之後執行下面的SQL腳本建立标量值函數,建立成功後效果如下圖所示:
(Figure10:注冊成功後)
(一) 我有一台伺服器A在本地無法使用SSMS登陸(2005),原因是因為我在A上重新安裝DotnetFramework的時候失敗了,但是不影響程式連結A的資料庫,在伺服器B也可以使用SSMS連結到伺服器A,我維護資料庫有時候需要在單使用者下進行表分區的維護(鎖比較多),如果在正常情況下,我隻需要在A停止TCP/IP就可以阻止其它使用者登陸,那這種情況有什麼辦法解決呢?對的,讓指定的伺服器B的IP能通路伺服器A的資料庫,寫個登陸觸發器,重新開機SQL Server服務,維護完之後删除登陸觸發器,具體的SQL代碼可以參考Script1的登陸觸發器:tr_connection_limit。
(Figure11:A伺服器SSMS打開錯誤)
(二) 如果在登陸觸發器中需要讀取表[Logon_DB].[dbo].[ValidLogOn],如果在ON ALL SERVER後面沒有加入WITH EXECUTE AS 'sa',當你使用test或者nightworker登陸就會一直報錯,因為test和nightworker是沒有權限讀取[Logon_DB].[dbo].[ValidLogOn]表。而tr_logon_CheckIP之是以不用WITH EXECUTE AS 'sa'是因為這本身就是對伺服器角色sysadmin的邏輯處理。
(三) 測試本地登陸的情況的時候需要測試[.]、[local]、[localhost]、[127.0.0.1]、[ipconfig]裡面顯示的内網IP位址這五種情況。([.]、[local]、[localhost]在EVENTDATA()的ClientHost标簽都是顯示<local machine>)
(一) 像Figure1、Figure3和Figure5等并沒有清晰顯示登陸錯誤資訊。比如:錯誤是什麼原因造成的?是哪個登陸觸發器攔截的?攔截規則是什麼?為了友善使用者清晰了解規則,我們需要自定義這些錯誤内容。
(二) 如果在tr_logon_CheckIP觸發器的ROLLBACK之前加入Print語句會出錯,錯誤資訊就如Figure3所示,原來可以登陸的,加了這個Print就不行了?為什麼?
(三) 建立登陸觸發器要在伺服器角色:sysadmin(比如sa)的權限下執行Create腳本,不然會報錯,即使使用了WITH EXECUTE AS 'sa'選項也同樣報錯,具體的官方文檔說明還沒找到。
(四) 為什麼在伺服器名稱使用localhost登陸的時候會有3條記錄插入到[LogonLog]表的呢?
(Figure12:一次登陸3條記錄)
<a href="http://www.sqlservercentral.com/articles/Security/66151/">Blocking Users by IP</a>
<a href="http://www.cnblogs.com/downmoon/archive/2011/03/01/1966670.html">SQL Server 2008中的代碼安全(二):DDL觸發器與登入觸發器</a>
<a href="http://msdn.microsoft.com/zh-cn/library/ms189799.aspx">CREATE TRIGGER (Transact-SQL)</a>
<a href="http://msdn.microsoft.com/zh-cn/library/bb326598.aspx">登入觸發器</a>
<a href="http://blog.csdn.net/zjqweter/article/details/7265034">Sql server限制IP通路方法</a>
<a href="http://msdn.microsoft.com/zh-cn/subscriptions/downloads/ms176013">sys.dm_exec_sessions (Transact-SQL)</a>
<a href="http://msdn.microsoft.com/zh-cn/subscriptions/downloads/ms181362">EXECUTE AS (Transact-SQL)</a>
<a href="http://msdn.microsoft.com/zh-cn/subscriptions/downloads/ms189492">ORIGINAL_LOGIN (Transact-SQL)</a>
<a href="http://msdn.microsoft.com/en-us/library/ms173781.aspx">EVENTDATA (Transact-SQL)</a>
<a href="http://www.cnblogs.com/xugang/archive/2010/02/20/1669619.html">淺談SQL Server 資料庫之觸發器</a>
<a href="http://msdn.microsoft.com/zh-cn/library/ms186376.aspx">事件通知</a>