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

登录

1)开启http绑定

在openfire控制台中启用http绑定(因为websocket连接是通过http请求建立的),如下图。将使用 7070端口访问。

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

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

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)实现登录

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

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

代码

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各部分 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服务端提供服务列表 ……

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

服务器已经建立连接了,但是状态是离线的。由于“你”上线了,所以“你”会收到服务端的一些信息:

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

猜你喜欢

转载自blog.csdn.net/jkl852qaz/article/details/80946782