wse2.0實現webservice安全(轉)

 非常不錯的wse2.0實現webservice安全的文章,講的很詳細,轉自http://blog.csdn.net/nealbzdn/,感謝原創作者。
 

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>元素裏的屬性設置來對證書的有效性進行驗證。如果驗證失敗,則返回相應錯誤。

四、使用用戶名和口令對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安全的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章