SQL Server解決孤立用戶淺析

一、 孤立用戶概念

所謂孤立用戶即指在服務器實例上未定義或錯誤定義了其相應 SQL Server 登錄名的數據庫用戶,無法登錄到實例(有數據庫用戶卻無對應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;

從上面可以看出,

  • 孤立賬號必須是SQL Server 用戶(issqluser= 1)
  • 返回與安全標識號 (SID) 關聯的登錄名必須爲空值
  • SID的長度小於16
  • 必須是sys、guest、INFORMATION_SCHEMA賬號以外的SQL Server用戶
SELECT * FROM sysusers WHERE SID IS NULL OR SID = 0x0;

clip_image0013:

 

三、 解決孤立賬號

1. 方法1

  • Step 1:  檢測、查看對應的孤立賬號
USE <DatabaseName>;
GO
 
EXEC sp_change_users_login  @Action='Report';
GO
  • Step 2: 新建對應的登錄名,例如上面檢測到Test賬號爲孤立賬號
USE [master]
GO
 
CREATE LOGIN [Test] WITH PASSWORD=N'Pa@#456' MUST_CHANGE, DEFAULT_DATABASE=[xxxx], CHECK_EXPIRATION=ON, CHECK_POLICY=ON
GO
  • Step 3:關聯對應賬號
USE EASN_EAP;
GO
 
EXEC sp_change_users_login @Action='Update_one',@UserNamePattern='xxxx',@LoginName='xxxx';
  • Step 4: 重複執行Step 1、Step 2、Step 3解決其它孤立賬號,直到所有孤立賬號全部被Fix掉。

 

2. 方法2

對於方法1,如果賬號比較多,操作起來比較鬱悶,重複幹繁瑣的體力活。於是我寫了一個存儲過程來解決

SET ANSI_NULLS ON
GO 
SET QUOTED_IDENTIFIER ON
GO
 
IF EXISTS ( SELECT  1
            FROM    dbo.sysobjects
            WHERE   id = OBJECT_ID(N'sp_fix_orphaned_users')
                    AND OBJECTPROPERTY(id, 'IsProcedure') = 1 )
    DROP PROCEDURE sp_fix_orphaned_users;
GO
 
--================================================================================
--        ProcedureName        :            sp_fix_orphaned_users
--        Author               :            Kerry    
--        CreateDate           :            2013-12-08
--        Description          :            批量解決數據庫孤立賬號
--                                        http://www.cnblogs.com/kerrycode/
/**********************************************************************************************
        Parameters            :                                    參數說明
***********************************************************************************************
            @DefaultPwd       :            所有孤立賬戶使用同一個密碼@DefaultPwd
            @LoginName        :            所有需要fix的孤立賬戶,eg 'test1|test2|test3' 表示孤立賬戶test1、test2、test3。
            @Password         :            對應@LoginName,eg '@341|Dbd123|D#25' 分別表示上面賬號對應的密碼
*************************************************************************************************
   Modified Date    Modified User     Version                 Modified Reason
**************************************************************************************************    2013-12-08             Kerry         V01.00.00                  創建該存儲過程。

*************************************************************************************************/
--=================================================================================================
 
CREATE PROCEDURE [dbo].[sp_fix_orphaned_users]
(
    @IsUseSamePwd  INT    = 0              ,
    @DefaultPwd       VARCHAR(32) = NULL ,
    @LoginName       NVARCHAR(MAX) =NULL,
    @Password       NVARCHAR(MAX) =NULL
)
AS 
 
DECLARE @UserName NVARCHAR(64);
DECLARE @tmpPwd      VARCHAR(20);
DECLARE @LoginRows INT;
DECLARE @PwdRows   INT;
    
IF @IsUseSamePwd =1 AND @DefaultPwd IS NULL
BEGIN
    RAISERROR('%s Invalid. Please check the paramter %s value',16,1, '@DefaultPwd');
    RETURN 1;
END 
 
IF @IsUseSamePwd = 0 AND ( @LoginName IS NULL OR  @Password IS NULL)
BEGIN
    RAISERROR('%s Invalid. Please check the paramter %s value',16,1, '@Password');
    RETURN 1;
END
    
IF @IsUseSamePwd = 0 
BEGIN
 
    CREATE TABLE #TempLoginNams
    (
        ID                INT,
        UserName        VARCHAR(20),
    )    
 
    INSERT INTO #TempLoginNams
            ( ID, UserName )
    SELECT * FROM dbo.SplitString(@LoginName,'|');
 
    CREATE TABLE #TempPassword
    (
        ID            INT,
        UserPassrd  VARCHAR(20)
    )
 
    INSERT INTO #TempPassword
    SELECT * FROM dbo.SplitString(@Password,'|');
 
    SELECT @LoginRows=COUNT(1) FROM #TempLoginNams;
    SELECT @PwdRows=COUNT(10) FROM #TempPassword;
 
IF @LoginRows != @PwdRows
    BEGIN
        RAISERROR('The paramter %s have different nums. Please check the paramter %s value',16,1, '@LoginName & @Password ');
        RETURN 1;
    END
 
END
 
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 @IsUseSamePwd = 1
            BEGIN
        
                EXEC sp_change_users_login 'Auto_Fix', @UserName, NULL,
                    @DefaultPwd;
                    
       
                EXEC sp_change_users_login @Action = 'update_one',
                    @UserNamePattern = @UserName, @LoginName = @UserName;
            END
        ELSE
            BEGIN
                SELECT  @UserName = o.UserName ,
                        @tmpPwd = p.UserPassrd
                FROM    #OrphanedUser o
                        LEFT JOIN #TempLoginNams l ON o.UserName = l.UserName
                        LEFT JOIN #TempPassword p ON l.ID = p.ID
                WHERE   o.UserName = @UserName;
    
                EXEC sp_change_users_login 'Auto_Fix', @UserName, NULL,
                    @tmpPwd;
                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;
 
IF @IsUseSamePwd = 0 
BEGIN
    DROP TABLE #TempLoginNams;
    DROP TABLE #TempPassword;
END
GO

其中該存儲過程調用了一個Function進行SplitString,該函數是我從網上搜索得來的,作者不詳,本來想自己重寫該函數,後來覺得沒有必要重複造輪子。因爲這個函數完全滿足我的需求。

 
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解決孤立賬號問題。

--SQL 1
EXEC [dbo].[sp_fix_orphaned_users] @IsuseSamePwd =1,@DefaultPwd='Qwe!@423'
--SQL 2
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掉。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章