openfire登陆及密码验证过程

来源:互联网 发布:淘宝卖潮牌的店 编辑:程序博客网 时间:2024/06/01 10:16

QQ: 277869380. 因为sina博客改版后xml字符无法正常显示。现在把sina博客上的内容迁移过来

本文有点乱,是我在openfire开发过程中随手写下来的


openfire支持多种方式的登陆, 包括TLS安全登陆和多种账号密码验证方式。

 

  

openfire的密码获取和设置部分由authProvider(DefaultAuthProvider)完成。

authProvider.getPassword(username.toLowerCase()); 从数据库中获取密码并解密。

 

openfire使用Blowfish对明文密码进行加密。它是一种对称密钥的分组密码,使用32位至448位的可变长度密钥,应用于内部加密或加密输出。openfire首先通过lCBCIV = m_rndGen.nextLong();对每一个明文密码产生一个随机数lCBCIV(8字节),然后使用这个随机数(64位密钥)来加密明文密码,最后一起存放在数据库表ofuser内。

 

openfire非常灵活,在实际项目中可以在配置文件(ofproperty表)中修改authProvider,加入自己的密码验证方

式。

 

登陆密码验证由StazaHandler的
("auth".equals(tag)) {
            // User is trying to authenticate using SASL
            startedSASL = true;
            // Process authentication stanza
            saslStatus = SASLAuthentication.handle(session, doc);
            处理。
           
       

最简单的当属PLAIN方式(base64编码)

    
           
         SASLAuthentication 解码base64码  
         token = StringUtils.decodeBase64(doc.getText().trim());
        
        
        
  SaslServerPlainImpl    PLAIN码的验证  
    public byte[] evaluateResponse(byte[] response)
        throws SaslException {
        if (completed) {
            throw new IllegalStateException("PLAIN authentication already completed");
        }
        if (aborted) {
            throw new IllegalStateException("PLAIN authentication previously aborted due to error");
        }
        try {
            if(response.length != 0) {
                String data = new String(response, "UTF8");
                StringTokenizer tokens = new StringTokenizer(data, "");
                if (tokens.countTokens() > 2) {
                    username = tokens.nextToken();
                    principal = tokens.nextToken();
                } else {
                    username = tokens.nextToken();
                    principal = username;
                }
                password = tokens.nextToken();
                NameCallback ncb = new NameCallback("PLAIN authentication ID: ",principal);
                VerifyPasswordCallback vpcb = new VerifyPasswordCallback(password.toCharArray());
                cbh.handle(new Callback[]{ncb,vpcb});

                if (vpcb.getVerified()) {
                    vpcb.clearPassword();
                    AuthorizeCallback acb = new AuthorizeCallback(principal,username);
                    cbh.handle(new Callback[]{acb});
           
           
           
            然后是XMPPCallbackHandler
           
            AuthToken at = AuthFactory.authenticate(name, new String(vpcb.getPassword()));
            ||
            authProvider.authenticate(username, password);   AuthFactory
            ||
            DefaultAuthProvider.authenticate(String, String) line: 62 
            ||
            DefaultAuthProvider.getPassword(String) line: 145 访问数据库获取密码

 

PLAIN登陆时双方消息交互

<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">YWRtaW5Aemhhb3l1bmh1YQBhZG1pbgAxMjM0NTY=</auth>

算法: base64.encode(username+@+host++username++password)

admin@zhaoyunhuaadmin123456

 

 

<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN"> cm9vdEB6aGFveXVuaHVhAHJvb3QAMTIzNDU2AAA=</auth>

算法: base64.encode(username+@+host++username++password)

root@zhaoyunhuaroot123456

 

 

如果被允许,服务器回应

<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>

 

以上操作涉及到

...  encryption, authentication, and resource binding ...

 

然后客户端请求服务器绑定资源

<iq type='set' id='bind_1'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/></iq>

 

服务器响应:

<iq type="result" id="bind_1" to="zhaoyunhua/dce1d669"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>dce1d669@zhaoyunhua/dce1d669</jid></bind></iq>

生成JID

 

请求一个session

<iq xmlns="jabber:client" id="cF25i-0" type="set"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></iq>

 

客户端发送在线消息

<presence id="9fU15-32"><status>Online</status><priority>1</priority></presence>

  

 

 在登陆过程中 , 会创建一个localClientSession
public class LocalClientSession extends LocalSession implements ClientSession {


会调用静态函数来创建
public static LocalClientSession createSession(String serverName, XmlPullParser xpp, Connection connection)
            throws XmlPullParserException {
           
            在JID生成之前, 使用connection向客户端发送信息
           
           
  
   bind的时候<iq id="PG44e-0" type="set"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><resource>Spark 2.6.3</resource></bind></iq>        
   向sessionManager添加session:
   SessionManager.addSession(LocalClientSession) line: 554 
LocalClientSession.setAuthToken(AuthToken, String) line: 600 
IQBindHandler.handleIQ(IQ) line: 154 
IQBindHandler(IQHandler).process(Packet) line: 65 
IQRouter.handle(IQ) line: 372 
IQRouter.route(IQ) line: 121 
PacketRouterImpl.route(IQ) line: 76 
ClientStanzaHandler(StanzaHandler).processIQ(IQ) line: 337 
ClientStanzaHandler.processIQ(IQ) line: 93 
ClientStanzaHandler(StanzaHandler).process(Element) line: 302 
ClientStanzaHandler(StanzaHandler).process(String, XMPPPacketReader) line: 194 
ClientConnectionHandler(ConnectionHandler).messageReceived(IoSession, Object) line: 169 

 

登出:最终由routertable remove session
           
RoutingTableImpl.removeClientRoute(JID) line: 740 
SessionManager.removeSession(ClientSession, JID, boolean, boolean) line: 1078 
SessionManager.removeSession(LocalClientSession) line: 1054 
SessionManager$ClientSessionListener.onConnectionClose(Object) line: 1169 
NIOConnection.notifyCloseListeners() line: 223 
NIOConnection.close() line: 206 
LocalClientSession(LocalSession).close() line: 322 
ClientStanzaHandler(StanzaHandler).process(String, XMPPPacketReader) line: 151 
ClientConnectionHandler(ConnectionHandler).messageReceived(IoSession, Object) line: 169 
AbstractIoFilterChain$TailFilter.messageReceived(IoFilter$NextFilter, IoSession, Object) line: 570 
SocketFilterChain(AbstractIoFilterChain).callNextMessageReceived(IoFilterChain$Entry, IoSession, Object) line: 299 
AbstractIoFilterChain.access$1100(AbstractIoFilterChain, IoFilterChain$Entry, IoSession, Object) line: 53 
AbstractIoFilterChain$EntryImpl$1.messageReceived(IoSession, Object) line: 648 

 

SessionManager的removeSession, 由此可见,
boolean removed = routingTable.removeClientRoute(fullJID);和
sessionInfoCache.remove(fullJID.toString());

    public boolean removeSession(ClientSession session, JID fullJID, boolean anonymous, boolean forceUnavailable) {
        // Do nothing if server is shutting down. Note: When the server
        // is shutting down the serverName will be null.
        if (serverName == null) {
            return false;
        }

        if (session == null) {
            session = getSession(fullJID);
        }

        // Remove route to the removed session (anonymous or not)
        boolean removed = routingTable.removeClientRoute(fullJID);

        if (removed) {
            // Fire session event.
            if (anonymous) {
                SessionEventDispatcher
                        .dispatchEvent(session, SessionEventDispatcher.EventType.anonymous_session_destroyed);
            }
            else {
                SessionEventDispatcher.dispatchEvent(session, SessionEventDispatcher.EventType.session_destroyed);

            }
        }

        // Remove the session from the pre-Authenticated sessions list (if present)
        boolean preauth_removed =
                localSessionManager.getPreAuthenticatedSessions().remove(fullJID.getResource()) != null;
        // If the user is still available then send an unavailable presence
        if (forceUnavailable || session.getPresence().isAvailable()) {
            Presence offline = new Presence();
            offline.setFrom(fullJID);
            offline.setTo(new JID(null, serverName, null, true));
            offline.setType(Presence.Type.unavailable);
            router.route(offline);
        }

        // Stop tracking information about the session and share it with other cluster nodes
        sessionInfoCache.remove(fullJID.toString());

        if (removed || preauth_removed) {
            // Decrement the counter of user sessions
            connectionsCounter.decrementAndGet();
            return true;
        }
        return false;
    }
   
 

 
 
 登陆的时候如果遇到相同的JID, 即全JID相同, 则前面的oldsession关闭:
 ClientSession oldSession = routingTable.getClientRoute(new JID(username, serverName, resource, true));
 
 int conflictCount = oldSession.incrementConflictCount();
                    if (conflictCount > conflictLimit) {
                        // Kick out the old connection that is conflicting with the new one
                        StreamError error = new StreamError(StreamError.Condition.conflict);
                        oldSession.deliverRawText(error.toXML());
                        oldSession.close();
                    }
                    else {
                        reply.setChildElement(packet.getChildElement().createCopy());
                        reply.setError(PacketError.Condition.conflict);
                        // Send the error directly since a route does not exist at this point.
                        session.process(reply);
                        return null;
                    }
                    SessionManager$ClientSessionListener.onConnectionClose(Object) line: 1154 
NIOConnection.notifyCloseListeners() line: 223 
NIOConnection.close() line: 206 
LocalClientSession(LocalSession).close() line: 322 
IQBindHandler.handleIQ(IQ) line: 139 //在这里关闭前一个连接
IQBindHandler(IQHandler).process(Packet) line: 65 
IQRouter.handle(IQ) line: 372   
IQRouter.route(IQ) line: 121 
PacketRouterImpl.route(IQ) line: 76 
ClientStanzaHandler(StanzaHandler).processIQ(IQ) line: 337 
ClientStanzaHandler.processIQ(IQ) line: 93 
ClientStanzaHandler(StanzaHandler).process(Element) line: 302 
ClientStanzaHandler(StanzaHandler).process(String, XMPPPacketReader) line: 194 


//认证
AuthorizationManager.authorize(String, String) line: 163 
XMPPCallbackHandler.handle(Callback[]) line: 128 
DigestMD5Server.validateClientResponse(byte[][]) line: 614 
DigestMD5Server.evaluateResponse(byte[]) line: 226 
SASLAuthentication.handle(LocalSession, Element) line: 325 
ClientStanzaHandler(StanzaHandler).process(String, XMPPPacketReader) line: 183 
ClientConnectionHandler(ConnectionHandler).messageReceived(IoSession, Object) line: 169 
AbstractIoFilterChain$TailFilter.messageReceived(IoFilter$NextFilter, IoSession, Object) line: 570 
SocketFilterChain(AbstractIoFilterChain).callNextMessageReceived(IoFilterChain$Entry, IoSession, Object) line: 299 
AbstractIoFilterChain.access$1100(AbstractIoFilterChain, IoFilterChain$Entry, IoSession, Object) line: 53 
AbstractIoFilterChain$EntryImpl$1.messageReceived(IoSession, Object) line: 648 
StalledSessionsFilter(IoFilterAdapter).messageReceived(IoFilter$NextFilter, IoSession, Object) line: 80 
SocketFilterChain(AbstractIoFilterChain).callNextMessageReceived(IoFilterChain$Entry, IoSession, Object) line: 299 
AbstractIoFilterChain.access$1100(AbstractIoFilterChain, IoFilterChain$Entry, IoSession, Object) line: 53 
AbstractIoFilterChain$EntryImpl$1.messageReceived(IoSession, Object) line: 648 
SimpleProtocolDecoderOutput.flush() line: 58 
ProtocolCodecFilter.messageReceived(IoFilter$NextFilter, IoSession, Object) line: 185 
SocketFilterChain(AbstractIoFilterChain).callNextMessageReceived(IoFilterChain$Entry, IoSession, Object) line: 299 
AbstractIoFilterChain.access$1100(AbstractIoFilterChain, IoFilterChain$Entry, IoSession, Object) line: 53 
AbstractIoFilterChain$EntryImpl$1.messageReceived(IoSession, Object) line: 648 
RawPrintFilter(IoFilterAdapter).messageReceived(IoFilter$NextFilter, IoSession, Object) line: 80 
RawPrintFilter.messageReceived(IoFilter$NextFilter, IoSession, Object) line: 69 
SocketFilterChain(AbstractIoFilterChain).callNextMessageReceived(IoFilterChain$Entry, IoSession, Object) line: 299 
AbstractIoFilterChain.access$1100(AbstractIoFilterChain, IoFilterChain$Entry, IoSession, Object) line: 53 
AbstractIoFilterChain$EntryImpl$1.messageReceived(IoSession, Object) line: 648 
ExecutorFilter.processEvent(IoFilter$NextFilter, IoSession, ExecutorFilter$EventType, Object) line: 239 
ExecutorFilter$ProcessEventsRunnable.run() line: 283 
ThreadPoolExecutor$Worker.runTask(Runnable) line: 886 
ThreadPoolExecutor$Worker.run() line: 908 
NamePreservingRunnable.run() line: 51 
Thread.run() line: 662 

 

AuthorizationManager.authorize(String, String) line: 163 
XMPPCallbackHandler.handle(Callback[]) line: 128 
SaslServerPlainImpl.evaluateResponse(byte[]) line: 125 
SASLAuthentication.handle(LocalSession, Element) line: 274 
ClientStanzaHandler(StanzaHandler).process(String, XMPPPacketReader) line: 179 
ClientConnectionHandler(ConnectionHandler).messageReceived(IoSession, Object) line: 169 

最后附上消息交互过程

 

1. 连接到服务器, 发送xml版本并初始化一个流stream

send:<?xml version='1.0'?><stream:stream to = 'zhaoyunhua' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>

 

2. 接收到服务器发过来的验证方式
recv:<?xml version='1.0' encoding='UTF-8'?><stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" from="openfire-server" id="2ff3891b" xml:lang="en" version="1.0"><stream:features><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism>DIGEST-MD5</mechanism><mechanism>JIVE-SHAREDSECRET</mechanism><mechanism>PLAIN</mechanism><mechanism>ANONYMOUS</mechanism><mechanism>CRAM-MD5</mechanism></mechanisms><compression xmlns="http://jabber.org/features/compress"><method>zlib</method></compression><auth xmlns="http://jabber.org/features/iq-auth"/><register xmlns="http://jabber.org/features/iq-register"/></stream:features>

 

 

3. 发送用户名,密码,  base64编码(用户名:admin 密码: 123456)admin123456
send:<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">YWRtaW4AMTIzNDU2</auth>

 

4. 登陆成功
recv:<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>

 

5. 新建一个流stream
send:<?xml version='1.0'?><stream:stream to = 'zhaoyunhua' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>

 

6. 新建流成功, 并标注流属性
recv:<?xml version='1.0' encoding='UTF-8'?><stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" from="openfire-server" id="2ff3891b" xml:lang="en" version="1.0"><stream:features><compression xmlns="http://jabber.org/features/compress"><method>zlib</method></compression><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></stream:features>

 

7. 绑定资源, 就是设定XXXX@domain/resource的resouse
send:<iq id="cF25i-0" type="set"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><resource>Gent.3</resource></bind></iq>

 

8. 绑定成功。
recv:<iq type="result" id="cF25i-0" to="openfire-server/2ff3891b"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>admin@openfire-server/Gent.3</jid></bind></iq>

 

9. 获得session
send:<iq xmlns="jabber:client" id="cF25i-1" type="set"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></iq>
recv:<iq type="result" id="cF25i-1" to="admin@openfire-server/Gent.3"/>

 

10. 发送在线消息
send:<presence id="cF25i-32"><status>Online</status><priority>1</priority></presence>


0 0
原创粉丝点击