SQL Server解決孤立用戶淺析


孤立用戶概念

       所謂孤立用戶即指在服務器實例上未定義或錯誤定義了其相應 SQL Server 登錄名的數據庫用戶無法登錄到實例。 這樣的用戶被稱為此服務器實例上的數據庫的“孤立用戶”。 如果刪除了對應的 SQL Server 登錄名,則數據庫用戶可能會變為孤立用戶。 另外,在數據庫還原或附加到 SQL Server 的其他實例之后,數據庫用戶也可能變為孤立用戶。 如果未在新服務器實例中提供數據庫用戶映射到的 SID,則該用戶可能變為孤立用戶

檢測孤立用戶

檢測孤立用戶相當簡單,可以使用下面SQL語句

Code Snippet
  1. USE DatabaseName;
  2. GO
  3.  
  4. EXEC sp_change_users_login @Action = 'Report';
  5. GO

當然如果你不想用系統自帶的存儲過程sp_change_users_login,其實檢測孤立賬號也很簡單,一個簡單的SQL語句即可搞定:

Code Snippet
  1. SELECT   UserName = name ,
  2.          UserSID = sid
  3. FROM     sysusers
  4. WHERE    issqluser = 1
  5.          AND ( sid IS NOT NULL
  6.                AND sid <> 0x0
  7.              )
  8.          AND ( LEN(sid) <= 16 )
  9.          AND SUSER_SNAME(sid) IS NULL
  10. 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;

clip_image001

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
 
其中該存儲過程調用了一個Function成為SplitString,該函數是我從網上搜索得來的,作者不詳,本來想自己重寫該函數,后來覺得沒有必要重復造輪子。因為這個函數完全滿足我的需求。
 
Code Snippet
  1. CREATE FUNCTION SplitString
  2.     (
  3.       -- Add the parameters for the function here
  4.       @myString VARCHAR(500) ,
  5.       @deliminator VARCHAR(10)
  6.     )
  7. RETURNS @ReturnTable TABLE
  8.     (
  9.       -- Add the column definitions for the TABLE variable here
  10.       [id] [int] IDENTITY(1, 1)
  11.                  NOT NULL ,
  12.       [part] [varchar](50) NULL
  13.     )
  14. AS
  15.     BEGIN
  16.         DECLARE @iSpaces INT
  17.         DECLARE @part VARCHAR(50)
  18.         --initialize spaces 
  19.         SELECT  @iSpaces = CHARINDEX(@deliminator, @myString, 0)
  20.         WHILE @iSpaces > 0
  21.             BEGIN
  22.                 SELECT  @part = SUBSTRING(@myString, 0,
  23.                                           CHARINDEX(@deliminator, @myString, 0))
  24.                 INSERT  INTO @ReturnTable
  25.                         ( part )
  26.                         SELECT  @part
  27.                 SELECT  @myString = SUBSTRING(@mystring,
  28.                                               CHARINDEX(@deliminator,
  29.                                                         @myString, 0)
  30.                                               + LEN(@deliminator),
  31.                                               LEN(@myString) - CHARINDEX(' ',
  32.                                                               @myString, 0))
  33.                 SELECT  @iSpaces = CHARINDEX(@deliminator, @myString, 0)
  34.             END
  35.         IF LEN(@myString) > 0
  36.             INSERT  INTO @ReturnTable
  37.                     SELECT  @myString
  38.         RETURN
  39.     END
  40.            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^%'

看到樺仔的回復(修改后的存儲過程后),那個確實是個不錯的方法,我測試了一下后發現還是這個問題:

Code Snippet
  1. CREATE PROCEDURE [dbo].[sp_fix_orphaned_users]
  2. AS
  3.     BEGIN
  4.         DECLARE @UserName NVARCHAR(64)
  5.         CREATE TABLE #SqlLoginUser
  6.         (
  7.             UserName SYSNAME ,
  8.             UserId INT IDENTITY(1, 1)
  9.         )
  10.          
  11.         INSERT  INTO #SqlLoginUser( UserName ) SELECT  [name]  FROM  SYS.[sql_logins]
  12.         CREATE TABLE #OrphanedUser
  13.             (
  14.               UserName SYSNAME ,
  15.               UserId INT
  16.             )
  17.         INSERT  INTO #OrphanedUser EXEC sp_change_users_login @Action = 'Report';
  18.         DECLARE Cur_OrphanedUsers CURSOR
  19.         FOR
  20.             SELECT  UserName
  21.             FROM    #OrphanedUser;
  22.         OPEN Cur_OrphanedUsers;
  23.         FETCH NEXT FROM Cur_OrphanedUsers INTO @UserName;
  24.         WHILE ( @@FETCH_STATUS = 0 )
  25.             BEGIN
  26.                 IF ( @UserName IN ( SELECT  [UserName]
  27.                                     FROM    [#SqlLoginUser] ) )
  28.                     BEGIN
  29.                         EXEC sp_change_users_login @Action = 'update_one',
  30.                             @UserNamePattern = @UserName,
  31.                             @LoginName = @UserName;
  32.                     END
  33.                 ELSE
  34.                     BEGIN
  35.                         DECLARE @SQL NVARCHAR(200)
  36.                         SET @SQL = 'CREATE LOGIN ' + @UserName + ' WITH PASSWORD='''''
  37.                         EXEC(@SQL)
  38.                         EXEC sp_change_users_login @Action = 'update_one',
  39.                             @UserNamePattern = @UserName,
  40.                             @LoginName = @UserName;
  41.                     END
  42.    
  43.                 FETCH NEXT FROM Cur_OrphanedUsers INTO @UserName
  44.             END
  45.         CLOSE Cur_OrphanedUsers
  46.         DEALLOCATE Cur_OrphanedUsers
  47.      
  48.         DROP TABLE   #OrphanedUser
  49.         DROP TABLE  #SqlLoginUser
  50.     END
  51. EXEC sp_fix_orphaned_users

消息 15116,級別 16,狀態 1,第 1 行
密碼有效性驗證失敗。該密碼太短,不符合 Windows 策略要求。
消息 15291,級別 16,狀態 1,過程 sp_change_users_login,第 137 行
正在終止此過程。缺少 Login 名稱 'xxx' 或該名稱無效。

不過對於這個錯誤倒是很好解決,創建登錄名是將CHECK_POLICY設置為OFF,就可避免上面錯誤。

Code Snippet
  1. USE [master]
  2. GO
  3. CREATE LOGIN [test] WITH PASSWORD=N'', DEFAULT_DATABASE=[master], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF
  4. 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掉。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM