xmpp通訊過程分析
XMPP訊息格式
Jabber/XMPP系統使用XML流在不同實體之間相互傳輸資料。在兩個實體的連線期間,XML流將從一個實體傳送到另一個實體。在實體間,有三個頂層的XML元素:,和。每一個都包含屬性和子節點。下面將分別描述這些元素。
1)訊息(message)元素:
一個即時訊息系統最基本的功能就是能夠在兩個使用者之間實時交換訊息,元素就提供了這個功能。每條訊息都有一個或多個屬性和子元素。屬性“from”和“to”分別表示了訊息傳送者和接收者的地址。也可以包含一個“type”屬性,這給接收者一個提示,這個訊息是什麼樣的訊息。表3-1給出了“type”屬性的可能取值。中也可以包含“id”屬性,用來唯一的標識一個輸出訊息的響應。
2)狀態(presence)元素:
元素用來傳遞一個使用者的存在狀態的感知資訊。使用者可以是“available”,要麼是“unavailable”,“Hide”等。當用戶連線到即時訊息伺服器後,好友發給他的訊息就立即被傳遞。如果使用者沒有連線到伺服器,好友發給他的訊息將被伺服器儲存起來直到使用者連線到伺服器。使用者通過即時訊息客戶端自己控制可用性。但是,如果使用者斷開了同伺服器的連線,伺服器將傳送給訂閱了這個使用者的存在資訊的使用者通知他們使用者已經不可用。還包含了兩個子元素:和。包含了一個對的文字描述。
3)IQ(Info<Query)元素
IQ元素是Jabber/XMPP訊息協議的第三個頂層元素。IQ代表”Info/Query”,用來發送和獲取實體之間的資訊。IQ訊息是通過“請求/響應”機制在實體間進行交換的。IQ元素用於不同的目的,它們之間通過不同的名稱空間來加以區分。在Jabber/XMPP訊息協議裡有許多的名稱空間,但最常用的名稱空間是:”jabber:iq:register”,”jabber:iq:auth”,”jabber:iq:roster”。
上面描述了Jabber協議的三個頂層節點。通過這種格式Jabber訊息不僅可以是簡單的文字(text),而且可以攜帶複雜的資料和各種格式的檔案,也就是說Jabber不僅可以用在人與人之間的交流,而且可以實現軟體與軟體或軟體與人之間的交流。Jabber的這種功能大大擴充套件了即時通訊的應用範圍。
XMPP通訊過程
XMPP被設計為能夠非同步並能快速交換短文的協議,為此客戶端和伺服器之間存在兩條XML流,用於非同步通訊,流傳輸的是XML文件。TCP是XMPP的預設承載協議,在客戶端到伺服器的通訊模型下,需要一條TCP鏈路;在伺服器到伺服器的通訊模式下,需要兩條TCP鏈路以傳輸兩個對端資料。
XMPP流以標記開始,以結束。XMPP流可以視為一個well-form的XML文件,每次傳輸的是文件的片段,而片段則是well-form的XML標籤。
stream標記的屬性如下:
to只能用於從客戶端到伺服器的XML流中。
from只能用於從伺服器到客戶端的XML流中。
id只能用於從接收實體到傳送實體的XML流中,id必須唯一,用於標記會話。
xml:lang只能用於發起方,用於約定語言。如發起方沒有攜帶xml:lang屬性,接收方應使用預設語言。
version至少在“1.0”以上。
可以看到客戶端和伺服器雙方都以標記開始會話,即雙方都在傳輸一個完整的XML文件。以發起會話後,雙方建立TLS安全鏈路,然後用SASL(稍後說明)認證對方身份,最後繫結資源並開始傳輸訊息報文。
TLS是SSL的後繼者,TLS1.0和SSL3.0非常相似。TLS建立在傳輸層上,通訊雙方先用不對稱加密演算法傳輸對稱加密演算法的金鑰,然後用對稱加密演算法加密傳輸內容。
TLS
第1步: 客戶端發起連線:
1 |
<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' to='maimaijun.com' version='1.0'> |
第2步: 伺服器向客戶端返回一個標記:
1 |
<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='c2s_123' from='maimaijun.com' version='1.0'> |
第3步: 伺服器向客戶端傳送STARTTLS擴充套件,並攜帶認證機制和流特性:
1 |
<stream:features><starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'><required/></starttls><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>DIGEST-MD5</mechanism><mechanism>PLAIN</mechanism></mechanisms></stream:features> |
第4步: 客戶端傳送STARTTLS給伺服器:
1 |
<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/> |
第5步: 伺服器提示客戶端可以繼續:
1 |
<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/> |
第6步: 客戶端和伺服器嘗試在已有的TCP鏈路上完成TLS握手過程。
第7步: 如果TLS握手成功,客戶端向伺服器發起一個新流:
1 |
<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' to='maimaijun.com' version='1.0'> |
第8步: 伺服器以一個stream頭響應,同時攜帶可能的流特性:
1 |
<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" from="maimaijun.com" id="3f857b69" xml:lang="en" version="1.0"><stream:features><starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"></starttls><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism>DIGEST-MD5</mechanism><mechanism>PLAIN</mechanism><mechanism>ANONYMOUS</mechanism><mechanism>CRAM-MD5</mechanism></mechanisms><compression xmlns="http://jabber.org/features/compress"><method>zlib</method></compression><auth xmlns="http://jabber.org/features/iq-auth"/><register xmlns="http://jabber.org/features/iq-register"/></stream:features> |
第9步: 客戶端繼續SASL握手。
SASL(Simple Authentication and Security Layer protocol)
第1步: 客戶端向伺服器發起流請求:
1 |
<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' to='maimaijun.com' version='1.0'> |
第2步: 伺服器向客戶端響應stream標記:
1 |
<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" from="maimaijun.com" id="9a7eced8" xml:lang="en" version="1.0"> |
第3步: 伺服器向客戶端提示可用的認證方法:
1 |
<stream:features><starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"></starttls><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism>DIGEST-MD5</mechanism><mechanism>PLAIN</mechanism><mechanism>ANONYMOUS</mechanism><mechanism>CRAM-MD5</mechanism></mechanisms><compression xmlns="http://jabber.org/features/compress"><method>zlib</method></compression><auth xmlns="http://jabber.org/features/iq-auth"/><register xmlns="http://jabber.org/features/iq-register"/></stream:features> |
第4步: 客戶端選擇一種認證方法:
1 |
<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/> |
第5步: 伺服器傳送以BASE64編碼的驗證碼給客戶端:
1 2 3 4 |
<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cmVhbG09InNvbWVyZWFsbSIsbm9uY2U9Ik9BNk1HOXRFUUdtMmhoIixxb3A9ImF1dGgiLGNoYXJzZXQ9dXRmLTgsYWxnb3JpdGhtPW1kNS1zZXNzCg==</challenge> 驗證碼解碼後如下: realm="somerealm",nonce="OA6MG9tEQGm2hh",qop="auth",charset=utf-8,algorithm=md5-sess |
第6步: 客戶端傳送以BASE64編碼的響應碼:
1 2 3 4 |
<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>dXNlcm5hbWU9InNvbWVub2RlIixyZWFsbT0ic29tZXJlYWxtIixub25jZT0iT0E2TUc5dEVRR20yaGgiLGNub25jZT0iT0E2TUhYaDZWcVRyUmsiLG5jPTAwMDAwMDAxLHFvcD1hdXRoLGRpZ2VzdC11cmk9InhtcHAvZXhhbXBsZS5jb20iLHJlc3BvbnNlPWQzODhkYWQ5MGQ0YmJkNzYwYTE1MjMyMWYyMTQzYWY3LGNoYXJzZXQ9dXRmLTgK</response> 解碼後的響應碼如下: username="somenode",realm="somerealm",nonce="OA6MG9tEQGm2hh",cnonce="OA6MHXh6VqTrRk",nc=00000001,qop=auth,digest-uri="xmpp/example.com",response=d388dad90d4bbd760a152321f2143af7,charset=utf-8 |
第7步: 伺服器傳送另一個以BASE64編碼的驗證碼給客戶端:
1 2 3 4 |
<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZAo=</challenge> 解碼後的驗證碼: rspauth=ea40f60335c427b5527b84dbabcdfffd |
第8步: 客戶端響應驗證碼:
1 |
<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/> |
第9步: 伺服器提示客戶端認證通過:
1 |
<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/> |
第10步: 客戶端向伺服器發起新連線:
1 2 3 4 5 |
<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' to='example.com' version='1.0'> |
第11步: 伺服器響應一個stream頭,可能攜帶特性:
1 2 3 4 5 6 7 8 9 |
<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='c2s_345' from='example.com' version='1.0'> <stream:features> <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/> <session xmlns='urn:ietf:params:xml:ns:xmpp-session'/> </stream:features> |
繫結資源
客戶端登陸
SENT:
1 |
<iq id="1a58416" type="get"><query xmlns="jabber:iq:auth"><username>mbed</username></query></iq> |
RECV:
1 |
<iq id='5573507c' type='result'><query xmlns='jabber:iq:auth'><username>mbed</username><digest/><password/><resource/></query></iq> |
注意中間的ID,這個ID是伺服器端返回給客戶端的驗證資訊,驗證資訊一般是以該ID號+使用者密碼通過SHA1(RFC3174)演算法進行操作的。也就是說客戶端得到該ID和密碼經過SHA1演算法加密後返回給伺服器。
SENT(客戶端提交欄位內容進行驗證):
文字格式,非加密模式
1 2 3 4 5 6 7 |
<iq type='set' id='auth2'> <query xmlns='jabber:iq:auth'> <username>mbed</username> <password>mirror</password> <resource>flash</resource> </query> </iq> |
加密模式
1 2 3 |
<iq id="ee17dc3f" type="set"><query xmlns="jabber:iq:auth"><username>mbed</username><resource>javaScript</resource><digest>459154dc8ea4a74ba5f692f79393e1c7e9de9fda</digest></query></iq> 或者 <iq id="ee17dc3f" type="set"><query xmlns="jabber:iq:auth"><username>mbed</username><resource>flash</resource><digest>459154dc8ea4a74ba5f692f79393e1c7e9de9fda</digest></query></iq> |
這個digest就是上面經過SHA1演算法得出的結果欄位;
如果客戶端傳送的欄位包括了使用者名稱和IQ-GET的欄位,伺服器不應該返回錯誤訊息(因為需要伺服器判斷當前使用者名稱是否在使用),如果伺服器不支援可插入的簡單認證及密碼模組,那麼必須返回一個的錯誤;如果客戶端企圖使用SASL認證但是失敗,伺服器必須返回錯誤資訊
在認證過程中,jabber:iq:auth命名、使用者名稱和資源是必須要求客戶端提供的,而伺服器返回的XML流中也必須提供和這2個元素。
RECV:
1 2 3 4 |
<iq id='jcl_104' type='result'/> <presence><c node="http://exodus.jabberstudio.org/caps" ver="0.9.1.0" xmlns="http://jabber.org/protocol/caps"/><status>Available</status><priority>1</priority></presence> <iq type='set' id='1a58416'><query xmlns='jabber:iq:auth'><username>mbed</username><password>mirror</password><resource>flash_as3</resource></query></iq> <iq xmlns='jabber:client' type="get" to="muc.maimaijun.com"><query xmlns='jabber:iq:mucAnonyRoom' actionType='getRoomIndex'><item><customerIndex>1</customerIndex></item></query></iq> |
登入結果
成功
1 |
<iq type='result' id='auth2'/> |
失敗 – 認證失敗,可能是使用者名稱密碼不匹配或數字驗證錯誤
1 2 3 4 5 |
<iq type='error' id='auth2'> <error code='401' type='auth'> <not-authorized xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </iq> |
失敗 – 資源衝突/錯誤
1 2 3 4 5 |
<iq type='error' id='auth2'> <error code='409' type='cancel'> <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </iq> |
失敗 – 沒有提供需要驗證的欄位
1 2 3 4 5 |
<iq type='error' id='auth2'> <error code='406' type='modify'> <not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </iq> |
客戶端連線成功後使用iq(Info Query)查詢伺服器資源。
1 2 3 4 |
<iq type='set' id='bind_2'> <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'> <resource>someresource</resource> </bind> </iq> 伺服器確認客戶端要繫結的資源後,必須返回一個iq標籤。 <iq type='result' id='bind_2'> <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'> <jid>[email protected]/someresource</jid> </bind> </iq> |
XML短語(Stanzas)
有三種短語——(訊息)、(線上狀態)和(資訊查詢),三種短語都有共同屬性,如下。
- Message語義
message短語可視為一種推送機制——某個實體向另一個實體推送資訊。所有message短語必須攜帶to屬性,以標明接收者。伺服器收到message短語後,或路由或投遞給接收者。 - Presence語義
presence可被視為一個基本的廣播或“釋出-訂閱”機制,多個實體接收他們訂閱的關於某個實體的資訊。 - IQ語義
Info/Query,或IQ,是一種”請求-響應“機制,與HTTP類似。IQ使一個實體能夠向另一個實體發起請求,請求/響應報文以id屬性標示。IQ互動通常以get/result和set/result模式執行。 - 基於XMPP的即時信使擴充套件
XMPP標準由XMPP Core(RFC3920)、XMPP IM(RFC3921)、XMPP CPIM(RFC3922)(對映XMPP到IETF的CPIM規範)、XMPP E2E(RFC3922)(端到端訊號和物件加密)、XMPP URN(RFC4854)(基於XMPP擴充套件的Uniform Resource Name樹)、XMPP ENUM(RFC4979)(在IANA註冊的列舉服務)、XMPP URI(RFC5122)(對RFC4622的勘誤)。本文只覆蓋了XMPP Core和XMPP IM,其中XMPP IM由XMPP Core擴充套件而得。