以遠端電腦上的使用者身份訪問Com+應用

來源:互聯網
上載者:User

標籤:des   io   os   使用   ar   for   資料   sp   問題   

DELPHI程式員開發com+應用的速度是非常快的,其主要原因是其較好地封裝了com+的window
s底層功能,開發人員通過較為簡單的類繼承就避開了複雜的com+底層技術細節,使開發人員
將精力放在應用本身的功能上面。Delphi在封裝com+應用時採取了許多折衷,在保留通用性
的同時也避開了一些實現起來困難但是應用面不太廣的com+底層特性。這些避開的特性中最
令delphi com開發人員關心的就是安全特性。從delphi 5開始,有許多人都面臨過這樣的問
題:com應用開發出來並且在本機上運行一切正常,但是一旦分發出去實施遠端存取時,就無
法正常運行了。我自己有段時間在看到“拒絕訪問”錯誤提示時會本能的頭暈。其實認真追
究起來,還是因為自己對windows安全技術瞭解不多造成的。多年來我一直沒有發現國內有w
indows安全方面比較系統的資料和書籍,直到Keith Brownr的<windows安全性編程>中文版的
出現。正是基於這本書我才有了下面的一些實驗,也知道了為什麼我老是補拒絕的原因。下
面的討論只是我在解決自身現有代碼的安全訪問問題時,總結出的幾個小經難方法。建議願
意瞭解windows安全性的朋友去看一看<windows安全性編程>一書,你會發現windows的安全不
再神秘。這篇文章將會說明如何以遠程工作站上的使用者身份啟用com+對象,並以此使用者身份
訪問Interface。 1、 Delphi預設com+對象的遠程啟用

  Delph中遠程com+對象啟用一般通過TdispatchConnection及其子類來實現,實際代碼中
多用TDCOMConnection或TsocketConnectoion這兩個組件,TDCOMConnection組件最終調用Co
CreateInstanceEx建立com+對象。CoCreateInstanceEx (const clsid: TCLSID; unkOuter:
IUnknown; dwClsCtx: Longint; ServerInfo:PCoServerInfo;dwCount: Longint; rgmqRes
ults: PMultiQIArray): HResult。 TDCOMConnection在調用CoCreateInstanceEx時為pCoSe
rverInfo參數中的pAuthInfo傳遞了Null值,因此TdcomConnection在建立Com對象時使用的是
本機電腦登入者的使用者令牌。假若A電腦上的登入使用者Auser使用TDCOMConnection類串連
遠端電腦B上的com+對象,則B電腦會使用Auser的使用者名稱/密碼在B電腦上建立登入工作階段
並最終建立com+對象。但是一台windows工作站上的本機使用者只能在本地登入而無法在別的計
算機上登入,因此A電腦上的Auser就無法在B工作站上建立登入工作階段,當然也就無法建立c
om+對象,此時遠程工作站B會嘗試用Guest帳戶建立會話並使用該賬戶啟用com+對象。在這種
情況下,如果B工作站上的Guest賬戶沒有啟用或Guest沒有啟用com+對象的許可權,你就會看見
令人頭暈的提示“拒絕訪問”。看到這裡你是不對現在網上最“流行”的dcom配置方法有所
悟了呢。那個方法就是允許everyone訪問、啟用com對象、並且將“預設身分識別驗證層級”設定
成無。這種方法能夠使你的com應用可以“用了”,但是,它可以上“任何人”訪問。而且這
種設定你將無法利用com+基於角色的安全存取控制功能。 2、怎樣不用GUEST賬戶啟用這
個問題的實際上應該是:怎樣用遠程工作站上的使用者啟用遠程com對象。解決這個問題其實很
簡單:只要你在調用CoCreateInstanceEx時為它指定遠程工作站上的使用者名稱和密碼,只要用
戶名/密碼通過遠端電腦的驗證,並且該使用者被授予了“遠程啟用”com+對象的許可權,那麼
遠程工作站會用該使用者身份啟用com+對象。看一下代碼:

  var

   mts:IMTSXjpimsDB;

   ov:Variant;

   i:integer;

   cai:_CoAuthInfo;

   cid:_CoAuthIdentity;

   csi:COSERVERINFO;

   mqi:MULTI_QI;

   iid_unk:TGUID;

   idsp:IDispatch;

   wUser,wDomain,wPsw:WideString;

  begin

   wUser:=eduser.text;//使用者名稱

   wDomain:=edSvr.Text;//遠端電腦名

   wPsw:=edPsw.Text;//密碼

   cid.user:=pUnshort(@wUser[1]);

   cid.UserLength:=length(wUser);

   cid.Domain:=pUnshort(@wDomain[1]);

   cid.DomainLength:=length(wDomain);

   cid.password:=pUnshort(@wPsw[1]);

   cid.PasswordLength:=length(wPsw);

   cid.Flags:=2;

   //以上填充_CoAuthIdentity結構

   cai.dwAuthnSvc:=10;//winNt預設的鑒證服務

   cai.dwAuthzSvc:=0;

   cai.pwszServerPrincName:=wDomain;

   cai.dwAuthnLevel:=0;

   cai.dwImpersonationLevel:=3;//必須設定成類比

   cai.pAuthIdentityData:[email protected];

   cai.dwCapabilities:=$0800; //以上填充_CoAuthInfo結構

   FillChar(csi, sizeof(csi), 0);

   csi.dwReserved1:=0;

   csi.pwszName:=pwidechar(wdomain);

   csi.pAuthInfo:[email protected];

   //以上填充COSERVERINFO結構

   iid_unk:=IUnknown;

   mqi.IID:[email protected]_unk;mqi.Itf:=nil;mqi.hr:=0;

   Screen.Cursor:=crHourGlass;
olecheck(CoCreateInstanceEx(CLASS_MTSXjpimsDB,nil,CLSCTX_REMOTE_SERVER,@csi,1,@mqi));
這段代碼中除了最後實際調用CoCreateInstanceEx外,前面的代碼都是設定參數。這些參數的含義請大家參考msdn,除了使用者名稱、主機名稱
、密碼外,只有一個重要要部分要說明:cai.dwImpersonationLevel必須設定成允許類比(值
為3),否則遠端電腦將無法按提供的使用者/密碼建議網路會話。

3、不修改現有代碼,可以實現用遠端使用者身份啟用嗎?當然可以,我擴充了TDcomConnection類,為其加入了使用者名稱和
密碼,並修改其預設的DoConnect方法,使其在調用CoCreateInstanceEx時用指定的使用者名稱和
密碼填充參數。代碼如下:

  unit SecDComConnection;

  

  interface

  

  uses

   windows,SysUtils, Classes,ActiveX, DB, DBClient, MConnect,comobj,Midas;

  

  type

  

  {typedef struct _SEC_WINNT_AUTH_IDENTITY

   unsigned short __RPC_FAR* User;

   unsigned long UserLength;

   unsigned short __RPC_FAR* Domain;

   unsigned long DomainLength;

   unsigned short __RPC_FAR* Password;

   unsigned long PasswordLength;

   unsigned long Flags;

   SEC_WINNT_AUTH_IDENTITY, *PSEC_WINNT_AUTH_IDENTITY;

  }

   {typedef struct _COAUTHIDENTITY

   USHORT * User;

   ULONG UserLength;

   USHORT * Domain;

   ULONG DomainLength;

   USHORT * Password;

   ULONG PasswordLength;

   ULONG Flags;

  COAUTHIDENTITY;}

  

  {#define RPC_C_AUTHN_NONE 0

  #define RPC_C_AUTHN_DCE_PRIVATE 1

  #define RPC_C_AUTHN_DCE_PUBLIC 2

  #define RPC_C_AUTHN_DEC_PUBLIC 4

  #define RPC_C_AUTHN_GSS_NEGOTIATE 9

  #define RPC_C_AUTHN_WINNT 10

  #define RPC_C_AUTHN_GSS_SCHANNEL 14

  #define RPC_C_AUTHN_GSS_KERBEROS 16

  #define RPC_C_AUTHN_MSN 17

  #define RPC_C_AUTHN_DPA 18

  #define RPC_C_AUTHN_MQ 100

  #define RPC_C_AUTHN_DEFAULT 0xFFFFFFFFL

  }

  

  {#define RPC_C_AUTHZ_NONE 0

  #define RPC_C_AUTHZ_NAME 1

  #define RPC_C_AUTHZ_DCE 2

  #define RPC_C_AUTHZ_DEFAULT 0xFFFFFFFF }

  

  {

  #define RPC_C_AUTHN_LEVEL_DEFAULT 0

  #define RPC_C_AUTHN_LEVEL_NONE 1

  #define RPC_C_AUTHN_LEVEL_CONNECT 2

  #define RPC_C_AUTHN_LEVEL_CALL 3

  #define RPC_C_AUTHN_LEVEL_PKT 4

  #define RPC_C_AUTHN_LEVEL_PKT_INTEGRITY 5

  #define RPC_C_AUTHN_LEVEL_PKT_PRIVACY 6 }

  

  {SEC_WINNT_AUTH_IDENTITY_UNICODE=2 }

  

   pUnShort=^Word;

  

   pCoAuthIdentity=^_CoAuthIdentity;

   _CoAuthIdentity=record

   user:pUnShort;

   UserLength:ULONG;

   Domain:pUnShort;

   DomainLength:Ulong;

   password:pUnShort;

   PasswordLength:ulong;

   Flags:ulong;

   end;

  

   _CoAuthInfo=record

   dwAuthnSvc:DWORD;

   dwAuthzSvc:DWORD;

   pwszServerPrincName:WideString;

   dwAuthnLevel:Dword;

   dwImpersonationLevel:dword;

   pAuthIdentityData:pCoAuthIdentity;

   dwCapabilities:DWORD;

   end;

  

   TSecDComConnection = class(TDCOMConnection)

   private

   FCai:_CoAuthInfo;

   FCid:_CoAuthIdentity;

   FSvInfo:COSERVERINFO;

   FUser:WideString;

   FPassWord:WideString;

   procedure SetPassword(const Value: wideString);

   procedure SetUser(const Value: wideString);

   procedure SetSvInfo(const Value: COSERVERINFO);

   protected

   procedure DoConnect; override;

  

   public

   property SvInfo:COSERVERINFO read FSvInfo write SetSvInfo;

   constructor Create(AOwner: TComponent); override;

   procedure MySetBlanket(itf:IUnknown;const vCai:_CoAuthInfo);

   function GetServer: IAppServer; override;

   published

   property User:wideString read FUser write SetUser;

   Property Password:wideString read FPassword write SetPassword;

   end;

  

  procedure Register;

  

  implementation

  

  constructor TSecDCOMConnection.Create(AOwner: TComponent);

  begin

   inherited Create(AOwner);

   FillMemory(@Fcai,sizeof(Fcai),0);

   FillMemory(@FCid,sizeof(FCid),0);

   FillMemory(@FSvInfo,sizeof(FSvInfo),0);

   with FCai do begin

   dwAuthnSvc:=10;//RPC_C_AUTHN_WINNT

   dwAuthzSvc:=0;// RPC_C_AUTHZ_NONE

   dwAuthnLevel:=0;//RPC_C_AUTHN_LEVEL_DEFAULT

   dwImpersonationLevel:=3;

   pAuthIdentityData:[email protected];

   dwCapabilities:=$0800;

   end;

  end;

  

  procedure TSecDCOMConnection.DoConnect;

  var

   tmpCmpName:widestring;

   IID_IUnknown:TGUID;

   iiu:IDispatch;

   Mqi:MULTI_QI;

   qr:HRESULT;

  begin

   if (ObjectBroker) <> nil then

   begin

   repeat

   if ComputerName = ‘‘ then

   ComputerName := ObjectBroker.GetComputerForGUID(GetServerCLSID);

   try

   SetAppServer(CreateRemoteComObject(ComputerName, GetServerCLSID) as IDispatch);

   ObjectBroker.SetConnectStatus(ComputerName, True);

   except

   ObjectBroker.SetConnectStatus(ComputerName, False);

   ComputerName := ‘‘;

   end;

   until Connected;

   end

   else if (ComputerName <> ‘‘) then

   begin

   with fcid do begin

   user:=pUnshort(@fuser[1]);

   UserLength:=length(fuser);

   tmpCmpName:=ComputerName;

   Domain:=pUnshort(@tmpCmpName[1]);

   DomainLength:=length(TmpCmpName);

   password:=pUnShort(@FPassword[1]);

   PasswordLength:=length(FPassword);

   Flags:=2;//Unicode

   end;

   FSvInfo.pwszName:=pwidechar(tmpCmpName);

   FSvinfo.pAuthInfo:[email protected];

   IID_IUnknown:=IUnknown;

   mqi.IID:[email protected]_IUnknown;mqi.Itf:=nil;mqi.hr:=0;

   olecheck(CoCreateInstanceEx(GetServerCLSID,nil,CLSCTX_REMOTE_SERVER,@FSvinfo,1,@mqi));

   olecheck(mqi.hr);

   MySetBlanket(mqi.Itf,Fcai);

   qr:=mqi.Itf.QueryInterface(idispatch,iiu);

   olecheck(qr);

   MySetBlanket(IUnknown(iiu),FCai);

   SetAppServer(iiu);

   end

   else

   inherited DoConnect;

  end;

  

  function TSecDComConnection.GetServer: IAppServer;

  var

   QIResult: HResult;

  begin

   Connected := True;

   QIResult := IDispatch(AppServer).QueryInterface(IAppServer, Result);

   if QIResult <> S_OK then

   begin

   Result := TDispatchAppServer.Create(IAppServerDisp(IDispatch(AppServer)));

   end;

   MySetBlanket(IUnknown(Result),FCai);

  end;

  

  

  procedure TSecDCOMConnection.MySetBlanket(itf: IUnknown;

   const vCai: _CoAuthInfo);

  begin

   with vCai do

   CoSetProxyBlanket(Itf,dwAuthnSvc,dwAuthzSvc,pwidechar(pAuthIdentityData^.Domain),

   dwAuthnLevel,dwImpersonationLevel,pAuthIdentityData,dwCapabilities);

  end;

  

  procedure TSecDCOMConnection.SetPassword(const Value: wideString);

  begin

   FPassword := Value;

  end;

  

  procedure TSecDCOMConnection.SetSvInfo(const Value: COSERVERINFO);

  begin

   FSvInfo := Value;

  end;

  

  procedure TSecDCOMConnection.SetUser(const Value: wideString);

  begin

   FUser := Value;

  end;

  

  

  procedure Register;

  begin

   RegisterComponents(‘DataSnap‘, [TSecDComConnection]);

  end;

  

  end.

  

   代碼中有一些C風格的注釋,是因為delphi沒有為我們預定義這些變數和資料結構。如
何使用呢?將這個組件安裝在IDE中,並將其放到你的現有代碼的遠端資料模組中去,將原有
指向TDOCMConnection的資料集控制項設定成這個新的TSecDCOMConnection控制項。然後你可以在
遠端電腦中設定最嚴格的安全選項。但是要記住應該為你要使用的使用者佈建合適的許可權:
給予遠程啟用許可權、給予遠端存取許可權。 4、到現在還沒有談到訪問的問題。首先啟用和訪
問並不是一回事。一個使用者可能擁有啟用許可權但沒有存取權限,也有可能只有存取權限卻無
啟用許可權。前面談到CoCreateInstacnceEx可以用另一身份啟用物件並取得IunKnown指標的一
個本地引用。如果你直接用這個指標去取得IappServer介面並調用方法,那麼你很可能又會
見到“拒絕訪問”資訊。這是IUnKnown指標的本地引用存在於客戶機的進程中,再沒有做特
殊設定前,該指標繼承了客戶機進程的本地令牌,也就是說當用這個指標擷取遠程IappServ
er介面時,會用客戶機當前登入令牌調用QueryInterface,在調用過程中遠端電腦將有此
令牌中緩衝的使用者名稱和密碼進行再次登入驗證,當然此時又會被拒絕,而後遠端電腦再次
嘗試用GUEST帳戶登入並擷取com對象介面,此時若沒有找開GUEST存取權限,則用戶端訪問失
敗,windows返回“拒絕訪問”資訊。那麼怎樣才能使QueryInterface調用也使用遠端使用者身
份呢,這就要調用CoSetProxyBlanket強制設定本地介面引用使用遠端使用者的令牌。在上面的
代碼中,我用MySetBlanket封裝了該API,以便使用啟用時的使用者身份調用QueryInterface。
而後在取得的IappServer介面上再次調用MySetBlanket,保證在使用該介面時也採用遠程用
戶身份。

   MySetBlanket(mqi.Itf,Fcai);

   qr:=mqi.Itf.QueryInterface(idispatch,iiu);

   olecheck(qr);

   MySetBlanket(IUnknown(iiu),FCai); 為保證直接引用DataProvider的TclientD
ataSet也能按上述要求工作,在擴充的TSecDCOMConnection控制項中,重載了GetServer方法。
這樣TSecDCOMConnection已能完全替換TDCOMConnection實現便利的com+應用編程了。由於時
間倉促,寫這篇時很多術語沒有做解釋交待,因此可能會有一些不太好理解,但是出於為de
lphi Fans提供一個簡單的實現安全性com訪問的方法,我還是將這篇貼上來,主要是可以讓
需要的朋友直接複製代碼用在自己的應用上。使用TSecDCOMConnection後,伺服器方的com+
對象可以強制找開訪問檢查,並開啟組件級的訪問檢查。在開啟訪問檢查的情況下,必須將
伺服器中允許訪問com+對象的使用者名稱加入到角色中才能正確訪問。 (上述代碼在delphi7/wi
nXP sp2中調試通過,對於windows98和windows nt4.0及以下作業系統,由於CoCreateInsta
nceEx不能直接產生com+對象的安全上下文,因此代碼不可用)

 

以遠端電腦上的使用者身份訪問Com+應用

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.