程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> .NET實例教程 >> 調試COM+應用的安全性

調試COM+應用的安全性

編輯:.NET實例教程
調試COM+應用的安全性

  這是一個比較簡單但可以被用來測試遠程訪問COM+數據庫的應用,在服務端只使用了Delphi
中TClIEntDataset的直接數據訪問功能,理論上不需要任何後端數據庫驅動程序支持,
這樣也就減少了程序在調試過程中的不可控因素,可以讓我們將關注點放在核心的客戶遠程
訪問問題上。
  先簡單介紹下服務器端的代碼:
unit uDemoRmtDB;

{$WARN SYMBOL_PLATFORM OFF}

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ComServ, ComObj, VCLCom, StdVcl, bdemts, DataBkr, DBClIEnt,
  MtsRdm, Mtx, MTSDemoSvr_TLB, DB, Provider;

type
  TMTSDemo = class(TMtsDataModule, IMTSDemo)
    ClientDataSet1: TClIEntDataSet;
    ClientDataSet1Name: TStringFIEld;
    ClientDataSet1Address: TStringFIEld;
    ClientDataSet1PhoneNum: TIntegerFIEld;
    ClientDataSet1HandPhone1: TIntegerFIEld;
    ClientDataSet1HandPhone2: TIntegerFIEld;
  private
    { Private declarations }
  protected
    class procedure UpdateRegistry(Register: Boolean; const ClassID, ProgID: string); override;
    procedure GetDemoData(out vData: OleVariant); safecall;
    procedure SaveDemoData(vData: OleVariant); safecall;
  public
    { Public declarations }
  end;

var
  MTSDemo: TMTSDemo;

implementation

{$R *.DFM}

class procedure TMTSDemo.UpdateRegistry(Register: Boolean; const ClassID, ProgID: string);
begin
  if Register then
  begin
    inherited UpdateRegistry(Register, ClassID, ProgID);
    EnableSocketTransport(ClassID);
    EnableWebTransport(ClassID);
  end else
  begin
    DisableSocketTransport(ClassID);
    DisableWebTransport(ClassID);
    inherited UpdateRegistry(Register, ClassID, ProgID);
  end;
end;

procedure TMTSDemo.GetDemoData(out vData: OleVariant);
begin
  with ClIEntDataSet1 do begin
    LoadFromFile('MyHandPhone.XML');
    vData:=data;
  end;


end;

procedure TMTSDemo.SaveDemoData(vData: OleVariant);
begin
  with ClIEntDataSet1 do begin
    Data:=vData;
    SaveToFile('MyHandPhone.XML');
  end;
end;

initialization
  TComponentFactory.Create(ComServer, TMTSDemo,
    Class_MTSDemo, ciMultiInstance, tmApartment);
end.
  這段代碼中只是簡單在服務器端實現了GetDemoData和SaveDemoData兩個接口方法,
分別用於獲取服務器端的數據和將數據保存至服務器。數據文件是MyHandPhone.XML,
從文件名上大家也能看出這是一個不能再簡單的電話簿。這個文件可以直接由TClIEntDataset
的屬性編輯器生成,生成過程就不詳細介紹了,大家可以在MTS數據模塊中的TClIEntDataset
控件上右鍵點擊觀查其功能菜單項。
  服務器代碼非常簡單相信方家不值一曬。
============================
  下面是客戶端的代碼,客戶端的GUI上只是一個DBGird控件和幾個TButton,用於浏覽和
調用遠程服務器上的方法。比較特殊的是,為了觀察接口的引用計數,我在GUI上放了個TTimer
控件,用於監視接口的引用計數。
  GUI主窗體的代碼如下:

  unit MTSDemoClnt platform;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, Grids, DBGrids, DB, DBClIEnt, MConnect, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    DataSource1: TDataSource;
    ClientDataSet1: TClIEntDataSet;
    DBGrid1: TDBGrid;
    btGetData: TButton;
    btSaveData: TButton;
    btExit: TButton;
    edRef: TEdit;
    Timer1: TTimer;
    procedure btGetDataClick(Sender: TObject);
    procedure btSaveDataClick(Sender: TObject);
    procedure btExitClick(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

uses comobj,MTSDemoSvr_TLB, uRmtObj;

{$R *.dfm}

var
  it{,it2,it3,it4}:IMTSDemoDisp;

procedure TForm1.btGetDataClick(Sender: TObject);
var
  tmp:OleVariant;
  //IID_IUnknown:TGUID;
begin
  //IID_IUnknown:=IUnknown;
  it:=IMTSDemoDisp(DoConnect(
      @CLASS_MTSDemo,@IID_IMTSDemo,'omiga','ibrow','ibrow'));
  it.GetDemoData(tmp);
  ClIEntDataSet1.Data:=tmp;
//  it2:=it;
//  it3:=it;
//  it4:=it;
end;

procedure TForm1.btSaveDataClick(Sender: TObject);
begin
  if Assigned(it) then begin
    it.SaveDemoData(ClIEntDataset1.data);
  end;
end;

procedure TForm1.btExitClick(Sender: TObject);
begin
  it:=nil;//主動清除接口引用,用於測試關閒接口時,遠程服務器上的認證反應
  close;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var
  i:integer;
begin
  if Assigned(it) then begin
    i:=IUnknown(it)._AddRef;
    i:=IUnknown(it)._Release;
    edRef.Text:=inttostr(i);
  end;
end;

end.

  主窗體代碼中需要說明的的代碼是 btGetDataClick(Sender: TObject),其中我用
自編的過程DoConnect()來獲取遠程計算機上的dispinterface接口,這裡的關鍵點就在
DoConnect()上,該過程在uRmtObj單位無中實現,其代碼如下:
unit uRmtObj platform;

interface

uses Windows,comobj,activex;

type

   pUnShort=^Word;

   pCoAuthIdentity=^_CoAuthIdentity;
   _CoAuthIdentity=record
     user:PWideChar;
     UserLength:ULONG;
     Domain:PWideChar;
     DomainLength:Ulong;
     passWord:PWideChar;
     PassWordLength:ulong;
     Flags:ulong;
   end;

  _CoAuthInfo=record
    dwAuthnSvc:DWord;
    dwAuthzSvc:DWord;
    pwszServerPrincName:PWideChar;
    dwAuthnLevel:DWord;
    dwImpersonationLevel:DWord;
    pAuthIdentityData:pCoAuthIdentity;
    dwCapabilitIEs:DWord;
  end;


Function MySetBlanket(var itf: IUnknown; const vCai: _CoAuthInfo):HRESULT;
function DoConnect(const Class_IID,itf_iid:PIID; computer,username,psw:WideString):IUnknown;

implementation

Function MySetBlanket(var itf: IUnknown; const vCai: _CoAuthInfo):HRESULT;
begin
 with vCai do  begin
   result:=CoSetProxyBlanket(Itf,dwAuthnSvc,dwAuthzSvc,pwidechar(pAuthIdentityData^.Domain),
    dwAuthnLevel,dwImpersonationLevel,pAuthIdentityData,dwCapabilitIEs);
 end;
end;

function DoConnect(const Class_IID,itf_iid:PIID; computer,username,psw:WideString):IUnknown;
var
 FCai:_CoAuthInfo;
 FCid:_CoAuthIdentity;
 FSvInfo:COSERVERINFO;
 //tmpCmpName:widestring;
 //IID_IUnknown:TGUID;
 //iiu:IDispatch;
 Mqi:MULTI_QI;
 Size: DWord;
 LocalMachine: array [0..MAX_COMPUTERNAME_LENGTH] of char;
 //qr:HRESULT;
begin
  Result:=nil;
  if Length(computer)>0 then begin
    size:=sizeof(LocalMachine);
    if GetComputerName(LocalMachine,size) and (computer <> LocalMachine) then
    begin
      FillMemory(@Fcai,sizeof(Fcai),0);
      FillMemory(@FCid,sizeof(FCid),0);
      FillMemory(@FSvInfo,sizeof(FSvInfo),0);
      with fcid do begin
        user:=pwideChar(userName);//pUnshort(@userName[1]);
        UserLength:=length(username);
        Domain:=pWideChar(Computer);//pUnshort(@computer[1]);
        DomainLength:=length(computer);
        passWord:=pWideChar(psw);//pUnShort(@psw[1]);
        PassWordLength:=length(psw);
        Flags:=2;//Unicode 字符串
      end;
      with FCai do begin
        dwAuthnSvc:=10;//RPC_C_AUTHN_WINNT  NTML認證服務
        dwAuthzSvc:=0;// RPC_C_AUTHZ_NONE
        dwAuthnLevel:=0;//RPC_C_AUTHN_LEVEL_DEFAULT 默認級別
  

      dwImpersonationLevel:=3;//身份模擬
        pAuthIdentityData:=@fcid;
        dwCapabilitIEs:=$0800;//靜態跟蹤
     end;
      FSvInfo.pwszName:=PWideChar(computer);
      FSvinfo.pAuthInfo:=@Fcai;

      //IID_IUnknown:=IUnknown;
      //mqi.IID:=@IID_IUnknown;mqi.Itf:=nil;mqi.hr:=0;

      with mqi do begin
        iid:=itf_iid;
        itf:=nil;
        hr:=0;
      end;
      //以遠程用戶身份激活並取得接口引用
      olecheck(CoCreateInstanceEx(class_iid^,nil,CLSCTX_REMOTE_SERVER,@FSvinfo,1,@mqi));
      olecheck(mqi.hr);
      //對取得的接口引用,要再次設置其安全屬性為遠程用戶,否則返回的指針將仍然
      //使用本地用戶進程的安全屬性向遠程發起調用,此時的結果就是"拒絕訪問"
      olecheck(MySetBlanket(mqi.Itf,Fcai));
      //qr:=mqi.Itf.QueryInterface(IID_IMySendKey,result);
      //olecheck(qr);
      //MySetBlanket(mqi.itf,FCai);
      result:=mqi.itf;
    end;
  end
  else
    OleCheck(CoCreateInstance(class_iid^, nil, CLSCTX_INPROC_SERVER or
      CLSCTX_LOCAL_SERVER, itf_iid^, Result));
end;

end.

   過程DoConnect()用指定的遠程計算機上的用戶名/密碼來激活遠程服務器並獲得指定的接口
引用,定義如下
   function DoConnect(const Class_IID,
             itf_iid:PIID;
             computer,
             username,
             psw:WideString):IUnknown;
      Class_IID是遠程MTS對象的類ID,itf_iid是要取得的接口指針ID(實際是指向一個TGUID
結構的指針)。computer是遠程計算機的名稱,username是遠程計算機上的用戶名,psw是該用
戶的密碼。DoConnect返回一個指定接口類型的引用。由於返回接口類型為IUnknown,所以在具
體使用時要做強制類型轉換。


      示例:
        it:=IMTSDemoDisp(DoConnect( @CLASS_MTSDemo,
                     @IID_IMTSDemo,
                                    'omiga','ibrow','ibrow'));

     將服務器端編譯並安裝到遠程計算機的COM+應用中,然後運行客戶端,然後可以進入
調試了。
  調試的步驟:
   1、打開遠程計算機上的"事件查看器",我的建議是運行dcomcnfg.exe,使用其中的事件
查看器,這樣你在高度過程中可以用dcomcnfg來隨時調整遠程計算機上的安全設置。
   2、支行本地計算機上的dcomcnfg.exe,切換到事件查看器。
   3、將遠程計算機和本地計算上"應用程序","安全性","系統"三個日志全部清空
      4、 在urmtObj單元doconnect()過程中設斷點,建議最少在這兩行上高斷點:
            olecheck(CoCreateInstanceEx(class_iid^,nil,CLSCTX_REMOTE_SERVER,@FSvinfo,1,@mqi));
            olecheck(MySetBlanket(mqi.Itf,Fcai));
          在主窗體btExitClick()中下述代碼上設斷點
              it:=nil;
       5、每個斷點采用調試模式的CPU窗口模式逐步跟進,並注意密切觀查遠程計算機和本地
計算機上的事件日志。
       6、將遠程計算機上com安全認證由最嚴格向最松散調整,將在每一個安全級別上跟蹤代碼
運行情況。
    好了,經過上述的試驗後,你應該最少有以下幾點認識:
    1、遠程計算機上要產生多次的用戶登錄認證
    2、“拒絕訪問”提示在本地和遠程計算機上都有提示,但內容的詳細程序有別。要解決
    "拒絕訪問"問題,要同時參考兩台計算機上的日志。建議你在遠程計算機上用組策略編輯
    器打開帳戶的登錄、注銷、提權審核,這樣你的日志會更詳細,有助於你解決問題
    3、遠程計算機上的com默認模擬級別可能要設置成"模擬"。
    4、遠程計算機上,你應該將用於遠程訪問COM+接口的用戶加入到  "訪問權限"及
    "啟動和激活權限"中去,並且你可以控制啟動、激活、訪問三個階段的安全性。
    5、如果你的遠程系統是Windows server 2003,別忘記將用來訪問的用戶加入到com+應用
    的CreatorOwner角色裡,如何加?自己找......呵呵。
    6、源代碼在我的資源中有打包下栽。
    7、調試完了,告訴我你的心得,並且幫我解決個問題:
      為什麼接口注銷後,遠程系統安全日志中會出現兩個用客戶計算機用戶名進行的失敗登錄?
    8、如果怎麼整都無法完成遠程訪問,別罵我,我的代碼沒問題,你的客戶和遠程計算機安全設置
   有問題。建議看看我的其他文章(不許拍黑磚!!!!)
    9、請將你的數據庫文件myhandphone.XML主在windows系統的搜索目錄裡,剛開始最後放在Windows\system32目錄裡,否則你會看到令你頭暈的、無法解釋的拒絕訪問提示。,為什麼?
    應為com+應用是由dllhost.exe這個系統程序以服務賬戶創建的,它不會在你的dll的當前目錄
    中尋找其他文件。同時,請注意正確設置數據文件的訪問權限,因為如果你用非管理員賬號
    遠程訪問它,很可能會因為只有讀而沒有寫的權利,那就又會出問題。
  10、有許多代碼被注釋掉了,你可以試著去掉注釋,再看看運行中的反應。注意是事件中的反應。
   祝Delphi同志們好運。
 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved