SQL Server 登陸賬戶/包含用戶密碼批量修改

SQL Server 數據庫管理中,賬戶安全是數據庫安全的一道重要屏障。除對密碼複雜度有嚴格要求外,週期性修改賬戶密碼,是保證數據庫安全的一個重要舉措。如下給出修改登陸賬戶/包含用戶密碼的腳本:

--更換登陸帳戶密碼
USE master
GO
ALTER LOGIN Jack WITH PASSWORD='I8@^mV7(Tyz0Jov',CHECK_POLICY=ON,CHECK_EXPIRATION=OFF
--修改包含用戶密碼
USE test
GO
ALTER USER Jack WITH PASSWORD='I8@^mV7(Tyz0Jov'

但對於生產數據庫,這樣直接修改原賬戶的密碼是存在問題的。因爲我們不能做到數據庫和應用同時修改密碼,這樣是會影響業務的正常運行,而導致不必要的損失。而較爲合理的做法是創建新賬戶,賦予原賬戶相同的權限,替換原賬戶。下面將使用《SQL Server 登陸賬戶權限克隆——sp_DBA_LoginClone 升級版》《克隆SQL Server 用戶及權限》兩篇文章中敘述的SQL Server登陸賬戶、SQL Server 數據庫用戶克隆的方式給出新賬戶的創建及權限賦予腳本。

爲更方便的給創建新賬戶提供隨機密碼,這裏首先給出生成隨機密碼的腳本,如下:

--創建字符集表,並插入生成密碼的字符
USE master
CREATE TABLE AsciiCode(id INT IDENTITY(1,1),characters CHAR(1))
DECLARE @i INT=33
WHILE @i<127
BEGIN
      IF @i NOT IN(34,39) --密碼中不能有單引號和雙引號
            INSERT INTO AsciiCode(characters)
            SELECT CHAR(@i)
      SET @i=@i+1
END
GO
--創建生成隨機密碼的存儲過程
USE master
GO
CREATE PROC pwdGenerate(
      @num smallint --密碼長度
      ,@pwd VARCHAR(50) OUTPUT
      )
AS
BEGIN
      DECLARE @i SMALLINT=1
      SET @pwd=''
      WHILE @i<=@num
      BEGIN
            SELECT TOP 1 @pwd=@pwd+characters
            FROM master.dbo.AsciiCode
            --WHERE id=RAND()*94
            ORDER BY NEWID()
            SET @i=@i+1
      END
      --RETURN @pwd
END
GO

替換賬戶信息

接下來確定需要替換的賬戶、新賬戶,並使用上面的腳本生成密碼,如下:

USE master
GO
--創建新舊帳戶密碼對應表
CREATE TABLE login_create
    (
    id INT IDENTITY(1,1)
    ,newLoginName  sysname NULL
    ,loginName sysname NULL
    ,pwd VARCHAR(50)
    ,dbName sysname NULL
    ,userName sysname NULL
    ,newUserName sysname NULL
    )
----插入需要替換的賬戶(一般針對生產環境使用的帳戶、用戶)、新帳戶、密碼(密碼可以由存儲過程pwdGenerate生成)
INSERT INTO login_create(loginName)
SELECT  name
FROM    sys.sql_logins
WHERE   name NOT IN ( 'NT SERVICE\MSSQLSERVER', 'NT AUTHORITY\SYSTEM', 'NT SERVICE\SQLSERVERAGENT','##MS_PolicyTsqlExecutionLogin##','##MS_PolicyEventProcessingLogin##' )
      AND is_disabled=0
--select * from login_create
--TRUNCATE TABLE login_create
UPDATE login_create SET newLoginName=loginName+'_V1'
--生成新帳戶的密碼
DECLARE @id INT=1,@maxNum INT
DECLARE @pwd VARCHAR(50)
SELECT
      @maxNum=MAX(id)
from login_create
WHILE @id<=@maxNum
BEGIN
      EXEC pwdGenerate 15,@pwd OUTPUT
      UPDATE login_create SET pwd=@pwd
      WHERE id=@id
      SET @id=@id+1
END
GO

注意:login_create中,對於登陸賬戶,DBName、userName、newUserName字段爲NULL;包含用戶newLoginName、loginName爲空。

克隆賬戶用戶

使用前文中的過程sp_DBA_LoginPermissionsClonesp_DBA_userPermisionsClone,生成新賬戶的創建腳本,及權限賦予腳本。注意執行下面腳本時需要先在master庫下創建sp_DBA_LoginPermissionsClonesp_DBA_userPermisionsClone兩過程,並標記爲系統過程。

USE master
GO
--生成新帳戶的創建腳本,並生成克隆原帳戶權限的賦予腳本
SET nocount ON
DECLARE @newLoginName SYSNAME
      ,@loginName SYSNAME
      ,@pwd CHAR(15)
      ,@userName sysname
      ,@dbName sysname
      ,@newUserName sysname
      ,@sql NVARCHAR(max)
DECLARE logins CURSOR
FOR
SELECT  newLoginName,loginName,pwd,dbName,userName,newUserName
FROM    login_create
OPEN logins
FETCH NEXT FROM logins INTO @newLoginName, @loginName, @pwd,@dbName,@userName,@newUserName
--循環登陸賬戶
WHILE @@FETCH_STATUS = 0
BEGIN
       if @loginName is not null
       begin
      EXEC dbo.sp_DBA_LoginPermissionsClone @loginName,@newLoginName,@pwd,@sql OUTPUT
      PRINT @sql
         set @userName=@loginName
       end
      --循環數據庫,生成創建數據用戶腳本及賦予用戶權限腳本
    DECLARE @databaseName SYSNAME
    DECLARE databaseNames CURSOR
    FOR
    SELECT  name
    FROM    sys.sysdatabases
    OPEN databaseNames
    FETCH NEXT FROM databaseNames INTO @databaseName
    WHILE @@FETCH_STATUS = 0
    BEGIN
        SET @sql = N'select @i=1 from ' + QUOTENAME(@databaseName,'[') + N'.sys.sysusers
                    where name=''' + @userName + ''''
        DECLARE @j INT= NULL
        EXEC sp_executesql @sql, N'@i int output', @i = @j OUTPUT
        IF ( @j IS NULL )     --數據庫不存在用戶,回到開始進行下次循環
        BEGIN
            FETCH NEXT FROM databaseNames INTO @databaseName
            CONTINUE
        END
        IF @userName IS NULL
                SET @userName=@loginName
        IF @newUserName IS NULL
                SET @newUserName=@newLoginName
        IF ( @j = 1 )
        BEGIN
                     if (@dbName is not null and @dbName=@databaseName) or @dbName is null
                  EXEC dbo.sp_DBA_userPermisionsClone @databaseName,@userName,@newUserName,@newLoginName,@pwd
        END
        FETCH NEXT FROM databaseNames INTO @databaseName
    END
    CLOSE databaseNames
    DEALLOCATE databaseNames
    FETCH NEXT FROM logins INTO @newLoginName, @loginName, @pwd,@dbName,@userName,@newUserName       
END
CLOSE logins
DEALLOCATE logins

注意:對於包含用戶,如果存在涉及到跨庫的包含用戶,需要單獨創建,具體參加《SQL Server 部分包含數據庫》

生成完腳本後,及時備份創建腳本或者執行生成的腳本。將新、舊賬戶對應關係及密碼導出到本機加密文件中,更新密碼字段爲NULL。同時檢查附近時點是否有對該庫進行備份的作業,確保新賬戶密碼沒有備份到數據庫備份文件中。

UPDATE login_create SET pwd=NULL

監控賬戶替換情況

新賬戶創建完成後,需要將舊、新賬戶及密碼發相關人員替換(開發或者IT),開啓就賬戶連接監控。我們可以執行使用查詢連接的腳本,查看哪些舊賬戶還沒有被替換掉:

--查詢指定連接數據庫賬戶的IP
SELECT distinct client_net_address,loginame,hostname,program_name
FROM sys.dm_exec_connections conn 
join master..sysprocesses procs
on conn.session_id=procs.spid
where loginame IN(SELECT loginName from login_create)

但對於哪些非頻繁連接的賬戶,使用上面的查詢並不一定能捕獲到,最好的方式是使用觸發器來監控:

--創建登陸信息日誌表
CREATE TABLE [dbo].[Login_Monitor](
      [Spid] [int] NULL,
      [Posttime] [datetime] NULL,
      [Servername] [varchar](30) NULL,
      [Hostname] [varchar](30) NULL,
      [Loginame] [varchar](30) NULL,
      [Logintype] [varchar](20) NULL,
      [ClientHost] [varchar](20) NULL,
      [Stat] [int] NULL
)
use master
GO
--==================================
-- 服務器登監控賬戶信息
--==================================
CREATE TRIGGER [LoginMonitor]
ON all server WITH EXECUTE AS 'sa'
FOR LOGON  
AS
BEGIN
      DECLARE @Data XML  
      DECLARE @PostTime NVARCHAR(24)  
      DECLARE @Spid NVARCHAR(6)  
      DECLARE @LoginName NVARCHAR(100)  
      DECLARE @HostName NVARCHAR(100) 
      DECLARE @ServerName NVARCHAR(20)  
      DECLARE @LoginType NVARCHAR(20)  
      DECLARE @ClientHost NVARCHAR(20)
      DECLARE @IP NVARCHAR(20)
      DECLARE @STAT INT = 0
      DECLARE  @IPint BIGINT=0
      
      SET @Data =             EVENTDATA()
      SET @HostName =         HOST_NAME() 
      SET @PostTime =         @Data.value('(/EVENT_INSTANCE/PostTime)[1]', 'NVARCHAR(24)')  
      SET @Spid =             @Data.value('(/EVENT_INSTANCE/SPID)[1]', 'nvarchar(6)')  
      SET @LoginName =      @Data.value('(/EVENT_INSTANCE/LoginName)[1]','NVARCHAR(100)')
      SET @ServerName =      @Data.value('(/EVENT_INSTANCE/ServerName)[1]','NVARCHAR(100)')
      SET @ClientHost =      @Data.value('(/EVENT_INSTANCE/ClientHost)[1]','NVARCHAR(100)')
      SET @LoginType =  @Data.value('(/EVENT_INSTANCE/LoginType)[1]', 'nvarchar(100)')
      SET @IP = @ClientHost
      
      IF @ClientHost<>'<local machine>'
      begin
            IF @LoginName in( SELECT loginName from login_create)
            BEGIN
                  INSERT INTO [Login_Monitor] 
                  VALUES(@SPID, @posttime,@ServerName,@hostname, @loginname,@LoginType,@IP,@STAT) 
            END
      END
END

經過一定週期的監控,確定所有賬戶已經替換後,接下來可以禁用原賬戶了。

 

禁用原賬戶

--生成禁用登陸帳戶腳本
SELECT 'ALTER LOGIN '+QUOTENAME(loginName,']') +'DISABLE'
FROM login_create

生成腳本後直接執行即可。

清理禁用的舊賬戶

通過觀察,沒有賬戶有報錯後,就可以清理舊的停用賬戶了。因爲刪除登陸賬戶時,沒有辦法直接關聯刪除其對應在各數據庫中的用戶,下面使用循環將停用舊登陸賬戶及對應各數據庫中的用戶刪除,腳本如下:

--刪除停用的帳戶,及各數據庫中對應的用戶
DECLARE @user AS VARCHAR(200)
DECLARE @databaseName AS VARCHAR(200)
DECLARE @sql AS VARCHAR(800)
--循環刪除停用的用戶
DECLARE curUsers CURSOR
FOR
SELECT  QUOTENAME(loginName, ']')
FROM    login_create
      
OPEN curUsers
FETCH NEXT FROM curUsers INTO @user
WHILE @@FETCH_STATUS = 0
BEGIN
    DECLARE databaseNameCur CURSOR
    FOR
    SELECT  name
    FROM    sys.databases
    WHERE   state = 0
    OPEN databaseNameCur
    FETCH NEXT FROM databaseNameCur INTO @databaseName
    WHILE @@FETCH_STATUS = 0
        BEGIN
            SET @sql = 'use ' + @databaseName + ';'
            SET @sql =@sql + 'drop user ' + @user
            BEGIN TRY
                EXEC(@sql)
                PRINT @sql
            END TRY
            BEGIN CATCH
                PRINT '數據庫' + @databaseName + '中不存在用戶' + @user
            END CATCH
            FETCH NEXT FROM databaseNameCur INTO @databaseName
        END
    CLOSE databaseNameCur
    DEALLOCATE databaseNameCur
    USE master
    SET @sql = 'drop login ' + @user
    PRINT @sql
    EXEC(@sql)
   
    FETCH NEXT FROM curUsers INTO @user
END
CLOSE curUsers
DEALLOCATE curUsers

 

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