使用WSE實現WebService安全—-我的第一篇

傑克.陳發表於2015-03-20
原文:
使用WSE實現Web Service安全—-我的第一篇

WSE(Web Services Enhancements)是微軟為了使開發者通過.NET建立出更強大,更好用的Web Services而推出功能增強外掛。現在最新的版本是WSE2.0(SP2).本文描述瞭如何使用WSE2.0中的安全功能增強部分來實現安全的Web Services。WSE的安全功能增強實現的是WS-Security標準,此標準是WebService自己的安全協議,由IBM, BEA, Microsoft等聯合制定,所以在.NET和Java系統上都可實現。

    我主要是根據WSE的文件說明和自己使用體會(其實多半也是按照文件畫瓢,呵呵)而寫,有錯誤之處請指出。另外,這是用的都是2.0,它與WSE1.0相比,變化很大,尤其在安全方面。

    還有一點注意,其實通過WSE實現安全有兩種途徑:一種就是我們下面要介紹的通過編寫程式碼的方法;另外一種是直接編寫策略檔案(XML文件),這兩種方法本質上都是通過對傳遞的SOAP訊息進行設定,如增加使用者訊息,加解密,簽名驗證等,來實現安全功能的。但本人對第二種方法不太熟悉,也沒時間研究,就不寫了,呵呵。

 

一、使用使用者名稱和口令驗證Web Services呼叫者身份。

    原理很簡單:客戶端通過SOAP擴充套件,在SOAP訊息中加入使用者名稱和口令(明文或加密),傳送給Web Services端;服務端接到訊息後,同樣通過擴充套件從訊息上下文中得到使用者名稱和口令,再進行身份驗證和其他操作。下面是實現步驟:

    客戶端:

    1.新增Microsoft.Web.Services2和客戶端要訪問的Web Services引用,沒有什麼好說的。當然,這兩個引用是必須的,你可能還需要客戶端需要新增其他引用。

    2.修改從Web Services引用生成的本地proxy類,這個類的程式碼在引用生成Reference檔案中。從.NET開發環境右邊的解決方案資源管理器裡開啟你要操作的Web Services引用資料夾,開啟Reference.map節點,就可以看到Reference.cs或Reference.vb檔案。如果你沒有看到這些檔案,可能是沒有顯示所有檔案,你需要在解決方案資源管理器或命令選單“專案”裡設定“顯示所有檔案”。找到Reference檔案後,開啟它,在proxy類的宣告處將類的繼承父類改成Microsoft.Web.Services2.WebServicesClientProtocol.因為只有這樣,proxy類才能訪問WSE提供的SOAP擴充套件。注意,如果更新了Web服務的引用,則需要重新把繼承類修改。

    3.通過UsernameToken類的例項新增使用者名稱與口令。UsernameToken屬於Microsoft.Web.Services2.Security.Tokens名稱空間。假設Web Services的本地proxy類名稱為WebServer.WebService,使用者名稱為Username,使用者口令為Userpwd,則程式碼可以如下所示(vb.net,下同):

    `生成本地proxy類例項

    Dim mywebserv as New WebServer.WebService

    `生成UsernameToken類例項,將使用者名稱,使用者口令和口令傳送方式寫在例項中

    Dim untoken As UsernameToken = New UsernameToken(Username, Userpwd, PasswordOption.SendPlainText)

    `設定SOAP訊息有效期,以確減少訊息即使被其他使用者截獲後重新使用的可能性為,這裡設定為30秒,但要注意不同系統時鐘同步的問題。

mywebserv.RequestSoapContext.Security.Timestamp.TtlInSeconds = 30

    `將UsernameToken例項加在SOAP訊息上下文中

    mywebserv.RequestSoapContext.Security.Tokens.Add(untoken)

    `呼叫Web服務的方法WebMethod

    mywebserv.WebMethod

    這裡需要說明口令傳送方式的設定。口令傳送方式為列舉型資料:SendNone,SendHashed,SendPlainText.分別為不傳送口令,傳送口令雜湊值和傳送口令明文,上面的例子是使用傳送明文。如果選擇SendNone,表示服務端不要驗證口令,這個安全級別就很低,而且如果服務需要對傳遞的SOAP訊息簽名,則客戶端要單獨提供口令對其簽名;SendHashed是指傳送的是口令的SHA-1雜湊值,這種情況下口令保護安全,這也是微軟推薦的最好方式。但它需要伺服器端通過編寫程式碼和config檔案設定自己來驗證使用者身份,具體驗證方法在下面的伺服器端設定會講到;SendPlainText是口令以明文方式傳遞,如果採取這種方式,上述程式碼中的UsernameToken最好加密,否則口令很容易被截獲。加密方法以後章節將詳細論述;另外,如果伺服器端選擇讓WSE自動根據Windows活動目錄域的使用者進行身份驗證,那這裡必須選擇傳送明文。

   

    服務端:

    1.首先在Web Services的配置檔案Web.config裡新增配置WSE的元素,這也是.NET開發的系統使用WSE最基本的一步。WSE文件上說如果服務的呼叫端是ASP.NET系統時才需要這一步的配置,如果是普通的Client程式則不需要加這個標識。可我測試發現即使呼叫端是Client程式,還是需要加這個配置。下面是完整的配置文件。

        <configuration>

              <system.web>

                <webServices>

                  <soapExtensionTypes>

                    <add type=”Microsoft.Web.Services2.WebServicesExtension, Microsoft.Web.Services2,Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35″ priority=”1″ group=”0″/>

                  </soapExtensionTypes>

                </webServices>

              </system.web>

    </configuration>

    當然,一般情況下你只需要在Web.config的<system.web>…</system.web>之間新增<webServices>…</webServices>部分。另外,add元素的資料最好寫成一行,否則可能會出錯。

    2.配置服務端的呼叫身份驗證行為,這一步是可選的。

    如前所述,呼叫方發過來的SOAP訊息中已加入了使用者資訊,服務端要做的工作是將這些資訊解析出來,再根據一定規則對比驗證,並返回結果。

上述處理過程也有兩種選擇:一是讓WSE自動驗證,我們自己的程式碼和配置檔案再也不用做什麼了。這種情況下,WSE在服務端收到呼叫方的SOAP訊息後,從裡面的UsernameToken中取出使用者名稱和口令,這也是為什麼前面提到過的自動驗證下使用者口令必須明文傳送的原因,取出使用者名稱和口令後,WSE是基於系統所在Windows域的使用者進行判別和驗證的。也就是說,WSE從活動目錄裡的使用者列表遍歷,尋找是否存在和所接收到的使用者名稱/口令匹配的有效使用者使用者帳號,如果未找到匹配使用者,則返回撥用者未被授權的錯誤。由此可見,這種方法下應用系統及使用者需要和Windows域緊密捆綁,缺乏靈活性並且不適合與已有業務系統對接。因此在實際應用中更多的應該是用下面第二種方法。

這種方法的核心是Web Services通過繼承UsernameTokenManager類,並過載AuthenticateToken方法實現的。

    a.首先宣告一個從UsernameTokenManager繼承下來的類。

    Public Class CustomUsernameTokenManager

        Inherits UsernameTokenManager

    這裡面有一個訪問許可權問題,為了使經過授權的程式集才能訪問這個類,你還需要給它在宣告時加上一些訪問限定。因為能夠訪問非託管程式碼(UnmanagedCode)的程式集信任級別都是比較高的,所以可以要求能訪問此類的程式集都是可以訪問UnmanagedCode的。這樣上面的宣告就變成如下形式:

<SecurityPermission(SecurityAction.Demand,Flags:= SecurityPermissionFlag.UnmanagedCode)> Public Class CustomUsernameTokenManager

                    Inherits UsernameTokenManager

當然你也可以配置成其他許可權要求,只要更改Flags的SecurityPermissionFlag列舉值即可。

b.在程式碼中過載AuthenticateToken方法。服務端接收到含有UsernameToken例項的SOAP訊息後,WSE將UsernameToken反序列化,並呼叫VerifyToken方法,而VerifyToken方法在執行過程中又會呼叫AuthenticateToken方法,這個方法會返回一個口令值,WSE會拿它與UsernameToken中的口令進行對比。如果傳送的口令為明文(UsernameToken.PasswordOption=SendPlainText),則直接對比;如果傳送口令為雜湊值(UsernameToken.PasswordOption=SendHashed),則WSE會對這個返回值做一個SHA-1雜湊運算,再將結果與UsernameToken中的口令進行對比。如果不一致,則返回使用者未被授權的錯誤。上述過程全部是自動完成的,因此我們要做的工作就是過載AuthenticateToken方法,在裡面實現返回正確的使用者口令,用於對比和驗證。這其實就相當於WSE1.0裡面的PasswordProvider.

過載AuthenticateToken方法實現的邏輯由系統根據具體要求來定,比如根據使用者名稱去資料庫裡尋找相應的使用者口令,或者從LDAP中等等。這裡就照找了WSE文件上的例子,返回的口令和提交的使用者名稱相同。

Protected Overrides Function AuthenticateToken(ByVal userName As UsernameToken) As String

` Ensure that the SOAP message sender passed a UsernameToken.

        If userName Is Nothing Then

            Throw New ArgumentNullException

        End If

               

        ` This is a very simple provider.

        ` In most production systems the following code

        ` typically consults an external database of (userName,hash)

        ` pairs. For this example, it is the UTF-8

        ` encoding of the user name.

Dim encodedUsername As Byte() = _

System.Text.Encoding.UTF8.GetBytes(userName.Username)

        Return System.Text.Encoding.UTF8.GetString(encodedUsername)

End Function

               

c.配置web.config檔案,告知Web Services使用哪個類來驗證使用者身份。

首先在檔案中加入標明使用WSE2的元素:

    <configuration>

        <configSections>

            <section name=”microsoft.web.services2″

                      type=”Microsoft.Web.Services2.Configuration.WebServicesConfiguration, Microsoft.Web.Services2, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35″ />

        </configSections>

    </configuration>

這裡要注意的是一定要把<configSections>…</configSections>寫到整個<configuration>…</configuration>裡的最前面,否則會出現“響應內容型別為 “text/html;charset=utf-8”,但應該是”text/xml””的錯誤。

    其次配置使用UsernameTokenManager子類

    <configuration>

        <microsoft.web.services2>

            <security>

                <securityTokenManager xmlns:wsse=”http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd”qname=”wsse:UsernameToken” type=”MyNamespace.CustomUsernameTokenProvider,MyAssemblyName,Version=2.0.0.0,Culture=neutral,PublicKeyToken=81f0828a1c0bb867″ />

            </security>

        </microsoft.web.services2>

    </configuration>

    在這裡面type屬性的值要保持在一行,”MyNamespace.CustomUsernameTokenProvider必須是派生類有效的類層次名,MyAssemblyName為程式集名稱。另外Version,Culture和PublicKeyToken等屬性可以省略 。

    這樣,我們就完成了在Web Service呼叫最基本的基於使用者名稱/口令的驗證。以後各項安全功能的實現都以它為基礎。

 

二、使用使用者名稱和口令對SOAP訊息簽名

    上述過程雖然實現了Web Services使用者身份確認。但它不能保證Web Services接收到的SOAP訊息就是所宣告使用者所傳送的,因此在實際的應用中是還需要呼叫方對SOAP訊息做一次簽名,並將簽名連同訊息一起傳送;服務端接收到訊息後,除了驗證使用者身份外,還需要對其中的簽名做一次認證。以確定訊息在傳輸過程中沒有被更改過,而驗證的使用者就是對訊息簽名的使用者。

    客戶端:

    只需要在原有客戶端SOAP訊息中新增簽名即可,如下所示,黑體為新加程式碼:

    mywebserv.RequestSoapContext.Security.Tokens.Add(untoken)

    mywebserv.RequestSoapContext.Security.Elements.Add(New _

MessageSignature(untoken))    

mywebserv.WebMethod

    上述程式碼就是客戶端根據UsernameToken的例項生成一個簽名,然後把簽名加在SOAP訊息裡,具體是WS-Security擴充套件的SOAP訊息頭。

    服務端:

    其實服務端無需改動什麼。只要客戶端傳送的SOAP訊息裡面包含了簽名,服務端就會自動驗證簽名的有效性。服務端首先驗證使用者名稱/口令,然後使用客戶端傳遞的使用者名稱和自己獲得的口令(WSE自動從Windows活動目錄中獲得,或者通過過載的AuthenticateToken的方法)對簽名進行驗證。如果驗證失敗,說明訊息在傳遞過程中被更改過或者不是當前呼叫使用者所籤,返回相應錯誤。

    WSE的幫助文件上這一塊加了一個判斷SOAP裡是否包含簽名的函式,通過它可以獲得簽名的一些相關資訊,並不是必須的。有興趣的朋友可以自己去看。

 

三、使用證照驗證身份並對SOAP訊息簽名

    使用使用者名稱/口令存在固有缺點,因此在某些安全要求較嚴格的系統中我們還需要使用證照來完成對使用者身份的驗證。關於PKI/CA的基礎知識這裡就不介紹了,大家可以去看相關資料。

    首先我們需要配置服務端保證WSE可以訪問證照及其私鑰。(至於證照的申請,管理和使用屬於基礎知識,在這就不講了)這裡主要是在Web Services端設定,以保證服務端可以自動完成某些功能,還無需使用者干預。在Web.config檔案新增如下<x509>元素(黑體部分):

<configuration>

  <microsoft.web.services2>

    <security>

      <x509 storeLocation=”CurrentUser” />

    </security>

  </microsoft.web.services2>

</configuration>

storeLocation屬性為WSE可訪問的證照儲存。它可選的值有兩項:“LocalMachine”和“CurrentUser”,分別表示本機證照儲存和當前使用者證照儲存。這裡用的是CurrentUser,而storeLocation預設值是LocalMachine.另外幾個屬性的配置如下: verifyTrust,是否驗證所用證照的證照鏈有效性,預設為true;allowTestRoot:驗證證照鏈過程中是否認為測試根CA所簽發證照有效,這個引數在verifyTrust為true時才有效,預設為false;allowRevocationUrlRetrieval,是否線上驗證證照是否被吊銷,在verifyTrust為true時才有效,預設為true;allowUrlRetrieval,是否線上驗證證照鏈有效性,allowRevocationUrlRetrieval,是否線上驗證證照吊銷狀態。

 

    客戶端:

    第1、2步與第一節使用使用者名稱/口令的步驟基本相同。但這裡也要多加一個引用:Microsoft.Web.Services2.Security.X509

3.編寫程式碼取得要使用的證照。我這裡用了一個listview控制元件顯示當前使用者證照儲存裡的個人證照,以供使用者選擇。你當然也可以象WSE文件上那樣利用證照的屬性去定位需要使用的證照。Listview控制元件名稱為lv_certlist,它有兩列:證照持有者主體和頒發者名稱。

Dim cert As X509Certificate

        Dim lvitem As ListViewItem

        `從當前使用者證照儲存中開啟個人證照庫

  certstore = X509CertificateStore.CurrentUserStore(X509CertificateStore.MyStore)

        certstore.Open()

        lv_certlist.Items.Clear()

        `遍歷證照,寫到listview控制元件裡

        For Each cert In certstore.Certificates

            lvitem = lv_certlist.Items.Add(cert.GetName)

            lvitem.SubItems.Add(cert.GetIssuerName)

            lvitem.Tag = lvitem.Index

        Next cert

4.選擇所選擇的證照生成X509SecurityToken的例項,它和前面的UsernameToken一樣是SecurityToken的子類。

        Dim cert As X509Certificate

        Dim certToken As X509SecurityToken

        Dim result As String

        cert = certstore.Certificates(lv_certlist.SelectedIndices(0))

        ’判斷所選擇證照是否支援簽名,並且私鑰是否存在

        If cert.SupportsDigitalSignature And Not (cert.Key Is Nothing) Then

            `遍歷證照,寫到listview控制元件裡

certToken = New X509SecurityToken(cert)

            Dim mywebserv As New WebServer.WebService

mywebserv.RequestSoapContext.Security.Timestamp.TtlInSeconds = 30

`新增包含證照資訊的X509SecurityToken

mywebserv.RequestSoapContext.Security.Tokens.Add(certToken)

`使用證照對SOAP訊息簽名,並將結果寫在訊息中

            mywebserv.RequestSoapContext.Security.Elements.Add(New _

MessageSignature(certToken))

`呼叫Web服務的方法

            mywebserv.WebMethod

        End If

 

    服務端:

    和驗證使用使用者名稱/口令簽名的訊息類似,服務端也同樣自動驗證簽名有效性。同時,服務端會根據web.config檔案中<x509>元素裡的屬性設定來對證照的有效性進行驗證。如果驗證失敗,則返回相應錯誤。簽名所使用證照可以通過SoapContext.Security.Tokens得到

四、使用使用者名稱和口令對SOAP訊息加密

    前面第一節講過,如果UsernameToken例項中的口令是明文,那最好將UsernameToken加密傳送。在第一節客戶端程式碼中做如下改動,黑體程式碼為新加:

mywebserv.RequestSoapContext.Security.Tokens.Add(untoken)

    mywebserv.RequestSoapContext.Security.Elements.Add(New _

Microsoft.Web.Services2.Security.EncryptedData (untoken))    

mywebserv.WebMethod

    上述程式碼就是客戶端根據UsernameToken的對SOAP訊息加密,然後把密文加在SOAP訊息裡,具體是WS-Security擴充套件的SOAP訊息頭

    服務端自動對訊息解密。服務端首先驗證使用者名稱/口令,然後使用客戶端傳遞的使用者名稱和自己獲得的口令(WSE自動從Windows活動目錄中獲得,或者通過過載的AuthenticateToken的方法)對解密資料。如果失敗,返回相應錯誤。

    WSE的幫助文件上這一塊同樣加了一個判斷SOAP是否加密的函式。

 

五、使用證照對SOAP訊息加解密

    這個略微複雜一些。根據PKI非對稱加解密原理,對訊息加密的客戶端需要事先得到作為接收方的服務端的證照公鑰,而服務端必須保證可以訪問其證照對應的私鑰。因此我們首先要在服務端選擇設定好它所使用的證照,原則就是使服務端能夠隨時並自動訪問其證照對應的私鑰。

    對於基於ASP.NET開發的Web Services,需要給它執行時的預設使用者賦予訪問證照私鑰的許可權。執行在IIS 6.0上的Web Services預設使用者是Network Service,其他情況下使用者賬戶由machine.config檔案裡<processModel>元素的userName屬性決定,預設情況下是“machine”,它等同於ASPNET賬戶。新增步驟如下:執行WSE 2.0自帶的X.509 Certificate tool(開始—程式—Microsoft WSE 2.0—X.509 Certificate tool),選擇並開啟WSE要訪問的證照。開啟後,“Private Key File Name”文字框裡會顯示證照所對應私鑰檔案的名稱,Private Key File Folder”文字框裡會顯示私鑰檔案的路徑。單擊“Private Key File Properties”開啟檔案屬性對話方塊,可以在安全屬性頁裡新增被授權訪問的Network Service或ASPNET使用者賬戶。

    如果開啟證照後發現私鑰檔案不存在或不可訪問,說明此證照有問題或者私鑰受保護,比如有口令保護或者儲存在安全外設中,不能被Web Services隨時自動訪問,因此也就不適用。

    對於Web Services,如果使用的使用者是預設的ASPNET,那最好選擇本機儲存(LocalMachine Store)裡的證照。因為ASPNET這個使用者是安裝ASP.NET時自動生成的,它的口令是沒有辦法訪問的。如果證照包含在當前使用者儲存(CurrentUser Store)中,服務端很可能就訪問不到私鑰。

    服務端設定好證照後,接下來就要使客戶端得到這個證照和公鑰。具體的方法和你的業務系統和應用需求有關係,你可以把它放在網頁上讓使用者事先下載安裝;如果應用在區域網環境,可以讓客戶端去LDAP或CA裡取得。

    客戶端做如下改動:

mywebserv.RequestSoapContext.Security.Tokens.Add(certToken)

SOAP訊息中新增加密結果

  mywebserv.RequestSoapContext.Security.Elements.Add(New _

  Microsoft.Web.Services2.Security.EncryptedData(certToken))

mywebserv.WebMethod

  服務端接收到加密後的SOAP訊息後,自動使用相應私鑰解密。如果失敗,返回相應錯誤。

可以看出,使用證照加解密訊息的難點在於證照的選擇和配置,尤其是訊息接收端。

 

WSE除了支援上述使用者名稱/口令,X509證照兩種令牌外。還支援Kerberos Ticket,安全上下文令牌(Security Context Token)和使用者自定義令牌。Kerberos Ticket好象是WSE自己加的內容,標準的WS-Security裡沒有,而且只在Windows XP SP1/SP2,Windows2003上支援。安全上下文令牌需要一個上下文令牌服務生成。而使用者自定義令牌的核心是由SecurityToken派生一個使用者自己的令牌類,完成各種安全功能。總之WSE就是使用這種基於令牌和擴充套件SOAP訊息來實現Web Services安全的。


相關文章