原文:http://idior.cnblogs.com/articles/381534.htmlphp
使用用戶名和密碼來驗證用戶的身份是最普通也最多見的方法,雖然在安全性方面也比較弱,因爲其運用的普遍性仍是成爲了WS-Security目前所支持的Security Token之一。其原理很是簡單,用戶在發送請求的時候,在Soap head中加入本身的用戶名以及密碼,接受請求的Service經過以前與Client創建的共享密碼來驗證密碼的合法性從而實現鑑別用戶的功能。
不過實際運用起來就不能考慮的那麼簡單了,該方法主要存在兩個問題: 1. 在SOAP包中傳輸密碼怎麼保證密碼的安全性? 2. 怎麼從用戶名密碼中得到簽名和加密所須要的密鑰?html
針對第一個問題有三種解決方案: 1. 使用運輸層的安全協議(如SSL)來保證實文密碼的安全性。 2. 對明文密碼作摘要後再傳送給Service。 3. 利用從密碼派生出來的密鑰來代替直接使用密碼來實現身份鑑別。c#
第一種方法採用了WS-Security結合運輸層的安全協議(SSL)來保證密碼的安全,在本系列一開始的文章已經描述過運輸層安全協議的缺點,因此在此不對該方法作詳細介紹。安全
第二種方法相似於HTTP Digest Authentication,先來看一段使用該方法的示例:
<wsse:UsernameToken> <wsse:Username>NNK</wsse:Username> <wsse:Password Type="...#PasswordDigest"> weYI3nXd8LjMNVksCKFV8t3rgHh3Rw== </wsse:Password> <wsse:Nonce>WScqanjCEAC4mQoBE07sAQ==</wsse:Nonce> <wsu:Created>2003-07-16T01:24:32Z</wsu:Created> </wsse:UsernameToken> 從中看出這裏使用了PasswordDigest類型的Password,從Password的內容也能夠看出這裏沒有使用明文密碼的形式。另外還多出了wsse:Nonce和wsu:Created兩個元素。 其中Password的內容的計算公式以下: Password_Digest = Base64 ( SHA-1 ( nonce + created + password ) )
讀者可能比較奇怪wsse:Nonce和wsu:Created這兩個元素的做用。爲何不直接SHA-1(password) ? 這樣作是爲了不重放(Replay)攻擊。假設Alice以摘要的形式向Service發送了密碼,若是Bob此時截獲了Alice發送的密碼摘要,而後再用它向Service發送請求,那麼Service將誤認爲Bob也是合法用戶。當咱們加入Nonce和Created元素以後,Service能夠檢查收到的消息中的Nonce是否已經收到過了,或者在一段時間(5min)內是否收到了相同用戶名密碼,從而避免重放攻擊的危險。不過使用PasswordDigest方式要求Service必須擁有密碼的明文形式,也就是說Service能夠看到每一個用戶的密碼,這對用戶來講增長了風險。由於一般狀況下用戶的密碼是以hash的形式保存在Service端的,從而保證用戶的信息不被泄漏。 儘管經過PasswordDigest能夠避免密碼的明文傳播,並且經過引入wsse:Nonce和wsu:Created能夠避免重放攻擊的危險,可是若是Bob可以把傳送中的密碼摘要徹底的攔截下來(使它沒法傳送到Service),而後利用攔截下來的密碼去冒充Alice去請求Service,那麼Service將一籌莫展。爲此,咱們引入了第三種方法。加密
第三種方法和Kerberos協議中KDC向Client傳送TGT的方式相似。 咱們能夠看出前兩種方式用戶都將本身的密碼發送給Service用於身份鑑別,難道爲了證實本身的身份就必須把密鑰(這裏是密碼)直接告訴別人嗎?其實問題的關鍵在於Client能向Service證實它擁有隻有C與S知道的密鑰。而證實擁有的最直接方法就是告訴對方這個密鑰,而後由Service比較這個密鑰是否和它所知道的密鑰一致,從而鑑別用戶的身份。可是這種方法如前所述存在多種缺陷。既然僅僅須要Client證實它知道這個密鑰,那麼Client能夠用這個密鑰對一段消息作一個簽名,而後將消息和簽名同時發送給Service,Service用它所知道的Client的密鑰也對一樣的消息作一次簽名,經過比較兩個簽名是否一致就能夠確認Client是否真的擁有它的密鑰。一樣經過加密的方法Client也能夠向Service證實本身是否真的擁有密鑰(由於只有C與S密鑰一致Service才能解密出用C密鑰加密的消息)。這樣一旦Client在消息中加入本身的一些特有信息(好比IP),即使Bob截獲了消息可是因爲他並不知道真正的密鑰,看不到那些特有信息,也就沒法冒充Alice。spa
經過這種間接證實擁有密鑰的方法,咱們同時解決了文章一開始提出的第二個問題: 怎麼從用戶名密碼中得到簽名和加密所須要的密鑰?
只要對密碼作一些處理就能夠從中派生出密鑰。固然爲了安全起見咱們但願每次派生出來的密鑰都不同,這樣就能夠避免屢次使用同一密鑰而致使密鑰被破解。下面就是WS-Security對密鑰派生的元素定義:
<wsse:UsernameToken wsse:Id=」…」> <wsse:Username>…</wsse:Username> <wsse11:Salt>…</wsse11:Salt> <wsse11:Iteration>…</wsse11:Iteration> </wsse:UsernameToken> 其中Salt是致使密鑰變化的因子,Iteration是密鑰派生時Hash的次數。 密碼的派生公式以下: K1 = SHA1( password + Salt) K2 = SHA1( K1) … Kn = SHA1 ( Kn-1)3d
能夠看到此時在UsernameToken已經再也不包含Password元素,由於Client將經過使用從Password派生出密鑰作簽名作加密的方式來證實它擁有密鑰,從而證實本身的身份。xml
由此看出第三種辦法相對來講安全性大大提升了,可是在實際應用中以上介紹的三種的方法都不被推薦使用。 第三種方法仍舊存在如下兩個缺陷: 1. 直接使用密碼派生密鑰,同以往臨時產生的會話密鑰相比,密碼一旦破解,全部由改密碼派生的密鑰也被破解。因爲密碼長期不變, 那麼隨後全部使用該密碼加密的消息都沒有安全性可言。並且該密碼可能還被用於Client與其餘Service的交互,那麼被破解後帶來的損失就大多了。 2. 用戶密碼必須以明文形式保存在Service端。htm
所以,在微軟的WSE對安全的默認支持方式中採用了UsernameToken和Service端Certification的組合的方式來表示Security Token。下圖就是WSE中已經實現的UsernameForCertificate對SOAP Envelop的擴展結構。
blog
從中能夠看到SOAP Head中的wsse:UsernameToken已經被加密,被xenc:EncryptedData所替代,查看其Token Reference發現加密使用的Key來自xenc:EncryptedKey。若是你完整閱讀了本系列的文章,你將不會對它太陌生,在XML Encryption中曾經對它的來由作了詳細介紹。
Client隨機產生了一個對稱密鑰並用它來加密和簽名SOAP Envelop中的其餘元素(如UsernameToken),而後經過使用Service(消息接受方)的公鑰(因爲是公鑰能夠方便獲取)來加密該對稱密鑰,以保證只有Service可以得到Client隨機產生的對稱密鑰,從而達到驗證消息完整性,解密數據以及鑑別用戶身份的目的。如下是採用這種方式保證安全的SOAP Envelop的示例:
<?xml version="1.0" encoding="utf-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-envelope" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xenc="http://www.w3.org/2001/04/xmlenc" xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"> <SOAP-ENV:Header> <wsse:Security xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/secext"> <wsse:UsernameToken> <wsse:Username>HotelService</wsse:Username> <wsse:Password>myword</wsse:Password> </wsse:UsernameToken> <xenc:EncryptedKey wsu:id="userSysmetricKey"> <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/> <ds:KeyInfo> <wsse:SecurityTokenReference> <wsse:KeyIdentifier ValueType="...oasis-wss-soap-message-security-1.1#ThumbPrintSHA1"> LKiQ/CmFrJDJqCLFcjlhIsmZ/+0= </wsse:KeyIdentifier> </wsse:SecurityTokenReference> </ds:KeyInfo> <xenc:CipherData> <xenc:CipherValue>G2wDCq24FsgBGerE...</xenc:CipherValue> </xenc:CipherData> <xenc:ReferenceList> <xenc:DataReference URI="#DiscountResponse"/> </xenc:ReferenceList> </xenc:EncryptedKey> <ds:Signature> <ds:SignedInfo> <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/> <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1"/> <ds:Reference URI="#DiscountedBookingForPartnersResponse"> <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1"/> <ds:DigestValue>JwFsd3eQc0iXlJm5PkLh7...</ds:DigestValue> </ds:Reference> </ds:SignedInfo> <ds:SignatureValue>BSxlJbSiFdm5Plhk...</ds:SignatureValue> <ds:KeyInfo> <wsse:SecurityTokenReference> <wsse:Reference URI="#userSysmetricKey" ValueType="...oasis-wss-soap-message-security-1.1#EncryptedKey"/> </wsse:SecurityTokenReference> </ds:KeyInfo> </ds:Signature> </wsse:Security> </SOAP-ENV:Header> <SOAP-ENV:Body wsu:Id="DiscountedBookingForPartnersResponse"> <s:GetSpecialDiscountedBookingForPartnersResponse xmlns:s="http://www.MyHotel.com/partnerservice "> <xenc:EncryptedData wsu:Id="DiscountResponse" type="http://www.w3.org/2001/04/xmlenc#Element"> <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc "/> <CipherData> <CipherValue>XD6sFa0DrWsHdehrHdhcW0x...</CipherValue> </CipherData> </xenc:EncryptedData> </s:GetSpecialDiscountedBookingForPartnersResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
如此,以前所提到的問題均可以解決了,讓咱們回顧一下: Q: 在SOAP包中傳輸密碼怎麼保證密碼的安全性? A: 使用密鑰加密,只有接受方能解密密碼。
Q: 怎麼從用戶名密碼中得到簽名和加密所須要的密鑰? A: 隨機產生密鑰,並經過接受方的公鑰加密,保證密鑰不被別人所知。
Q: 如何避免重放攻擊? A: 因爲其餘人沒法得到密鑰,因此即使截獲消息也沒法冒充。
Q: 直接使用密碼派生密鑰被破解了怎麼辦? A: 密鑰再也不從密碼派生,而是每次隨機產生,即使被破解也不會影響其餘的消息和其餘的服務。
Q: 用戶密碼必須以明文形式保存在Service端? A: 因爲密碼被加密而不是作摘要因此不須要Service擁有明文密碼。
應用場景: B2C網上購物,每一個用戶都有各自的用戶名密碼,並且能夠方便的得到Server端的Certification。(好比Amazon)
參考資料: OASIS Kerberos Token Profile 1.1 Protect Your Web Services Through The Extensible Policy Framework In WSE 3.0