我们已在启用内部激活的情况下运行 Service Broker。存储过程已签名。这个权限问题是如何发生的?我认为签名用户在调用过程时接管。为什么权限问题与签名用户无关?
916:服务器主体“{userName}”无法在当前安全上下文下访问数据库“{sisterDatabaseName}”。 - 线路:1
我们在同一台服务器上有多个数据库。这些数据库成对分组,数据库偶尔需要来回发送信息。通常,只需在数据库之间执行选择或插入时完全限定数据库名称即可完成此操作。
例如:
SELECT *
FROM dbname.dbo.tablename
但是,我们有一个流程可以从异步 SQL 执行中受益匪浅。
进入服务代理!我们运行了以下脚本:
ALTER AUTHORIZATION ON database::databasename TO someuser
ALTER DATABASE databasename SET NEW_BROKER WITH ROLLBACK IMMEDIATE
DECLARE @Password VARCHAR(20) = ''
DECLARE @char CHAR = ''
DECLARE @charI INT = 0
DECLARE @len INT = 20 -- Length of Password
WHILE LEN(@Password) < @len
BEGIN
SET @charI = ROUND(RAND()*73,0) + 49
SET @char = CHAR(@charI)
SET @Password += @char
END
SET @SQL = '
CREATE CERTIFICATE {certName}
ENCRYPTION BY PASSWORD = ''' + @Password + '''
WITH SUBJECT = ''Certificate to sign Internal Activation Procedure''
CREATE USER {certificateUserName} FROM CERTIFICATE {certName};
GRANT CONNECT TO PullConfirmRequestQueueProcessorUser;
ADD SIGNATURE TO [dbo].[{procedureName}]
BY CERTIFICATE {certName} WITH PASSWORD = ''' + @Password + '''
'
EXEC(@SQL)
本质上,我们正在创建一个只能分配给这个特定存储过程的证书。现在还好。这是在测试阶段。如果我们需要一种可以扩展到多个程序的更安全的解决方案,我们可以稍后再提出。我并不担心。但是,我希望这个测试能够发挥作用。
这是存储过程:
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[{procedureName}]') AND type in (N'P', N'PC'))
BEGIN
EXEC dbo.sp_executesql @statement = N'CREATE PROCEDURE [dbo].[{procedureName}] AS'
GRANT EXECUTE ON {procedureName} TO {username}
END
GO
ALTER PROCEDURE [dbo].[{procedureName}]
WITH EXECUTE AS OWNER
AS
BEGIN
SET NOCOUNT ON
BEGIN TRY
DECLARE @SSBSTargetDialogHandle UNIQUEIDENTIFIER;
DECLARE @RecvdRequestMessage XML;
DECLARE @RecvdRequestMessageTypeName sysname;
WHILE (1=1)
BEGIN
BEGIN TRANSACTION;
WAITFOR
( RECEIVE TOP(1)
@SSBSTargetDialogHandle = conversation_handle,
@RecvdRequestMessage = CONVERT(XML, message_body),
@RecvdRequestMessageTypeName = message_type_name
FROM dbo.{queueName}
), TIMEOUT 1000;
IF (@@ROWCOUNT = 0)
BEGIN
IF (@@TRANCOUNT > 0 ) ROLLBACK TRANSACTION;
BREAK;
END
IF @RecvdRequestMessageTypeName = N'{messageName}'
BEGIN
--Statements to process message in this database and "sister" database
END CONVERSATION @SSBSTargetDialogHandle;
END
ELSE IF @RecvdRequestMessageTypeName IN (N'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog',N'http://schemas.microsoft.com/SQL/ServiceBroker/Error')
BEGIN
END CONVERSATION @SSBSTargetDialogHandle;
END
COMMIT TRANSACTION;
END
END TRY
BEGIN CATCH
IF (@@TRANCOUNT > 0 ) ROLLBACK TRANSACTION;
INSERT INTO ServiceBrokerErrors(
MessageXml,
DialogHandle,
TimeLoggedUTC,
ErrorNumber,
ErrorSeverity,
ErrorState,
ErrorProcedure,
ErrorLine,
ErrorMessage)
VALUES(@RecvdRequestMessage,@SSBSTargetDialogHandle,GETUTCDATE(),ERROR_NUMBER(),ERROR_SEVERITY(),ERROR_STATE(),ERROR_PROCEDURE(),ERROR_LINE(),ERROR_MESSAGE())
END CATCH
END
GO
以下四个查询返回相同的值:
SELECT sid FROM {mainDatabase}.sys.database_principals WHERE name = 'dbo'
SELECT sid FROM {sisterDatabase}.sys.database_principals WHERE name = 'dbo'
SELECT owner_sid FROM sys.databases WHERE name = '{mainDatabase}'
SELECT owner_sid FROM sys.databases WHERE name = '{sisterDatabase}'
对于跨数据库访问,需要将证书复制到签名过程访问的每个数据库中。将证书复制到其他数据库的示例脚本:
USE {mainDatabase};
DECLARE @cert_id int = cert_id('{certName}')
DECLARE @public_key varbinary(MAX) = certencoded(@cert_id),
@private_key varbinary(MAX) =
certprivatekey(@cert_id,
'{password}',
'{password}');
DECLARE @sql nvarchar(MAX) =
'CREATE CERTIFICATE {certName}
FROM BINARY = ' + convert(varchar(MAX), @public_key, 1) + '
WITH PRIVATE KEY (BINARY = ' +
convert(varchar(MAX), @private_key, 1) + ',
DECRYPTION BY PASSWORD = ''{password}'',
ENCRYPTION BY PASSWORD = ''{password}'')';
EXEC {sisterDatabase}.sys.sp_executesql @sql;