2018-05-20-基于openfire服务器的web即时通信-003登陆

登陆

1)开启http绑定

在openfire控制台中启用http绑定(由于websocket链接是经过http请求创建的),以下图。将使用 7070端口访问。javascript

http://image.linxingyang.net/image/note/2018-05-10-webim/01.png

2)使用到的Strophe.Connection对象

登陆将要用到Strophe.Connection,其实咱们后面主要使用这个对象。html

// 建立该对象能够传入两个参数,第二个参数是可选项。第一个参数是服务器端url。
Strophe.Connection = function (service, options) {}

关于service

由于是基于websocket,openfire提供了两个端口,分别是:
ws://domain:7070/ws/
wss://domain:7443/ws/ 这种的须要CA证书

须要CA证书这种的没有去作,百度了一段。java

wss的实现和https的实现没有本质的区别,都是只需在websocket(ws)或者http的基础上添加证书就好。具体就是服务器server端加载本身的证书文件cert和私钥key,客户端(浏览器)在Root certification中添加CA。个人问题出现的缘由是浏览器没法信任加入的CA,毕竟这个证书是我本身作的CA,不是权威机构的,因此浏览器不承认,天然也就没法完成握手了。node

解决思路就只能是强制浏览器服务器证书,具体的作法就是在浏览器的安全选项中添加例外,将服务器的ip地址(https://:port)加进去就行了,描述的不是很清楚,但愿能够供你们参考。web

关于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

node@domain/resource // 全JID
node@domain // 纯jid

node表明帐号
domain表明服务器的域,也可使用服务器所在ip表示
resource是一个随机字符串,资源的意义在于,一个帐号能够在多个地方登录,他们的resource部分不一样。
    可由客户端指定(即在connection方法中传入的是一个全JID),
    若客户端不指定(即在connection方法中传入的是一个纯JID),则服务器端能够分配一个给客户端。

我预先注册的一个帐号
lxy@127.0.0.1/whatever
lxy@openfire/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)实现登陆

登录界面以下,就是用户名,密码,服务器地址三个参数websocket

http://image.linxingyang.net/image/note/2018-05-10-webim/15.png

代码

webim.view.js中代码session

;(function(factory) {
    window.webimView = factory(jQuery);
}(function($) {
 "use strict";

    var view = {
        // 把登陆的status转换成信息
        loginInfo: [
            "发生错误!", // 0
            "正在链接服务器中...", // 1 链接中...
            "链接服务器失败!", // 2 链接失败!
            "登陆中...", // 3 身份认证中...
            "用户名或者密码错误!", // 4 身份认证失败!
            "登陆成功!", // 5
            "已退出!", // 6 已登出
            "退出中...", // 7 登出中
            "重定向!", // 8
            "链接超时!" // 9
        ],

        // JID各部分 node@domain/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>lxy@openfire/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>lxy@openfire/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="lxy@openfire/res_3d58500ae0191"/>
<!-- 至此登陆成功 -->
[2018-06-08 11:24:05] [INFO]  [webimView]  登陆结果:5 登陆成功! null

6)登陆成功以后要作的事

登陆成功后,此处因为本身还未发送出席节,因此对于“你”的好友的客户端,“你”仍是离线状态。

此时咱们应该请求以下一些信息

  • 发送iq vcard节,请求本身的名片信息(最主要的是得到本身的头像)
  • 发送iq roster节,请求好友列表
  • 根据好友列表,发送iq vcard,请求好友的名片信息(也是为了得到好友的头像)
  • 根据须要请求其余的一些东西,如MUC会议室列表,disco#item服务端提供服务列表 ……

发送一个出席节 —— 这是一个分水岭,发送完出席节后,正式意味着“你”上线了,在这以前“你”虽然和

服务器已经创建链接了,可是状态是离线的。因为“你”上线了,因此“你”会收到服务端的一些信息:

  • 在你离线时好友给你发送的信息
  • 在你离线时别人申请你为好友的请求
  • ….