在使用smack开发聊天功能的时候,对XMPP协议中使用TLS和SSAL协议保证通讯安全的知识进行学习和记录。文章中的smack版本是V4.2.4,openfire服务器是V4.2.3.
TLS和SASL的作用
- TLS:全称是Transport Layer Security(安全传输层协议),最为我们熟知的就是HTTPS,便是HTTP+TLS(SSL)组合而成的。而客户端和openfire服务器的连接也是一个TCP链接,这里我们在使用smack 的API的时候通过连接类名
XMPPTCPConnection
就可得知。 - SSAL:这是一个鉴权协议,主要用来保证客户端登录服务器时候传输的鉴权数据的安全性。
所以,我们可以得知TLS是对整个传输过程的加密,SASL只是对于鉴权数据的加密。如果要和openfire服务器使用TLS协议进行连接,正常情况下是需要到掏钱CA机构登记。
因为是出于学习目的,我们也可以使用自签名的证书去验证。对于使用TLS连接会在下篇文章写到,这篇文章先写使用不同SASL机制的登录报文分析学习。
SASL
假如在smack中配置xmpp连接的时候只使用了SASL没有使用TLS,那么除了账号密码登录之外,我们的所有通讯内容都是裸奔!
SASL全拼是Simple Authentication and Security Layer,是一种用来扩充C/S模式验证能力的机制,是一种鉴权协议,制定了鉴权数据的交换,但是并没有制定数据的内容。不同于与TLS&SSL这种协议,TLS&SSL是对整个传输通道全部数据加密,具体的加密过程和HTTPS类似,不去改变HTTP协议及其传输内容,只是在C和S之间对了认证、加密、解密等过程。
在XMPP协议中,SASL是使用一个简单的xml命名空间来完成的鉴权协议,需要服务器和客户端在连接建立的开始去确认采用何种加密方式。这里先说一下smack中默认支持多少种SASL加密机制,及其特性。
默认的SASL机制
下面这几种加密机制,被初始化在SASLAuthentication.REGISTERED_MECHANISMS
容器中,我们可以手动实现SASL机制注册进去,客户端采用何种SASL的机制是根据该容器中的顺序。
这里先不拿具体报文去解析,会增加理解难度,先解释不同SASL机制的不同特性再去查看客户端连接服务器的报文
- PLAIN:对鉴权数据Base64编码后传输,相当于明文传输。假如传输登录密码,那就是登录密码BASE64以后传输而已,不配合TLS对传输数据进行加密处理就等同于裸奔了,密码都告诉别人了。
- ANONYMOUS:看具体的实现类的关键方法均是空实现,这块的知识以后补充
- EXTERNAL:采用这种SASL鉴权机制,用户名可以为空,并且同SSL进行验证连接安全性
- X-OAUTH2:这种鉴权机制,密码会以开放授权的方式 进行验证。是谷歌大佬定义的,建议不要使用。在
SASLXOauth2Mechanism
的注释中是有注明不建议使用的原因的 - SCRAM-SHA-1-PLUS:加密机制,墙裂建议使用
- SCRAM-SHA-1:加密机制,墙裂建议使用
- DIGEST-MD5:加密机制,墙裂建议使用
以上的几种加密机制,建议使用SCRAM-SHA-1-PLUS
,SCRAM-SHA-1
和DIGEST-MD5
。在最新的smack库中,如果服务器全部支持,框架会自动选择SCRAM-SHA-1,而至于PLAIN
不建议使用,对于鉴权数据(登录密码)只进行了Base64加密以后传输。
连接openfire服务器的报文解析
SENT
表明是客户端发送的报文,RECV
是服务器返回的。
这里采用的PLAIN
去连接openfire服务器,观察分析报文内容再对SASL的鉴权进行进一步学习了解
//配置SASL机制黑名单,让客户端采用PLAIN机制鉴权
SASLAuthentication.blacklistSASLMechanism(SASLAnonymous.NAME);
SASLAuthentication.blacklistSASLMechanism(SASLXOauth2Mechanism.NAME);
SASLAuthentication.blacklistSASLMechanism(SASLExternalMechanism.NAME);
SASLAuthentication.blacklistSASLMechanism(SCRAMSHA1Mechanism.NAME);
SASLAuthentication.blacklistSASLMechanism(ScramSha1PlusMechanism.NAME);
SASLAuthentication.blacklistSASLMechanism(SASLDigestMD5Mechanism.NAME);
客户端连接服务器
//客户端请求连接服务器
SENT (0):
<stream:stream xmlns='jabber:client' to='127.0.0.1' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' xml:lang='en'>
//服务器返回,告知客户端的id
RECV (0):
<?xml version="1.0" encoding="utf-8"?>
<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" from="127.0.0.1" id="196jv2n3s5" xml:lang="en" version="1.0"/>
//鉴权验证阶段,在mechanisms标签中,告知客户端openfire服务器支持多少种mechanism,由客户端自行选择适合的mechanism进行鉴权操作
//compression标签告知客户端,服务器的压缩方法,
RECV (0):
<stream:features>
<starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"></starttls>
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
<mechanism>PLAIN</mechanism>
<mechanism>SCRAM-SHA-1</mechanism>
<mechanism>CRAM-MD5</mechanism>
<mechanism>DIGEST-MD5</mechanism>
</mechanisms>
<compression xmlns="http://jabber.org/features/compress">
<method>zlib</method>
</compression>
<ver xmlns="urn:xmpp:features:rosterver"></ver>
<register xmlns="http://jabber.org/features/iq-register"></register>
</stream:features>
鉴权过程
//采用PLAIN,直接把账号名和密码Base64编码以后,打包发送。打包的实现看SASLMechanism.getAuthenticationText()方法
SENT (0):
<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHd6aAAxMjM0NTY=</auth>
//success表明鉴权成功
RECV (0):
<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>
握手
//客户端发送一个带有id的请求
SENT (0):
<stream:stream xmlns='jabber:client' to='127.0.0.1' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' id='196jv2n3s5' xml:lang='en'>
//服务器回应待会一些参数
RECV (0):
<?xml version='1.0' encoding='utf-8'?>
<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" from="127.0.0.1" id="196jv2n3s5" xml:lang="en" version="1.0">
<stream:features>
<compression xmlns="http://jabber.org/features/compress">
<method>zlib</method>
</compression>
<ver xmlns="urn:xmpp:features:rosterver" />
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind" />
<session xmlns="urn:ietf:params:xml:ns:xmpp-session">
<optional />
</session>
<sm xmlns='urn:xmpp:sm:2' />
<sm xmlns='urn:xmpp:sm:3' />
</stream:features>
</stream:stream>
开启压缩
//客户端请求开启压缩传输
SENT (0):
<compress xmlns='http://jabber.org/protocol/compress'><method>zlib</method></compress>
//服务器同意开启
RECV (0):
<compressed xmlns='http://jabber.org/protocol/compress'/>
握手
//客户端发起再次请求
SENT (0):
<stream:stream xmlns='jabber:client' to='127.0.0.1' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' id='196jv2n3s5' xml:lang='en'>
//服务器回应
RECV (0):
<?xml version='1.0' encoding='utf-8'?>
<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" from="127.0.0.1" id="196jv2n3s5" xml:lang="en" version="1.0">
<stream:features>
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
<mechanism>PLAIN</mechanism>
<mechanism>SCRAM-SHA-1</mechanism>
<mechanism>CRAM-MD5</mechanism>
<mechanism>DIGEST-MD5</mechanism>
</mechanisms>
<ver xmlns="urn:xmpp:features:rosterver" />
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind" />
<session xmlns="urn:ietf:params:xml:ns:xmpp-session">
<optional />
</session>
<sm xmlns='urn:xmpp:sm:2' />
<sm xmlns='urn:xmpp:sm:3' />
</stream:features>
</stream:stream>
//资源绑定
SENT (0):
<iq id='rhrxe-3' type='set'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>wzh</resource></bind></iq>
//服务器资源绑定成功
RECV (0):
<iq type="result" id="rhrxe-3" to="127.0.0.1/196jv2n3s5"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>wzh@127.0.0.1/wzh</jid></bind></iq>
//请求开启流服务器(XEP-0198),需要4.0以后的openfire服务器才支持
SENT (0):
<enable xmlns='urn:xmpp:sm:3' resume='true'/>
//服务器支持该协议的话,开启流服务器
RECV (0):
<enabled xmlns="urn:xmpp:sm:3" resume="true" id="d3poADE5Nmp2Mm4zczU="/>
接下来采用SCRAM-SHA-1
机制,如果不对SASL进行配置的话,这是默认被采用的机制,下面是登录报文
//默认步骤,请求连接服务器
SENT (0):
<stream:stream xmlns='jabber:client' to='127.0.0.1' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' xml:lang='en'>
//服务器接收到请求,告诉客户端你的id
RECV (0):
<?xml version='1.0' encoding='utf-8'?>
<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" from="127.0.0.1" id="3fvx965utw" xml:lang="en" version="1.0"></stream:stream>
//鉴权验证阶段,在mechanisms标签中,告知客户端openfire服务器支持多少种mechanism,由客户端自行选择适合的mechanism进行鉴权操作
//compression标签告知客户端,服务器的压缩方法,
RECV (0):
<?xml 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>PLAIN</mechanism>
<mechanism>SCRAM-SHA-1</mechanism>
<mechanism>CRAM-MD5</mechanism>
<mechanism>DIGEST-MD5</mechanism>
</mechanisms>
<compression xmlns="http://jabber.org/features/compress">
<method>zlib</method>
</compression>
<ver xmlns="urn:xmpp:features:rosterver" />
<register xmlns="http://jabber.org/features/iq-register" />
</stream:features>
鉴权过程
//challenge-response校验过程,关于程序的实现可以从XMPPTCPConnection的login过程去查看具体实现
//客户端选用SCRAM-SHA-1机制,发送初始的字符串,由ScramMechanism.getAuthenticationText()方法生成,也可以是null
SENT (0):
<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='SCRAM-SHA-1'>biwsbj13emgscj0wP2subiQzXyQ4MF9EUjRjPydAYmouSGh5ZVsvM1BIeg==</auth>
//服务器返回一个challenge,俗称挑战码,如果客户端挑战成功的话,登录成功。需要注意的是,如果在这里SASL验证失败,这个连接不能重新验证,需要重新builder一个连接再次登录
RECV (0):
<challenge xmlns="urn:ietf:params:xml:ns:xmpp-sasl">cj0wP2subiQzXyQ4MF9EUjRjPydAYmouSGh5ZVsvM1BIemEwNWM4ZWMwLWUxN2YtNGMzYS05NDVkLWZjMWUxNTNlYTNmYixzPW1Ha3pxNTk3QkdGSU5RYU1mc2xCUGdKUzkxWDlVSWVHLGk9NDA5Ng==</challenge>
SENT (0):
<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>Yz1iaXdzLHI9MD9rLm4kM18kODBfRFI0Yz8nQGJqLkhoeWVbLzNQSHphMDVjOGVjMC1lMTdmLTRjM2EtOTQ1ZC1mYzFlMTUzZWEzZmIscD1rSGhBMXZKYVJiMmowTFZ3R01ibkN4NnFqWFk9</response>
RECV (0):
<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl">dj0wQW9qeG5kMXhHWFNYbTVVSlhLeHBMTUFyczA9</success>
握手及其他连接
//握手
SENT (0):
<stream:stream xmlns='jabber:client' to='127.0.0.1' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' id='3fvx965utw' xml:lang='en'>
RECV (0):
<?xml version='1.0' encoding='utf-8'?>
<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" from="127.0.0.1" id="3fvx965utw" xml:lang="en" version="1.0">
<stream:features>
<compression xmlns="http://jabber.org/features/compress">
<method>zlib</method>
</compression>
<ver xmlns="urn:xmpp:features:rosterver" />
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind" />
<session xmlns="urn:ietf:params:xml:ns:xmpp-session">
<optional />
</session>
<sm xmlns='urn:xmpp:sm:2' />
<sm xmlns='urn:xmpp:sm:3' />
</stream:features>
</stream:stream>
//开启压缩
SENT (0):
<compress xmlns='http://jabber.org/protocol/compress'><method>zlib</method></compress>
RECV (0):
<compressed xmlns='http://jabber.org/protocol/compress'/>
//握手
SENT (0):
<stream:stream xmlns='jabber:client' to='127.0.0.1' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' id='3fvx965utw' xml:lang='en'>
RECV (0):
<?xml version='1.0' encoding='utf-8'?>
<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" from="127.0.0.1" id="3fvx965utw" xml:lang="en" version="1.0">
<stream:features>
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
<mechanism>PLAIN</mechanism>
<mechanism>SCRAM-SHA-1</mechanism>
<mechanism>CRAM-MD5</mechanism>
<mechanism>DIGEST-MD5</mechanism>
</mechanisms>
<ver xmlns="urn:xmpp:features:rosterver" />
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind" />
<session xmlns="urn:ietf:params:xml:ns:xmpp-session">
<optional />
</session>
<sm xmlns='urn:xmpp:sm:2' />
<sm xmlns='urn:xmpp:sm:3' />
</stream:features>
</stream:stream>
//绑定资源
SENT (0):
<iq id='GJh2Y-3' type='set'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>wzh</resource></bind></iq>
RECV (0):
<iq type="result" id="GJh2Y-3" to="127.0.0.1/3fvx965utw"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>wzh@127.0.0.1/wzh</jid></bind></iq>
//开启流服务
SENT (0):
<enable xmlns='urn:xmpp:sm:3' resume='true'/>
RECV (0):
<enabled xmlns="urn:xmpp:sm:3" resume="true" id="d3poADNmdng5NjV1dHc="/>
以上是对客户端连接openfire服务器登录报文的初步学习,关于使用TLS连接会在下篇文章中写出。
网友评论