一、Xmpp资源绑定
XMPP协议设计中引入了一个抽象的资源绑定过程,何为资源,如何绑定?
首先这得从JID的格式设计说起,JID是XMPP前身Jabber协议ID的简写,用于唯一标识一个客户身份。一个合法的 JID 包括一组排列好的元素,包括域名(domain identifier),节点名(node identifier),和资源名(resource identifier),如下:
jid = [ node "@" ] domain [ "/" resource ] ,所有 JID 都是基于上述的结构,类似 { user@host/resource }这种结构。
- node:是对用户的抽象,既可以代表一个真实的用户,也能表示一个虚拟用户如一个聊天室等。
- domain:表达了客户所连接的服务器,在实践中通常表示一个特定的集群,由同一domain来表示。
- resource:它通常表示一个特定的会话,连接。对于服务器和和其他客户端来说,资源名是不透明的。
资源名的获得需要经历一个资源绑定的过程,这个过程按照XMPP协议约定是在SASL握手完成后,由客户端重新发起初始化流请求后。
服务器向客户端声明资源绑定特性,过程如下:
客户端发起资源绑定请求,并指定一个绑定的资源名
pc-win-someone
服务端响应资源绑定请求,并返回绑定后的Full JID名
[email protected]/pc-win-someone-server-gen-random-string
以上过程即完成了资源绑定,那么资源绑定有什么作用呢,注意查看协议xml中客户端端请求绑定资源名为pc-win-someone,通常实现中可考虑用客户端的平台相关标识,例如 pc-win标示pc下的windows平台等,标识连接客户端的平台和自身名称,但XMPP协议约定resource由服务端按照每客户端生成随机值,用于唯一标识一个客户端一次连接会话。因此服务端的实现在客户端请求资源名后添加了随机生成的唯一后缀,用于区分不同的客户端连接。
那么如此设计的目的何在?
主要考虑方便同账号用户的多点登陆(手机、pad、pc端等多点同时在线),通过resource区分同一用户的不同接入点,由node+domain+resource组成唯一的用户在线标识。通过用户ID形成一对多的用户接入映射,方便获得同一账号的多个接入信息,可灵活的设计多点登陆时用户的自选策略(是否踢下其他登陆、或选择最近登陆接收消息等)。
二、Xmpp安全机制
XMPP(Extensible Messaging and Presence Protocol)是一个应用于实时通信的开放协议,定义了有关即时消息通信的各方面内容,本文主要是关于XMPP安全机制的介绍以及设计实现思考。
XMPP包含一个保证流安全的方法来防止篡改和偷听,包括两个层次的安全机制,分别是TLS(Tansport Layer Security)和 SASL(Simple Authentication Security Layer)。
TLS主要用于保证传输通道安全,SASL用于用户鉴权认证,协商流程如下:
- 客户端发起,流初始化(建立TCP连接,发送如下格式数据)
- 服务端应答流初始化
- 服务端发送TLS流特征说明
- 客户端发起TLS握手
- 服务端应答
-- 握手成功,继续
-- 握手失败,结束流,关闭TCP连接
- 握手成功后,客户端重新初始化加密流,并采用安全加密传输(通常由SSL实现),注意:这一步之后的交互数据全部经过加密传输TLS协商完成。
- 服务端应答加密流初始化
- 服务端发送SASL特征说明,mechanism指出了服务端支持的认证机制,有关SASL认证的机制可参考RFC4422[html] [view plain]
EXTERNAL
SCRAM-SHA-1-PLUS
SCRAM-SHA-1
PLAIN
- 客户端选择认证机制
AGp1bGlldAByMG0zMG15cjBtMzA=
以下认证过程根据选择的认证机制有所不同,实践中真正的实现一般就具体采用一种认证,依赖具体的用户权限系统进行设计。
这里说说其中一种常见Challenge-Response认证机制
客户端在发送
服务端接收到认证请求后,发回挑战码,挑战码由服务器每次随机生成(挑战码也经过BASE64编码)
cmVhbG09InNvbWVyZWFsbSIsbm9uY2U9Ik9BNk1HOXRFUUdtMmhoIixxb3A9ImF1dGgi
LGNoYXJzZXQ9dXRmLTgsYWxnb3JpdGhtPW1kNS1zZXNzCg==
客户端接收到挑战码后,根据用户输入的密码(原文)按注册用户时保存密码采用Hash算法进行同样的计算,得到与服务后端数据库存储的密码Hash同样的值,再以此为种子对挑战码进行特定算法计算。
具体算法可以用对称加密、二次加盐hash等,经过计算后的挑战码作为响应发回给服务端,如下:
dXNlcm5hbWU9InNvbWVub2RlIixyZWFsbT0ic29tZXJlYWxtIixub25jZT0i
T0E2TUc5dEVRR20yaGgiLGNub25jZT0iT0E2TUhYaDZWcVRyUmsiLG5jPTAw
MDAwMDAxLHFvcD1hdXRoLGRpZ2VzdC11cmk9InhtcHAvZXhhbXBsZS5jb20i
LHJlc3BvbnNlPWQzODhkYWQ5MGQ0YmJkNzYwYTE1MjMyMWYyMTQzYWY3LGNo
YXJzZXQ9dXRmLTgK
服务端根据之前提供的UID获取用户保存的密码hash值,对响应码进行相同的算法计算后与客户端传递的挑战码响应进行碰撞认证
-- 成功,返回
-- 失败,返回
结束流,并关闭TCP连接
三、Xmpp消息格式
交换消息是XMPP的一个基本用途并且随之而来的是一个用户生成一个发给另一个实体的消息节。
XMPP定义的消息节语法完整格式如下:
I implore you!
Úpěnlivě prosím!
Wherefore art thou, Romeo?
Pročež jsi ty, Romeo?
0e3141cd80894871a68e6fe6b1ec56fa
- from属性:
设置消息发送方自身的Full JID(node@domain/resource) - to属性:
设置消息接收方的Bare JID(node@domain),通常第一次发送方无法确知接收方的Full JID,通过服务器中转路由时由服务器根据Base JID映射接收方的Full JID。 但如果这个消息是在回复之前接收到的消息,则to属性应该包含对方完整的Full JID。
如此设计的好处在于:当to属性设定为Full JID时可以帮助服务器省却了接收者资源定位(接入定位),在一个IM服务集群环境中这种定位通常意味着一次分布式缓存读取操作。 - type属性:XMPP约定了type的枚举值,包括:
chat: 表明在一个点对点会话环境中的聊天消息。
groupchat:表明在一个多人会话环境中的聊天消息。
headline: 通常一些系统通知、警告、实时数据更新采用此类型,这类消息不期待客户端回复或响应,具有很高的实时性,不需要离线存储。
normal: 默认的消息类型(缺乏type属性时),通常表达一种要求接收方必须确认的消息,一般用于系统提示强制用户确认或取消等。
error: 表示一个错误消息,可能由服务端发送给客户端,也可能是另一个客户接收端回应给客户发送端,此类消息也不需要离线存储。 子元素:
表明一个消息主题,通常客户端实现显示在聊天窗口标题栏处- 子元素:
消息内容部分 和 都允许包含多个元素标签,不同的标签根据xml:lang表达了不同的语言(XMPP可是一个国际化协议) 子元素:
用于跟踪一个会话, 该元素的作用主要在于方便客户端实现消息展示(例如:消息历史查询时按每次会话折叠显示消息),每次会话产生一个唯一的thread id,xmpp推荐采用uuid算法,具体用法可参考XEP-0201扩展协议和RFC6121。
还有一种情况是离线消息,它与正常消息的格式和处理机制又有所不同,格式如下所示:
O blessed, blessed night! I am afeard.
Being in night, all this is but a dream,
Too flattering-sweet to be substantial.
Offline Storage
离线消息中包含了一个
四、XMPP多用户文本聊天协议(MUC:Multi User Chat)
 XMPP在其XEP-0045扩展中定义了一个用于多用户文本会议(群聊)的协议,类似于聊天室、QQ群等。由于它作为一个标准协议在定义模型上力求完备,涵盖了现实中的绝大部分IM产品模型,而现实中的IM产品基本都只实现了XMPP定义的模型中的一个子集。
XMPP定义的一些基本概念:
- 房间:房间的JID标识 room@service (例如, [email protected]), 这里 "room" 是房间的名称而 "service" 是多用户聊天服务运行所在的主机名
- 房客:房客的JID标识
,nick是房客在房间的昵称 - 岗位:表达了用户和房间的长期关系。XMPP定义的岗位有:所有者(owner)、管理者(admin)、成员(member)、排斥者(outcast)
- 角色:表达了用户和房间的临时联系,它只存在与一次访问期间。XMPP定义的角色有:主持人(moderator)、与会者(paticipant)、游客(visitor)
有关岗位、角色及其权限详细描述,参考协议规范描述(角色、岗位和权限)
XMPP MUC协议扩展定义了一个广泛的用例集合,下面提取一些典型的核心场景来简要分析说明并辅助实现。
- MUC服务发现
主要用于客户端向服务器咨询是否支持MUC,协议交互细节详见:MUC Discovering - 新建房间
从房间创建的视角来看,本质上有2种类型的房间:
instant room 临时房间(类似于临时会话),适用于那些临时选取多个用户进行会话的场景
reserverd room 永久房间(类似于固定群) - 销毁房间
销毁房间通常仅限于房间的所有者,临时房间通常是在房间所有用户都离开后自动销毁 - 加入房间
加入房间可以有2种方式,申请和邀请 - 发言
在房间内发言方式从使用场景的角度看通常有3种:
- 向房间内所有人发言,发言者发送一个消息类型为groupchat的消息,由房间服务转发给所有与会者。
- 向部分人发言,这个场景发言者实际创建了一个临时房间,在该临时房间内进行群发。
- 向某一个人发送似有消息,这个场景退化为了一对一的单独聊天。
- 退出房间
主动退出、管理员(主持人)踢出房间
关于XMPP多用户文本聊天协议的完整用例集合,请参考协议规范。