孤立用戶概念
所謂孤立用戶即指在服務器實例上未定義或錯誤定義了其相應 SQL Server 登錄名的數據庫用戶無法登錄到實例。 這樣的用戶被稱為此服務器實例上的數據庫的“孤立用戶”。 如果刪除了對應的 SQL Server 登錄名,則數據庫用戶可能會變為孤立用戶。 另外,在數據庫還原或附加到 SQL Server 的其他實例之后,數據庫用戶也可能變為孤立用戶。 如果未在新服務器實例中提供數據庫用戶映射到的 SID,則該用戶可能變為孤立用戶
檢測孤立用戶
檢測孤立用戶相當簡單,可以使用下面SQL語句
- USE DatabaseName;
- GO
- EXEC sp_change_users_login @Action = 'Report';
- GO
當然如果你不想用系統自帶的存儲過程sp_change_users_login,其實檢測孤立賬號也很簡單,一個簡單的SQL語句即可搞定:
- SELECT UserName = name ,
- UserSID = sid
- FROM sysusers
- WHERE issqluser = 1
- AND ( sid IS NOT NULL
- AND sid <> 0x0
- )
- AND ( LEN(sid) <= 16 )
- AND SUSER_SNAME(sid) IS NULL
- ORDER BY name
從上面可以看出,
1:孤立賬號必須是SQL Server 用戶(issqluser= 1),:
2:它必須是sys、guest、INFORMATION_SCHEMA賬號以外的SQL Server用戶
SELECT * FROM sysusers WHERE SID IS NULL OR SID = 0x0;
3:它返回與安全標識號 (SID) 關聯的登錄名必須為空值
4:SID的長度小於16
解決孤立賬號
方法1:
1: Step 1: 檢測、查看對應的孤立賬號
2:
3:
4: USE <DatabaseName>;
5:
6: GO
7:
8: EXEC sp_change_users_login @Action='Report';
9:
10: GO
11:
12: Step 2: 新建對應的登錄名,例如上面檢測到Test賬號為孤立賬號
13:
14: USE [master]
15:
16: GO
17:
18: CREATE LOGIN [Test] WITH PASSWORD=N'Pa@#456' MUST_CHANGE, DEFAULT_DATABASE=[xxxx], CHECK_EXPIRATION=ON, CHECK_POLICY=ON
19:
20: GO
21:
22: Step 3:
23:
24: USE EASN_EAP;
25:
26: GO
27:
28: EXEC sp_change_users_login @Action='Update_one',@UserNamePattern='xxxx',@LoginName='xxxx';
29:
30: Step 4: 重復執行Step 1、Step 2、Step 3解決其它孤立賬號,直到所有孤立賬號全部被Fix掉。
31:
方法2:對於方法1,如果賬號比較多,操作起來比較郁悶,重復干繁瑣的體力活。於是我寫了一個存儲過程來解決
1: SET ANSI_NULLS ON
2: GO
3:
4: SET QUOTED_IDENTIFIER ON
5: GO
6:
7:
8:
9: IF EXISTS ( SELECT 1
10: FROM dbo.sysobjects
11: WHERE id = OBJECT_ID(N'sp_fix_orphaned_users')
12: AND OBJECTPROPERTY(id, 'IsProcedure') = 1 )
13: DROP PROCEDURE sp_fix_orphaned_users;
14: GO
15:
16: --================================================================================
17: -- ProcedureName : sp_fix_orphaned_users
18: -- Author : Kerry
19: -- CreateDate : 2013-12-08
20: -- Description : 批量解決數據庫孤立賬號
21: -- http://www.cnblogs.com/kerrycode/
22: /**********************************************************************************************
23: Parameters : 參數說明
24: ***********************************************************************************************
25: @DefaultPwd : 所有孤立賬戶使用同一個密碼@DefaultPwd
26: @LoginName : 所有需要fix的孤立賬戶,eg 'test1|test2|test3' 表示孤立賬戶test1、test2、test3。
27: @Password : 對應@LoginName,eg '@341|Dbd123|D#25' 分別表示上面賬號對應的密碼
28: *************************************************************************************************
29: Modified Date Modified User Version Modified Reason
30: ************************************************************************************************** 2013-12-08 Kerry V01.00.00 創建該存儲過程。
31:
32: *************************************************************************************************/
33: --=================================================================================================
34:
35: CREATE PROCEDURE [dbo].[sp_fix_orphaned_users]
36: (
37: @IsUseSamePwd INT = 0 ,
38: @DefaultPwd VARCHAR(32) = NULL ,
39: @LoginName NVARCHAR(MAX) =NULL,
40: @Password NVARCHAR(MAX) =NULL
41: )
42: AS
43:
44: DECLARE @UserName NVARCHAR(64);
45: DECLARE @tmpPwd VARCHAR(20);
46: DECLARE @LoginRows INT;
47: DECLARE @PwdRows INT;
48:
49:
50:
51: IF @IsUseSamePwd =1 AND @DefaultPwd IS NULL
52: BEGIN
53: RAISERROR('%s Invalid. Please check the paramter %s value',16,1, '@DefaultPwd');
54: RETURN 1;
55: END
56:
57: IF @IsUseSamePwd = 0 AND ( @LoginName IS NULL OR @Password IS NULL)
58: BEGIN
59: RAISERROR('%s Invalid. Please check the paramter %s value',16,1, '@Password');
60: RETURN 1;
61: END
62:
63: IF @IsUseSamePwd = 0
64: BEGIN
65:
66: CREATE TABLE #TempLoginNams
67: (
68: ID INT,
69: UserName VARCHAR(20),
70: )
71:
72: INSERT INTO #TempLoginNams
73: ( ID, UserName )
74: SELECT * FROM dbo.SplitString(@LoginName,'|');
75:
76: CREATE TABLE #TempPassword
77: (
78: ID INT,
79: UserPassrd VARCHAR(20)
80: )
81:
82: INSERT INTO #TempPassword
83: SELECT * FROM dbo.SplitString(@Password,'|');
84:
85: SELECT @LoginRows=COUNT(1) FROM #TempLoginNams;
86: SELECT @PwdRows=COUNT(10) FROM #TempPassword;
87:
88: IF @LoginRows != @PwdRows
89: BEGIN
90: RAISERROR('The paramter %s have different nums. Please check the paramter %s value',16,1, '@LoginName & @Password ');
91: RETURN 1;
92: END
93:
94: END
95:
96:
97: CREATE TABLE #OrphanedUser
98: (
99: UserName sysname,
100: UserId INT
101: )
102:
103:
104: INSERT INTO #OrphanedUser EXEC sp_change_users_login @Action='Report';
105:
106:
107: DECLARE Cur_OrphanedUsers CURSOR FOR
108: SELECT UserName FROM #OrphanedUser;
109:
110:
111: OPEN Cur_OrphanedUsers;
112:
113: FETCH NEXT FROM Cur_OrphanedUsers INTO @UserName;
114: WHILE ( @@FETCH_STATUS = 0 )
115: BEGIN
116: IF @IsUseSamePwd = 1
117: BEGIN
118:
119: EXEC sp_change_users_login 'Auto_Fix', @UserName, NULL,
120: @DefaultPwd;
121:
122:
123: EXEC sp_change_users_login @Action = 'update_one',
124: @UserNamePattern = @UserName, @LoginName = @UserName;
125: END
126: ELSE
127: BEGIN
128: SELECT @UserName = o.UserName ,
129: @tmpPwd = p.UserPassrd
130: FROM #OrphanedUser o
131: LEFT JOIN #TempLoginNams l ON o.UserName = l.UserName
132: LEFT JOIN #TempPassword p ON l.ID = p.ID
133: WHERE o.UserName = @UserName;
134:
135: EXEC sp_change_users_login 'Auto_Fix', @UserName, NULL,
136: @tmpPwd;
137: EXEC sp_change_users_login @Action = 'update_one',
138: @UserNamePattern = @UserName, @LoginName = @UserName;
139: END
140:
141: FETCH NEXT FROM Cur_OrphanedUsers INTO @UserName
142: END
143: CLOSE Cur_OrphanedUsers
144: DEALLOCATE Cur_OrphanedUsers
145:
146: DROP TABLE #OrphanedUser;
147:
148: IF @IsUseSamePwd = 0
149: BEGIN
150: DROP TABLE #TempLoginNams;
151: DROP TABLE #TempPassword;
152: END
153:
154: GO
- CREATE FUNCTION SplitString
- (
- -- Add the parameters for the function here
- @myString VARCHAR(500) ,
- @deliminator VARCHAR(10)
- )
- RETURNS @ReturnTable TABLE
- (
- -- Add the column definitions for the TABLE variable here
- [id] [int] IDENTITY(1, 1)
- NOT NULL ,
- [part] [varchar](50) NULL
- )
- AS
- BEGIN
- DECLARE @iSpaces INT
- DECLARE @part VARCHAR(50)
- --initialize spaces
- SELECT @iSpaces = CHARINDEX(@deliminator, @myString, 0)
- WHILE @iSpaces > 0
- BEGIN
- SELECT @part = SUBSTRING(@myString, 0,
- CHARINDEX(@deliminator, @myString, 0))
- INSERT INTO @ReturnTable
- ( part )
- SELECT @part
- SELECT @myString = SUBSTRING(@mystring,
- CHARINDEX(@deliminator,
- @myString, 0)
- + LEN(@deliminator),
- LEN(@myString) - CHARINDEX(' ',
- @myString, 0))
- SELECT @iSpaces = CHARINDEX(@deliminator, @myString, 0)
- END
- IF LEN(@myString) > 0
- INSERT INTO @ReturnTable
- SELECT @myString
- RETURN
- END
- GO
這個存儲過程在執行時,有一個既可以說是小bug,也可以說沒有驗證的錯誤,就是登錄名的密碼設置如果過於簡單,則執行
EXEC sp_change_users_login 'Auto_Fix', @UserName, NULL, @tmpPwd; 則會報如下錯誤
消息 15118,級別 16,狀態 1,第 1 行
密碼有效性驗證失敗。該密碼不夠復雜,不符合 Windows 策略要求。
消息 15497,級別 16,狀態 1,過程 sp_change_users_login,第 223 行
無法使用 sp_addlogin 添加登錄名(用戶 = easn)。即將終止此過程。
一時還沒有找到如何去驗證密碼是否符合復雜度的方法,留待以后進一步完善。
假如遷移數據庫后,發現有user1、user2、user3三個孤立賬號,如果我想着三個孤立賬號使用同一密碼,那么執行SQL 1 ,如果我想給user1、user2、user3三個賬號設置各自密碼,那么使用SQL 2解決孤立賬號問題。
1: --SQL 1
2: EXEC [dbo].[sp_fix_orphaned_users] @IsuseSamePwd =1,@DefaultPwd='Qwe!@423'
3:
4: --SQL 2
5: EXEC [dbo].[sp_fix_orphaned_users] @IsuseSamePwd =0, @loginName='user1|user2|user3', @Password='Qwe!@423|QweD2@#4|Oi87^%'
看到樺仔的回復(修改后的存儲過程后),那個確實是個不錯的方法,我測試了一下后發現還是這個問題:
- CREATE PROCEDURE [dbo].[sp_fix_orphaned_users]
- AS
- BEGIN
- DECLARE @UserName NVARCHAR(64)
- CREATE TABLE #SqlLoginUser
- (
- UserName SYSNAME ,
- UserId INT IDENTITY(1, 1)
- )
- INSERT INTO #SqlLoginUser( UserName ) SELECT [name] FROM SYS.[sql_logins]
- CREATE TABLE #OrphanedUser
- (
- UserName SYSNAME ,
- UserId INT
- )
- INSERT INTO #OrphanedUser EXEC sp_change_users_login @Action = 'Report';
- DECLARE Cur_OrphanedUsers CURSOR
- FOR
- SELECT UserName
- FROM #OrphanedUser;
- OPEN Cur_OrphanedUsers;
- FETCH NEXT FROM Cur_OrphanedUsers INTO @UserName;
- WHILE ( @@FETCH_STATUS = 0 )
- BEGIN
- IF ( @UserName IN ( SELECT [UserName]
- FROM [#SqlLoginUser] ) )
- BEGIN
- EXEC sp_change_users_login @Action = 'update_one',
- @UserNamePattern = @UserName,
- @LoginName = @UserName;
- END
- ELSE
- BEGIN
- DECLARE @SQL NVARCHAR(200)
- SET @SQL = 'CREATE LOGIN ' + @UserName + ' WITH PASSWORD='''''
- EXEC(@SQL)
- EXEC sp_change_users_login @Action = 'update_one',
- @UserNamePattern = @UserName,
- @LoginName = @UserName;
- END
- FETCH NEXT FROM Cur_OrphanedUsers INTO @UserName
- END
- CLOSE Cur_OrphanedUsers
- DEALLOCATE Cur_OrphanedUsers
- DROP TABLE #OrphanedUser
- DROP TABLE #SqlLoginUser
- END
- EXEC sp_fix_orphaned_users
消息 15116,級別 16,狀態 1,第 1 行
密碼有效性驗證失敗。該密碼太短,不符合 Windows 策略要求。
消息 15291,級別 16,狀態 1,過程 sp_change_users_login,第 137 行
正在終止此過程。缺少 Login 名稱 'xxx' 或該名稱無效。
不過對於這個錯誤倒是很好解決,創建登錄名是將CHECK_POLICY設置為OFF,就可避免上面錯誤。
- USE [master]
- GO
- CREATE LOGIN [test] WITH PASSWORD=N'', DEFAULT_DATABASE=[master], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF
- GO
給登錄名密碼設置為空,這個做法相當不安全,我還是覺得有所不妥,其實,我現在在的需求是這樣(很多時候,由於表達能力不足,沒有闡述清楚): 數據庫從一台服務器遷移到另外一台服務器后,這個數據庫對應的賬號變成了孤立賬號,假設其孤立賬號為U1、U2……UN在遷移整理過程我發現,其實我只需要賬號U1、U2、 U4、U6,其它賬號沒有必要也遷移過去。所以我才為存儲過程sp_fix_orphaned_users設置了參數@LoginName和@Password, 用於解決這種需求。@LoginName=‘U1|U2|U4|U6’, @Password=‘Pwd1|Pwd2|Pwd4|Pwd6’,而有時候在測試數據庫環境,為了圖方便、省事,就所有孤立賬號使用同一個秘密,這就是加入參數@IsUseSamePwd的緣故。當然這些是我自己的特殊需求。至於如果不用驗證密碼復雜性,可以結合樺仔的方法,先新建登錄名,然后使用sp_change_users_login來Fix掉。