欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-07-13 14:42:29
...

登录

1)开启http绑定

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

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

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

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

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

代码

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

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

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

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