文档链接:《Extensible Messaging and Presence Protocol (XMPP): Core》
客户和服务器之间端口为5222,服务器之间的端口为5269.
通信协议格式:
|--------------------|
| < stream > |
|--------------------|
| < presence > |
| < show /> |
| </ presence > |
|--------------------|
| < message to ='foo' > |
| < body /> |
| </ message > |
|--------------------|
| < iq to ='bar' > |
| < query /> |
| </ iq > |
|--------------------|
| |
|--------------------|
| </ stream > |
| < stream > |
|--------------------|
| < presence > |
| < show /> |
| </ presence > |
|--------------------|
| < message to ='foo' > |
| < body /> |
| </ message > |
|--------------------|
| < iq to ='bar' > |
| < query /> |
| </ iq > |
|--------------------|
| |
|--------------------|
| </ stream > |
客户端和服务器之间维持一个TCP连接,服务器之间维持两个TCP连接。
想传输数据之间必须得通过验证,否则不予处理
To:用于客户端到服务器端,
From:用于服务器到客户端
Id:用于服务器到客户端,是服务器端生成的会话id号
Xml:lang:包括在客户端的初始流种,指定XML数据的字体,服务器端应该记住此设置,若客户端没有设置,则服务器使用其默认配置,并在应答流中通知给客户端。
| initiating to receiving | receiving to initiating
--------- + --------------------------- + -----------------------
to | hostname of receiver | silently ignored
from | silently ignored | hostname of receiver
id | silently ignored | session key
xml :lang | default language | default language
version | signals XMPP 1.0 support | signals XMPP 1.0 support
--------- + --------------------------- + -----------------------
to | hostname of receiver | silently ignored
from | silently ignored | hostname of receiver
id | silently ignored | session key
xml :lang | default language | default language
version | signals XMPP 1.0 support | signals XMPP 1.0 support
若客户端在初始化流包中包含了版本字段,则服务器在应答中必须包含一个<features/>元素,从而让客户端知道服务器支持的特性是哪些,以便客户端和服务器就某些特性进行协商。
流级别的错误是不可恢复的,因此一旦出现,则探测到错误的实体必须发送一个流错误信息给对方,并且发送</stream>结束符,关闭底层TCP连接。
一次简单的会话示例:
C:
<?
xml version='1.0'
?>
< stream:stream
to ='example.com'
xmlns ='jabber:client'
xmlns:stream ='http://etherx.jabber.org/streams'
version ='1.0' >
S: <? xml version='1.0' ?>
< stream:stream
from ='example.com'
id ='someid'
xmlns ='jabber:client'
xmlns:stream ='http://etherx.jabber.org/streams'
version ='1.0' >
encryption, authentication, and resource binding
C: < message from ='juliet@example.com'
to ='romeo@example.net'
xml:lang ='en' >
C: < body > Art thou not Romeo, and a Montague? </ body >
C: </ message >
S: < message from ='romeo@example.net'
to ='juliet@example.com'
xml:lang ='en' >
S: < body > Neither, fair saint, if either thee dislike. </ body >
S: </ message >
C: </ stream:stream >
S: </ stream:stream >
< stream:stream
to ='example.com'
xmlns ='jabber:client'
xmlns:stream ='http://etherx.jabber.org/streams'
version ='1.0' >
S: <? xml version='1.0' ?>
< stream:stream
from ='example.com'
id ='someid'
xmlns ='jabber:client'
xmlns:stream ='http://etherx.jabber.org/streams'
version ='1.0' >
encryption, authentication, and resource binding
C: < message from ='juliet@example.com'
to ='romeo@example.net'
xml:lang ='en' >
C: < body > Art thou not Romeo, and a Montague? </ body >
C: </ message >
S: < message from ='romeo@example.net'
to ='juliet@example.com'
xml:lang ='en' >
S: < body > Neither, fair saint, if either thee dislike. </ body >
S: </ message >
C: </ stream:stream >
S: </ stream:stream >
一次发生错误的会话:
C:
<?
xml version='1.0'
?>
< stream:stream
to ='example.com'
xmlns ='jabber:client'
xmlns:stream ='http://etherx.jabber.org/streams'
version ='1.0' >
S: <? xml version='1.0' ?>
< stream:stream
from ='example.com'
id ='someid'
xmlns ='jabber:client'
xmlns:stream ='http://etherx.jabber.org/streams'
version ='1.0' >
encryption, authentication, and resource binding
C: < message xml:lang ='en' >
< body > Bad XML, no closing body tag!
</ message >
S: < stream:error >
< xml-not-well-formed
xmlns ='urn:ietf:params:xml:ns:xmpp-streams' />
</ stream:error >
S: </ stream:stream >
< stream:stream
to ='example.com'
xmlns ='jabber:client'
xmlns:stream ='http://etherx.jabber.org/streams'
version ='1.0' >
S: <? xml version='1.0' ?>
< stream:stream
from ='example.com'
id ='someid'
xmlns ='jabber:client'
xmlns:stream ='http://etherx.jabber.org/streams'
version ='1.0' >
encryption, authentication, and resource binding
C: < message xml:lang ='en' >
< body > Bad XML, no closing body tag!
</ message >
S: < stream:error >
< xml-not-well-formed
xmlns ='urn:ietf:params:xml:ns:xmpp-streams' />
</ stream:error >
S: </ stream:stream >
出于安全性的考虑,在客户和服务器之间,服务器之间可能会要求先进行TLS 安全性的验证,而且必须在SASL协商之前完成。
客户到服务器示例:
Step 1: 客户端初始化到服务器端的流:
< stream:stream
xmlns ='jabber:client'
xmlns:stream ='http://etherx.jabber.org/streams'
to ='example.com'
version ='1.0' >
Step 2: 服务器的应答,回送一个 < stream > 标签,并指明会话id:
< stream:stream
xmlns ='jabber:client'
xmlns:stream ='http://etherx.jabber.org/streams'
id ='c2s_123'
from ='example.com'
version ='1.0' >
Step 3: 服务器发送STARTTLS extension 给客户端,并且指明验证机制和其他支持的流特性:
< 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 >
Step 4:客户端发送STARTTLS命令给服务器:
< starttls xmlns ='urn:ietf:params:xml:ns:xmpp-tls' />
Step 5: 服务器通知客户端,它被允许继续处理:
< proceed xmlns ='urn:ietf:params:xml:ns:xmpp-tls' />
Step 5 (alt): 服务器通知客户,TLS协商失败,关闭流和TCP连接:
< failure xmlns ='urn:ietf:params:xml:ns:xmpp-tls' />
</ stream:stream >
Step 6:客户端和服务器端准备在现有的TCP连接上完成TLS验证。
Step 7: 若TLS验证成功,客户端初始化一个到服务器的新流:
< stream:stream
xmlns ='jabber:client'
xmlns:stream ='http://etherx.jabber.org/streams'
to ='example.com'
version ='1.0' >
Step 7 (alt): 若TLS验证失败,服务器关闭TCP连接
Step 8:服务器回送应答信息,包含了一个流头部和其他可用的流特性:
< stream:stream
xmlns ='jabber:client'
xmlns:stream ='http://etherx.jabber.org/streams'
from ='example.com'
id ='c2s_234'
version ='1.0' >
< stream:features >
< mechanisms xmlns ='urn:ietf:params:xml:ns:xmpp-sasl' >
< mechanism > DIGEST-MD5 </ mechanism >
< mechanism > PLAIN </ mechanism >
< mechanism > EXTERNAL </ mechanism >
</ mechanisms >
</ stream:features >
Step 9: 客户端继续进行SASL验证。
< stream:stream
xmlns ='jabber:client'
xmlns:stream ='http://etherx.jabber.org/streams'
to ='example.com'
version ='1.0' >
Step 2: 服务器的应答,回送一个 < stream > 标签,并指明会话id:
< stream:stream
xmlns ='jabber:client'
xmlns:stream ='http://etherx.jabber.org/streams'
id ='c2s_123'
from ='example.com'
version ='1.0' >
Step 3: 服务器发送STARTTLS extension 给客户端,并且指明验证机制和其他支持的流特性:
< 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 >
Step 4:客户端发送STARTTLS命令给服务器:
< starttls xmlns ='urn:ietf:params:xml:ns:xmpp-tls' />
Step 5: 服务器通知客户端,它被允许继续处理:
< proceed xmlns ='urn:ietf:params:xml:ns:xmpp-tls' />
Step 5 (alt): 服务器通知客户,TLS协商失败,关闭流和TCP连接:
< failure xmlns ='urn:ietf:params:xml:ns:xmpp-tls' />
</ stream:stream >
Step 6:客户端和服务器端准备在现有的TCP连接上完成TLS验证。
Step 7: 若TLS验证成功,客户端初始化一个到服务器的新流:
< stream:stream
xmlns ='jabber:client'
xmlns:stream ='http://etherx.jabber.org/streams'
to ='example.com'
version ='1.0' >
Step 7 (alt): 若TLS验证失败,服务器关闭TCP连接
Step 8:服务器回送应答信息,包含了一个流头部和其他可用的流特性:
< stream:stream
xmlns ='jabber:client'
xmlns:stream ='http://etherx.jabber.org/streams'
from ='example.com'
id ='c2s_234'
version ='1.0' >
< stream:features >
< mechanisms xmlns ='urn:ietf:params:xml:ns:xmpp-sasl' >
< mechanism > DIGEST-MD5 </ mechanism >
< mechanism > PLAIN </ mechanism >
< mechanism > EXTERNAL </ mechanism >
</ mechanisms >
</ stream:features >
Step 9: 客户端继续进行SASL验证。
服务器到服务器示例:
Step 1: Server1 initiates stream to Server2:
< stream:stream
xmlns ='jabber:server'
xmlns:stream ='http://etherx.jabber.org/streams'
to ='example.com'
version ='1.0' >
Step 2: Server2 responds by sending a stream tag to Server1:
< stream:stream
xmlns ='jabber:server'
xmlns:stream ='http://etherx.jabber.org/streams'
from ='example.com'
id ='s2s_123'
version ='1.0' >
Step 3: Server2 sends the STARTTLS extension to Server1 along with authentication mechanisms and any other stream features:
< 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 > KERBEROS_V4 </ mechanism >
</ mechanisms >
</ stream:features >
Step 4: Server1 sends the STARTTLS command to Server2:
< starttls xmlns ='urn:ietf:params:xml:ns:xmpp-tls' />
Step 5: Server2 informs Server1 that it is allowed to proceed:
< proceed xmlns ='urn:ietf:params:xml:ns:xmpp-tls' />
Step 5 (alt): Server2 informs Server1 that TLS negotiation has failed and closes stream:
< failure xmlns ='urn:ietf:params:xml:ns:xmpp-tls' />
</ stream:stream >
Step 6: Server1 and Server2 attempt to complete TLS negotiation via TCP.
Step 7: If TLS negotiation is successful, Server1 initiates a new stream to Server2:
< stream:stream
xmlns ='jabber:server'
xmlns:stream ='http://etherx.jabber.org/streams'
to ='example.com'
version ='1.0' >
Step 7 (alt): If TLS negotiation is unsuccessful, Server2 closes TCP connection.
Step 8: Server2 responds by sending a stream header to Server1 along with any available stream features:
< stream:stream
xmlns ='jabber:server'
xmlns:stream ='http://etherx.jabber.org/streams'
from ='example.com'
id ='s2s_234'
version ='1.0' >
< stream:features >
< mechanisms xmlns ='urn:ietf:params:xml:ns:xmpp-sasl' >
< mechanism > DIGEST-MD5 </ mechanism >
< mechanism > KERBEROS_V4 </ mechanism >
< mechanism > EXTERNAL </ mechanism >
</ mechanisms >
</ stream:features >
Step 9: server1继续进行SASL验证
< stream:stream
xmlns ='jabber:server'
xmlns:stream ='http://etherx.jabber.org/streams'
to ='example.com'
version ='1.0' >
Step 2: Server2 responds by sending a stream tag to Server1:
< stream:stream
xmlns ='jabber:server'
xmlns:stream ='http://etherx.jabber.org/streams'
from ='example.com'
id ='s2s_123'
version ='1.0' >
Step 3: Server2 sends the STARTTLS extension to Server1 along with authentication mechanisms and any other stream features:
< 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 > KERBEROS_V4 </ mechanism >
</ mechanisms >
</ stream:features >
Step 4: Server1 sends the STARTTLS command to Server2:
< starttls xmlns ='urn:ietf:params:xml:ns:xmpp-tls' />
Step 5: Server2 informs Server1 that it is allowed to proceed:
< proceed xmlns ='urn:ietf:params:xml:ns:xmpp-tls' />
Step 5 (alt): Server2 informs Server1 that TLS negotiation has failed and closes stream:
< failure xmlns ='urn:ietf:params:xml:ns:xmpp-tls' />
</ stream:stream >
Step 6: Server1 and Server2 attempt to complete TLS negotiation via TCP.
Step 7: If TLS negotiation is successful, Server1 initiates a new stream to Server2:
< stream:stream
xmlns ='jabber:server'
xmlns:stream ='http://etherx.jabber.org/streams'
to ='example.com'
version ='1.0' >
Step 7 (alt): If TLS negotiation is unsuccessful, Server2 closes TCP connection.
Step 8: Server2 responds by sending a stream header to Server1 along with any available stream features:
< stream:stream
xmlns ='jabber:server'
xmlns:stream ='http://etherx.jabber.org/streams'
from ='example.com'
id ='s2s_234'
version ='1.0' >
< stream:features >
< mechanisms xmlns ='urn:ietf:params:xml:ns:xmpp-sasl' >
< mechanism > DIGEST-MD5 </ mechanism >
< mechanism > KERBEROS_V4 </ mechanism >
< mechanism > EXTERNAL </ mechanism >
</ mechanisms >
</ stream:features >
Step 9: server1继续进行SASL验证
在TLS验证后,进行SASL验证
客户到服务器示例:
Step 1: 客户端初始到服务器的流:
<
stream:stream
xmlns ='jabber:client'
xmlns:stream ='http://etherx.jabber.org/streams'
to ='example.com'
version ='1.0' >
xmlns ='jabber:client'
xmlns:stream ='http://etherx.jabber.org/streams'
to ='example.com'
version ='1.0' >
Step 2:服务器发送应答:
<
stream:stream
xmlns ='jabber:client'
xmlns:stream ='http://etherx.jabber.org/streams'
id ='c2s_234'
from ='example.com'
version ='1.0' >
xmlns ='jabber:client'
xmlns:stream ='http://etherx.jabber.org/streams'
id ='c2s_234'
from ='example.com'
version ='1.0' >
Step 3:服务器通知客户可选的验证机制:
<
stream:features
>
< mechanisms xmlns ='urn:ietf:params:xml:ns:xmpp-sasl' >
< mechanism > DIGEST-MD5 </ mechanism >
< mechanism > PLAIN </ mechanism >
</ mechanisms >
</ stream:features >
< mechanisms xmlns ='urn:ietf:params:xml:ns:xmpp-sasl' >
< mechanism > DIGEST-MD5 </ mechanism >
< mechanism > PLAIN </ mechanism >
</ mechanisms >
</ stream:features >
Step 4: 客户端选择一种验证机制:
<
auth
xmlns
='urn:ietf:params:xml:ns:xmpp-sasl'
mechanism ='DIGEST-MD5' />
mechanism ='DIGEST-MD5' />
Step 5: 服务器发送BASE64编码过的应答数据给客户端:
<
challenge
xmlns
='urn:ietf:params:xml:ns:xmpp-sasl'
>
cmVhbG09InNvbWVyZWFsbSIsbm9uY2U9Ik9BNk1HOXRFUUdtMmhoIixxb3A9ImF1dGgi
LGNoYXJzZXQ9dXRmLTgsYWxnb3JpdGhtPW1kNS1zZXNzCg==
</ challenge >
cmVhbG09InNvbWVyZWFsbSIsbm9uY2U9Ik9BNk1HOXRFUUdtMmhoIixxb3A9ImF1dGgi
LGNoYXJzZXQ9dXRmLTgsYWxnb3JpdGhtPW1kNS1zZXNzCg==
</ challenge >
解码后的数据为:
realm="somerealm",nonce="OA6MG9tEQGm2hh",\
qop="auth",charset=utf-8,algorithm=md5-sess
qop="auth",charset=utf-8,algorithm=md5-sess
Step 5 (alt):服务器返回错误给客户端:
<
failure
xmlns
='urn:ietf:params:xml:ns:xmpp-sasl'
>
< incorrect-encoding />
</ failure >
</ stream:stream >
< incorrect-encoding />
</ failure >
</ stream:stream >
Step 6: 客户端发送一个BASE64编码的应答数据给服务器:
<
response
xmlns
='urn:ietf:params:xml:ns:xmpp-sasl'
>
dXNlcm5hbWU9InNvbWVub2RlIixyZWFsbT0ic29tZXJlYWxtIixub25jZT0i
T0E2TUc5dEVRR20yaGgiLGNub25jZT0iT0E2TUhYaDZWcVRyUmsiLG5jPTAw
MDAwMDAxLHFvcD1hdXRoLGRpZ2VzdC11cmk9InhtcHAvZXhhbXBsZS5jb20i
LHJlc3BvbnNlPWQzODhkYWQ5MGQ0YmJkNzYwYTE1MjMyMWYyMTQzYWY3LGNo
YXJzZXQ9dXRmLTgK
</ response >
dXNlcm5hbWU9InNvbWVub2RlIixyZWFsbT0ic29tZXJlYWxtIixub25jZT0i
T0E2TUc5dEVRR20yaGgiLGNub25jZT0iT0E2TUhYaDZWcVRyUmsiLG5jPTAw
MDAwMDAxLHFvcD1hdXRoLGRpZ2VzdC11cmk9InhtcHAvZXhhbXBsZS5jb20i
LHJlc3BvbnNlPWQzODhkYWQ5MGQ0YmJkNzYwYTE1MjMyMWYyMTQzYWY3LGNo
YXJzZXQ9dXRmLTgK
</ response >
解码后的应答消息是:
username="somenode",realm="somerealm",\
nonce="OA6MG9tEQGm2hh",cnonce="OA6MHXh6VqTrRk",\
nc=00000001,qop=auth,digest-uri="xmpp/example.com",\
response=d388dad90d4bbd760a152321f2143af7,charset=utf-8
nonce="OA6MG9tEQGm2hh",cnonce="OA6MHXh6VqTrRk",\
nc=00000001,qop=auth,digest-uri="xmpp/example.com",\
response=d388dad90d4bbd760a152321f2143af7,charset=utf-8
Step 7: 服务器发送第二个BASE64编码的challenge数据给客户:
<
challenge
xmlns
='urn:ietf:params:xml:ns:xmpp-sasl'
>
cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZAo=
</ challenge >
cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZAo=
</ challenge >
解码后的challenge是:
rspauth=ea40f60335c427b5527b84dbabcdfffd
Step 7 (alt): 服务器返回错误给客户:
<
failure
xmlns
='urn:ietf:params:xml:ns:xmpp-sasl'
>
< temporary-auth-failure />
</ failure >
</ stream:stream >
< temporary-auth-failure />
</ failure >
</ stream:stream >
Step 8: 客户返回应答消息给服务器:
<
response
xmlns
='urn:ietf:params:xml:ns:xmpp-sasl'
/>
Step 9: 服务器通知客户成功通过验证:
<
success
xmlns
='urn:ietf:params:xml:ns:xmpp-sasl'
/>
Step 9 (alt):服务器通知客户验证失败:
<
failure
xmlns
='urn:ietf:params:xml:ns:xmpp-sasl'
>
< temporary-auth-failure />
</ failure >
</ stream:stream >
< temporary-auth-failure />
</ failure >
</ stream:stream >
Step 10:客户端初始化一个到服务器的新流:
<
stream:stream
xmlns ='jabber:client'
xmlns:stream ='http://etherx.jabber.org/streams'
to ='example.com'
version ='1.0' >
xmlns ='jabber:client'
xmlns:stream ='http://etherx.jabber.org/streams'
to ='example.com'
version ='1.0' >
Step 11: 服务器返回一个流头部,包含了服务器支持的流特性:
<
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 >
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 >