2018-05-20-基于openfire服务器的web即时通讯-003登录
程序员文章站
2022-07-13 14:42:29
...
登录
1)开启http绑定
在openfire控制台中启用http绑定(因为websocket连接是通过http请求建立的),如下图。将使用 7070端口访问。
2)使用到的Strophe.Connection对象
登录将要用到Strophe.Connection,其实我们后面主要使用这个对象。
// 创建该对象可以传入两个参数,第二个参数是可选项。第一个参数是服务器端url。
Strophe.Connection = function (service, options) {}
关于service
因为是基于websocket,openfire提供了两个端口,分别是:
ws://domain:7070/ws/
wss://domain:7443/ws/ 这种的需要CA证书
需要CA证书这种的没有去做,百度了一段。
wss的实现和https的实现没有本质的区别,都是只需在websocket(ws)或者http的基础上添加证书就好。具体就是服务器server端加载自己的证书文件cert和私钥key,客户端(浏览器)在Root certification中添加CA。我的问题出现的原因是浏览器无法信任加入的CA,毕竟这个证书是我自己做的CA,不是权威机构的,所以浏览器不认可,自然也就无法完成握手了。
解决思路就只能是强制浏览器服务器证书,具体的做法就是在浏览器的安全选项中添加例外,将服务器的ip地址(https://:port)加进去就好了,描述的不是很清楚,希望可以供大家参考。
关于options:没有使用到
// 源码中倒是提了一句,如果连接wss,使用如下格式
// So to connect to "wss://CURRENT_HOSTNAME/xmpp-websocket" you would call
var conn = new Strophe.Connection("/xmpp-websocket/", {protocol: "wss"});
3)使用到Conenction对象的connect()方法
我们调用Connection对象connect()方法来连接openfire服务器
// 其中wiat,hold,route方法是使用BOSH连接openfire使用的,故可以不去看,authcid也可不看
// jid 需要一个纯JID,或者是全JID,
// pass 密码
// callback 登录结果回调
connect: function (jid, pass, callback, wait, hold, route, authcid)
关于JID
aaa@qq.com/resource // 全JID
aaa@qq.com // 纯jid
node代表账号
domain代表服务器的域,也可以使用服务器所在ip表示
resource是一个随机字符串,资源的意义在于,一个账号可以在多个地方登陆,他们的resource部分不同。
可由客户端指定(即在connection方法中传入的是一个全JID),
若客户端不指定(即在connection方法中传入的是一个纯JID),则服务器端可以分配一个给客户端。
我预先注册的一个账号
aaa@qq.com127.0.0.1/whatever
aaa@qq.com/whatever
lxy就是我的账号,127.0.0.1或者openfire就是服务器的域,whatever就是资源
关于callback
// 源码中,登陆回调返回两个参数
// 第一个是Strophe.Status列举的登录状态码
// 第二个是在发生错误时的错误原因,如果没有错误就为空
this.connect_callback(status, condition);
4)随机生成资源
由于许多地方都使用到了随机码之类的,比如iq节的id等等。此处我们用它来生成随机资源部分
webimUtils.js中的getUniqueId()方法如下
/**
* 产生唯一的id,用于节中
* @param prefix 如果提供了,则以prefix为首,如果没有提供,则自定义以:webim 为首
* @returns {String}
*/
getUniqueId : function (prefix) {
var cdate = new Date();
var offdate = new Date(2010, 1, 1);
var offset = cdate.getTime() - offdate.getTime();
// 后面多生产3位数的随机数,因为当几乎同时调用getUniqueId时,可能产生一样的ID
var hexd = parseInt(offset).toString(16) + Math.floor(Math.random()*1000).toString();
if (typeof prefix === 'string' || typeof prefix === 'number') {
return prefix + '_' + hexd;
} else {
return 'webim_' + hexd;
}
},
5)实现登录
登陆界面如下,就是用户名,密码,服务器地址三个参数
代码
webim.view.js中代码
;(function(factory) {
window.webimView = factory(jQuery);
}(function($) {
"use strict";
var view = {
// 把登录的status转换成信息
loginInfo: [
"发生错误!", // 0
"正在连接服务器中...", // 1 连接中...
"连接服务器失败!", // 2 连接失败!
"登录中...", // 3 身份认证中...
"用户名或者密码错误!", // 4 身份认证失败!
"登录成功!", // 5
"已退出!", // 6 已登出
"退出中...", // 7 登出中
"重定向!", // 8
"连接超时!" // 9
],
// JID各部分 aaa@qq.com/resource
node: '',
domain: '',
resource: '',
// 日志器
logger : null,
// 获得纯jid
getPureJid: function() {
return this.node + "@" + this.domain;
},
// 获得全jid
getFullJid: function() {
return this.node + "@" + this.domain + "/" + this.resource;
},
};
// 获取日志器
view.logger = webimLogManager.getLogger('webimView');
view.login = function(username, password, serverUrl) {
view.logger.d('用户名:' + username);
view.logger.d('密码:' + password);
view.logger.d('服务器url:' + serverUrl);
// 界面输入的用户名只有node部分
view.node = username;
// 从serverUrl获得域部分
view.domain = webimUtils.getIPFromURL(serverUrl);
// 随机生成以 res_ 开头的资源部分
view.resource = webimUtils.getUniqueId("res");
// 创建Connection对象。
var connection = new Strophe.Connection(serverUrl);
// 重写Connection对象中rawInput和rawOutput方法,用来打印日志
// 其中的webimUtils.formatXml()方法用来格式化xml
connection.rawInput = function(data) {
view.logger.d('[RECEIVE]\r\n' + webimUtils.formatXml(data))
};
connection.rawOutput = function(data) {
view.logger.d('[SEND]\r\n' + webimUtils.formatXml(data));
}
// connect方法需要的jid为纯id或者全jid
// 此处使用全jid,客户端自己指定资源部分
var jid = view.getPureJid();
connection.connect(jid, password, function(status, condition) {
var connectionInfo = view.loginInfo[status];
view.logger.i("登录结果:" + status + " " + connectionInfo + " " + condition);
// 登陆界面展示登录结果
$('#webimLoginInfo').html(connectionInfo);
})
};
return view;
}));
报文
控制台打印报文如下,使用 是加上的备注
[2018-06-08 11:24:05] [DEBUG] [webimView] 用户名:lxy
[2018-06-08 11:24:05] [DEBUG] [webimView] 密码:123
[2018-06-08 11:24:05] [DEBUG] [webimView] 服务器url:ws://127.0.0.1:7070/ws/
[2018-06-08 11:24:05] [INFO] [webimView] 登录结果:1 正在连接服务器中... null
[2018-06-08 11:24:05] [DEBUG] [webimView] [SEND]
<!-- 此时底层websocket通道已经建立,进行上层XMPP的流协商。 -->
<!-- 客户端打开流 -->
<open xmlns='urn:ietf:params:xml:ns:xmpp-framing' to='127.0.0.1' version='1.0'/>
[2018-06-08 11:24:05] [DEBUG] [webimView] [RECEIVE]
<!-- 服务端打开流 -->
<open from='openfire' id='65a9z5nsk3' xmlns='urn:ietf:params:xml:ns:xmpp-framing' xml:lang='en' version='1.0'/>
[2018-06-08 11:24:05] [DEBUG] [webimView] [RECEIVE]
<!-- 服务端为客户端提供握手加密方式列表 -->
<stream:features xmlns:stream='http://etherx.jabber.org/streams'>
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
<mechanism>DIGEST-MD5</mechanism>
<mechanism>SCRAM-SHA-1</mechanism>
<mechanism>JIVE-SHAREDSECRET</mechanism>
<mechanism>PLAIN</mechanism>
<mechanism>ANONYMOUS</mechanism>
<mechanism>CRAM-MD5</mechanism>
</mechanisms>
<auth xmlns='http://jabber.org/features/iq-auth'/>
</stream:features>
[2018-06-08 11:24:05] [DEBUG] [webimView] [SEND]
<!-- 客户端选择SCRAM-SHA-1加密方式,并且带了一个串过去,此串的生成与用户名相关
Strophe中提供多个加密方式,同时用一个数字设定每种加密的优先级,值越大越优先。
如果服务端不支持SCRAM-SHA-1,Strophe会选择第二优先级加密方式继续与服务端协商,以此类推。
一般我们对这个没有要求,按照Strophe提供的优先级,如果有需要可以到源码中修改各个加密方式
的优先级
-->
<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='SCRAM-SHA-1'>biwsbj1seHkscj1kNDFkOGNkOThmMDBiMjA0ZTk4MDA5OThlY2Y4NDI3ZQ==</auth>
[2018-06-08 11:24:05] [DEBUG] [webimView] [RECEIVE]
<!-- 服务端客户端协商后使用SCRAM-SHA-1加密方式,服务端发起挑战
挑战-响应模式:客户端服务端间不通过明文传递密码,通过给定的串,使用选定的加密方式(SCRAM-SHA-1)进行对密码进行处理,达到验证的效果。
-->
<challenge xmlns="urn:ietf:params:xml:ns:xmpp-sasl">cj1kNDFkOGNkOThmMDBiMjA0ZTk4MDA5OThlY2Y4NDI3ZTI1NjFhMWI2LTMwZjUtNGExZi1iODg2LTU2ZWNiNWM1YTZkZSxzPUJRMndnODdTUkZWSjRDZmkyUXkwOGg5YTR4Z2IvMTBFLGk9NDA5Ng==</challenge>
[2018-06-08 11:24:05] [DEBUG] [webimView] [SEND]
<!-- 客户端响应挑战 -->
<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>Yz1iaXdzLHI9ZDQxZDhjZDk4ZjAwYjIwNGU5ODAwOTk4ZWNmODQyN2UyNTYxYTFiNi0zMGY1LTRhMWYtYjg4Ni01NmVjYjVjNWE2ZGUscD1oNml4YkJSV1JIZGVCbW1uMkhUcGw4ci9SYm89</response>
[2018-06-08 11:24:05] [DEBUG] [webimView] [RECEIVE]
<!-- 通过success可以知道挑战-响应成功了
可能结果:
初始化实体退出这个验证机制的握手.<abort/>
接收方实体报告握手失败.<failure/>
接收方实体报告握手成功.<success/>
-->
<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl">dj1VeHQyeStxeUpZaDdWVUdQTHVYeG1mRm4walE9</success>
[2018-06-08 11:24:05] [DEBUG] [webimView] [SEND]
<!-- 身份认证成功后根据协议规定双方重启流 -->
<open xmlns='urn:ietf:params:xml:ns:xmpp-framing' to='127.0.0.1' version='1.0'/>
[2018-06-08 11:24:05] [DEBUG] [webimView] [RECEIVE]
<!-- 身份认证成功后双方重启流 -->
<open from='openfire' id='65a9z5nsk3' xmlns='urn:ietf:params:xml:ns:xmpp-framing' xml:lang='en' version='1.0'/>
[2018-06-08 11:24:05] [DEBUG] [webimView] [RECEIVE]
<!-- 服务端询问客户端是否需要绑定一些什么东西:
bind(资源绑定),
session(会话),
sm -->
<stream:features xmlns:stream='http://etherx.jabber.org/streams'>
<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:3'/>
</stream:features>
[2018-06-08 11:24:05] [DEBUG] [webimView] [SEND]
<!-- 由于我们使用的全jid,所以客户端发送给服务端说绑定资源为xxx
如果使用纯jid,那么此处发送的报文将为如下样例:
<iq type='set' id='_bind_auth_2' xmlns='jabber:client'>
<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>
</iq>
-->
<iq type='set' id='_bind_auth_2' xmlns='jabber:client'>
<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>
<resource>res_3d58500ae0191</resource>
</bind>
</iq>
[2018-06-08 11:24:05] [DEBUG] [webimView] [RECEIVE]
<!-- 服务端同意所绑定的资源, 并返回全JID
如果上面一步没有绑定resource,那么将有服务端随机生成一个resource部分。如下样例:
<iq xmlns="jabber:client" type="result" id="_bind_auth_2" to="openfire/8td27u8hg1">
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
<jid>aaa@qq.com/8td27u8hg1</jid>
</bind>
</iq>
-->
<iq xmlns="jabber:client" type="result" id="_bind_auth_2" to="openfire/65a9z5nsk3">
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
<jid>aaa@qq.com/res_3d58500ae0191</jid>
</bind>
</iq>
[2018-06-08 11:24:05] [DEBUG] [webimView] [SEND]
<!-- 没有绑定session -->
<iq type='set' id='_session_auth_2' xmlns='jabber:client'>
<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>
</iq>
[2018-06-08 11:24:05] [DEBUG] [webimView] [RECEIVE]
<iq xmlns="jabber:client" type="result" id="_session_auth_2" to="aaa@qq.com/res_3d58500ae0191"/>
<!-- 至此登录成功 -->
[2018-06-08 11:24:05] [INFO] [webimView] 登录结果:5 登录成功! null
6)登录成功之后要做的事
登录成功后,此处由于自己还未发送出席节,所以对于“你”的好友的客户端,“你”还是离线状态。
此时我们应该请求如下一些信息
- 发送iq vcard节,请求自己的名片信息(最主要的是获得自己的头像)
- 发送iq roster节,请求好友列表
- 根据好友列表,发送iq vcard,请求好友的名片信息(也是为了获得好友的头像)
- 根据需要请求其他的一些东西,如MUC会议室列表,disco#item服务端提供服务列表 ……
发送一个出席节 —— 这是一个分水岭,发送完出席节后,正式意味着“你”上线了,在这之前“你”虽然和
服务器已经建立连接了,但是状态是离线的。由于“你”上线了,所以“你”会收到服务端的一些信息:
- 在你离线时好友给你发送的信息
- 在你离线时别人申请你为好友的请求
- ….