這是我的經驗。開發WCF Service時先在本機上寫Service及Cient,Visual Studio及.NET Framework做掉了大部分的Dirty Work,拖拖拉拉,動動小指,一段WCF程式就寫出來了,開開心心地做完測試,將包含WCF Service的ASP.NET部署到遠端機器上,再把Client端的Config指向遠端主機,理論上似乎就可以改測遠端主機連線模式:

<client>
    <endpoint address="httq://remoteMachine/WCFService/Service.svc" binding="wsHttpBinding"
        bindingConfiguration="WSHttpBinding_IService" contract="Darkthread.IService"
        name="WSHttpBinding_IService">
        <identity>
            <dns value="localhost" />
        </identity>
    </endpoint>
</client>

但是,噹~~~ 一頭撞到鐵板,在呼叫WCF Method時得到以下錯誤:

System.ServiceModel.Security.SecurityNegotiationException was unhandled
  Message=The caller was not authenticated by the service.
  Source=mscorlib
  StackTrace:
    Server stack trace:
       at System.ServiceModel.Security.IssuanceTokenProviderBase`1.DoNegotiation(TimeSpan timeout)
       at System.ServiceModel.Security.SspiNegotiationTokenProvider.OnOpen(TimeSpan timeout)
       ... 略 ...

由於先前WCF的大小瑣事都是VS打理,遇到了這個問題,讓我丈二金剛摸不著腦袋,費了好些功夫才理出頭緒。(不過先聲明,我對WCF的安全性研究得很粗淺,以下只是個人一些簡單的心得,若有前輩高人發現其中有誤,還請不吝指正。)

故事要從預設值開始談起,Visual Studio透過精靈建立WCF Service時,預設會採用較安全嚴謹的WSHttpBinding,並使用Windows認證方式(關於BasicHttpBinding與WSHttpBinding,CodeProject有一篇淺顯的介紹文章)。當WCF Service與Client在同一台執行時,身份認證不會是問題,但一旦Service被放到另一台機器上,甚至與Client並不屬於同一個Domain,此時,Windows身份認證便會成為問題。

因此我們需要額外指定身份認證設定(紅色部分),就可以解決前述的認證問題囉! (2010-08-02補充: 實務上多會採取Config檔儲存加密值的方式保存帳號密碼資料,此處僅為示意)

static void Main(string[] args)
{
    Darkthread.ServiceClient sc = new Darkthread.ServiceClient();
    sc.ClientCredentials.Windows.ClientCredential.UserName = "username";
    sc.ClientCredentials.Windows.ClientCredential.Password = "password";
    sc.ClientCredentials.Windows.ClientCredential.Domain = "domainName";

    string r = sc.DoWork("Jeffrey");
    Console.WriteLine(r);
    Console.Read();
}

除了指定認證身份外,還有另一個做法: 停用安全認證及加密機制。但這麼做會增加資安風險,使用前請自行評估適切性。

要停用WCF的安全認證機制,Service端請加入以下黃底部分的設定:

  </behaviors>
  <bindings>
    <wsHttpBinding>
        <binding name="noSecBinding">
            <security mode="None" />
        </binding>
    </wsHttpBinding>
  </bindings>

  <services>
   <service behaviorConfiguration="WCFService.ServiceBehavior" name="WCFService.Service">
    <endpoint address="" binding="wsHttpBinding" bindingConfiguration="noSecBinding"
        contract="WCFService.IService">
     <identity>
      <dns value="localhost" />
     </identity>
    </endpoint>
    <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
   </service>
  </services>
</system.serviceModel>

Client端也要配合調成security mode="None"

<bindings>
    <wsHttpBinding>
        <binding name="WSHttpBinding_IService" ...略...  allowCookies="false">
            <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
                maxBytesPerRead="4096" maxNameTableCharCount="16384" />
            <reliableSession ordered="true" inactivityTimeout="00:10:00"
                enabled="false" />
            <security mode="Message">
                <transport clientCredentialType="Windows" proxyCredentialType="None"
                    realm="" />
                <message clientCredentialType="Windows" negotiateServiceCredential="true"
                    algorithmSuite="Default" />
            </security>
        </binding>
    </wsHttpBinding>
</bindings>

如此,就可以省去身份認證過程,直接使用WCF Service囉!


Comments

# by ChrisTorng

在 client 端程式自動填入 Windows 帳號,會讓有心人士可以用 reflector (也許用記事本就看得到字串?) 取得伺服器帳號。這點有沒有辦法克服?

# by Jeffrey

to Chris Torng, 如果Client與Server同屬一個Domain,可使用Server所認可的Domain Account執行Client,就可以不用寫死UserName/Password,直接連線即可(如同在localhost的狀況)。(若無Domain,我記得也可在Client/Server建立同名同密碼帳號產生類似效果)

# by ChrisTorng

若希望不要求使用 Domain,也不建同帳號,也就是允許任意的 client 都能透過這個 WCF 連線 server,只是不想將帳密洩露出去。是否有辦法加密該 server 帳號/密碼? 或者透過什麼憑證、PKI... 之類技術可以辦到? 我目前能想得到的唯一爛招是 .NET 程式模糊化...

# by Tom

To Chris: "也就是允許任意的 client 都能透過這個 WCF 連線 server", 這不就是 "不要認證" 的意思? 既然不要認證, 何來帳號密碼洩漏的問題呢? 你提出的問題真是令人摸不透啊~~

Post a comment