openfire源码分析---7

用户注册

本章介绍openfire中的用户注册,分成两步介绍。

第一个请求

客户端首先发起一个xmpp请求,如下所示

<iq id="#id" to="#serverName" type="get">
    <query xmlns="jabber:iq:register">query>
iq>

这里,#id和#serverName都是在上一章中创建的,该请求最终会到达ClientStanzaHandler的process函数,该函数从mina框架中的IoSession取出处理器ClientStanzaHandler,该ClientStanzaHandler在第一次创建IoSession时构造的,第五章中分析了,然后调用其process方法,为了方便分析,这里贴出关键代码,

    public void process(String stanza, XMPPPacketReader reader) throws Exception {

        boolean initialStream = stanza.startsWith(") || stanza.startsWith(");
        if (!sessionCreated || initialStream) {

            ...

        }

        ...

        Element doc = reader.read(new StringReader(stanza)).getRootElement();

        ...

        String tag = doc.getName();
        process(doc);
    }

这里,由于已经创建了LocalClientSession,这里不会进入到第一个if语句里,然后继续调用proc函数,

    private void process(Element doc) throws UnauthorizedException {

        String tag = doc.getName();
        if ("message".equals(tag)) {

            ...

        }
        else if ("presence".equals(tag)) {

            ...

        }
        else if ("iq".equals(tag)) {
            IQ packet;
            try {
                packet = getIQ(doc);
            }
            catch (IllegalArgumentException e) {

            }

            ...

            processIQ(packet);
        }
        else {

        }
    }

getIQ定义在StanzaHandler中,

    private IQ getIQ(Element doc) {
        Element query = doc.element("query");
        if (query != null && "jabber:iq:roster".equals(query.getNamespaceURI())) {
            return new Roster(doc);
        }
        else {
            return new IQ(doc, !validateJIDs());
        }
    }

根据XMPP协议,这里就是构造一个IQ,validateJIDs表是是否需要对to或者from的JID进行验证,这里默认返回true,表示需要对其进行验证,并将其添加进缓存中。
回到process函数中,接下来调用processIQ进行处理,定义在ClientStanzaHandler中,

    protected void processIQ(IQ packet) throws UnauthorizedException {
        packet.setFrom(session.getAddress());
        super.processIQ(packet);
    }

这里先要在packet中设置from变量,这里个人认为是为了防止后面取不出from的bug,不管它。其父类的函数如下

    protected void processIQ(IQ packet) throws UnauthorizedException {
        router.route(packet);
        session.incrementClientPacketCount();
    }

这里的router是在XMPPServer的start函数中构造的PacketRouterImpl,其route函数如下

    public void route(IQ packet) {
        iqRouter.route(packet);
    }

iqRouter也是在XMPPServer中构造的IQRouter,其route函数如下

    public void route(IQ packet) {
        JID sender = packet.getFrom();
        ClientSession session = sessionManager.getSession(sender);
        Element childElement = packet.getChildElement(); // may be null
        try {
            InterceptorManager.getInstance().invokeInterceptors(packet, session, true, false);
            JID to = packet.getTo();
            if (session != null && to != null && session.getStatus() == Session.STATUS_CONNECTED &&
                    !serverName.equals(to.toString())) {

                ...

            }
            else if (session == null || session.getStatus() == Session.STATUS_AUTHENTICATED || (
                    childElement != null && isLocalServer(to) && (
                        "jabber:iq:auth".equals(childElement.getNamespaceURI()) ||
                        "jabber:iq:register".equals(childElement.getNamespaceURI()) ||
                        "urn:ietf:params:xml:ns:xmpp-bind".equals(childElement.getNamespaceURI())))) {
                handle(packet);
            } else if (packet.getType() == IQ.Type.get || packet.getType() == IQ.Type.set) {

                ...

            }
            InterceptorManager.getInstance().invokeInterceptors(packet, session, true, true);
        }
        catch (PacketRejectedException e) {

        }
    }

这里会通过拦截器,拦截器和业务代码没有太大关系,本章暂时不分析它,后面有时间再细看。因为刚刚的XMPP消息的命名空间为jabber:iq:register,因此进入第二个if语句,调用handle进行处理,继续看

    private void handle(IQ packet) {
        JID recipientJID = packet.getTo();

        ...

        try {

            ...

           if (isLocalServer(recipientJID)) {
                Element childElement = packet.getChildElement();
                String namespace = null;
                if (childElement != null) {
                    namespace = childElement.getNamespaceURI();
                }
                if (namespace == null) {

                    ...

                }
                else {

                    ...

                    IQHandler handler = getHandler(namespace);
                    if (handler == null) {

                        ...

                    }
                    else {
                        handler.process(packet);
                    }
                }
            }
            else {

                ...

            }
        }
        catch (Exception e) {

        }
    }

这里首先判断客户端请求的XMPP消息是否发往本服务器,如果是,就从该消息中取出namespace(jabber:iq:register),然后调用getHandler根据namespace取出相应的事件处理器。

    private IQHandler getHandler(String namespace) {
        IQHandler handler = namespace2Handlers.get(namespace);
        if (handler == null) {
            for (IQHandler handlerCandidate : iqHandlers) {
                IQHandlerInfo handlerInfo = handlerCandidate.getInfo();
                if (handlerInfo != null && namespace.equalsIgnoreCase(handlerInfo.getNamespace())) {
                    handler = handlerCandidate;
                    namespace2Handlers.put(namespace, handler);
                    break;
                }
            }
        }
        return handler;
    }

这里就是从iq消息的Handler列表iqHandlers中依次取出Handler,然后比较namespace,如果相等就添加到namespace2Handlers哈希表中,然后返回。哪一个Handler对应到刚才的XMPP消息的命名空间jabber:iq:register呢?IQRegisterHandler!看看其构造函数,

    public IQRegisterHandler() {
        super("XMPP Registration Handler");
        info = new IQHandlerInfo("query", "jabber:iq:register");
    }

它是在XMPP中loadModules函数中实例化的,然后在IQRouter的initialize函数中被添加进iqHandlers中的,

    public void initialize(XMPPServer server) {

        ...

        iqHandlers.addAll(server.getIQHandlers());

        ...

    }

返回到handle函数中,取出IQRegisterHandler后,调用其process函数继续处理,该函数定义在其父类IQHandler中,

    public void process(Packet packet) throws PacketException {
        IQ iq = (IQ) packet;
        try {
            IQ reply = handleIQ(iq);
            if (reply != null) {
                deliverer.deliver(reply);
            }
        }
        catch (org.jivesoftware.openfire.auth.UnauthorizedException e) {

        }
        catch (Exception e) {

        }
    }

首先将消息包Packet强制转化为IQ消息包,然后调用handleIQ进行处理,然后调用deliver函数发送结果。handleIQ函数定义在IQRegisterHandler中,

    public IQ handleIQ(IQ packet) throws PacketException, UnauthorizedException {
        ClientSession session = sessionManager.getSession(packet.getFrom());
        IQ reply = null;

        if (IQ.Type.get.equals(packet.getType())) {
            if (!registrationEnabled) {

                ...

            }
            else {
                reply = IQ.createResultIQ(packet);
                if (session.getStatus() == Session.STATUS_AUTHENTICATED) {

                    ...

                }
                else {
                    reply.setTo((JID) null);
                    reply.setChildElement(probeResult.createCopy());
                }
            }
        }
        else if (IQ.Type.set.equals(packet.getType())) {

            ...

        }
        if (reply != null) {
            session.process(reply);
        }
        return null;
    }

这里本次客户端的请求类型为get,然后调用createResultIQ构造返回信息,并添加一些基本的信息,

    public static IQ createResultIQ(IQ iq) {
        IQ result = new IQ(Type.result, iq.getID());
        result.setFrom(iq.getTo());
        result.setTo(iq.getFrom());
        return result;
    }

因此,这里就是设置XMPP协议中type、to和from属性。
回到handleIQ函数,接下来先更改reply的to属性为null,然后设置其子元素。probeResult在IQRegisterHandler模块的initialize函数中被设置,

    public void initialize(XMPPServer server) {
        super.initialize(server);
        userManager = server.getUserManager();
        rosterManager = server.getRosterManager();

        if (probeResult == null) {
            probeResult = DocumentHelper.createElement(QName.get("query", "jabber:iq:register"));
            probeResult.addElement("username");
            probeResult.addElement("password");
            probeResult.addElement("email");
            probeResult.addElement("name");

            final DataForm registrationForm = new DataForm(DataForm.Type.form);
            registrationForm.setTitle("XMPP Client Registration");
            registrationForm.addInstruction("Please provide the following information");

            final FormField fieldForm = registrationForm.addField();
            fieldForm.setVariable("FORM_TYPE");
            fieldForm.setType(FormField.Type.hidden);
            fieldForm.addValue("jabber:iq:register");

            final FormField fieldUser = registrationForm.addField();
            fieldUser.setVariable("username");
            fieldUser.setType(FormField.Type.text_single);
            fieldUser.setLabel("Username");
            fieldUser.setRequired(true);

            final FormField fieldName = registrationForm.addField(); 
            fieldName.setVariable("name");
            fieldName.setType(FormField.Type.text_single);
            fieldName.setLabel("Full name");
            if (UserManager.getUserProvider().isNameRequired()) {
                fieldName.setRequired(true);
            }

            final FormField fieldMail = registrationForm.addField();
            fieldMail.setVariable("email");
            fieldMail.setType(FormField.Type.text_single);
            fieldMail.setLabel("Email");
            if (UserManager.getUserProvider().isEmailRequired()) {
                fieldMail.setRequired(true);
            }

            final FormField fieldPwd = registrationForm.addField();
            fieldPwd.setVariable("password");
            fieldPwd.setType(FormField.Type.text_private);
            fieldPwd.setLabel("Password");
            fieldPwd.setRequired(true);

            probeResult.add(registrationForm.getElement());
        }

        JiveGlobals.migrateProperty("register.inband");
        JiveGlobals.migrateProperty("register.password");

        registrationEnabled = JiveGlobals.getBooleanProperty("register.inband", true);
        canChangePassword = JiveGlobals.getBooleanProperty("register.password", true);
    }

这里就不详细分析了,总之就是构造一个Element,添加对应的元素。回到handleIQ函数中,最后调用LocalClientSession的process函数发送消息,这样最后客户端返回的结果如下,

<iq type="result" id="#id" from="#serverName">
    <query xmlns="jabber:iq:register">
        <username/><password/><email/><name/>
        <x xmlns="jabber:x:data" type="form">
            <title>XMPP Client Registrationtitle>
            <instructions>Please provide the following informationinstructions>
            <field var="FORM_TYPE" type="hidden">
                <value>jabber:iq:registervalue>
            field>
            <field var="username" type="text-single" label="Username">
                <required/>
            field>
            <field var="name" type="text-single" label="Full name"/>
            <field var="email" type="text-single" label="Email"/>
            <field var="password" type="text-private" label="Password">
                <required/>
            field>
        x>
    query>
iq>

因此这里就是返回给客户端服务器需要的信息,客户端需要根据这里得到的信息进行相应的填写,并提交给openfire服务器。

第二个请求

这里假设客户端根据刚刚服务器返回的信息,进行填写,然后提交给openfire服务器的消息如下所示,

<iq id="#id" to="#serverName" type="set">
    <query xmlns="jabber:iq:register">
        <username>123username>
        <email>email>
        <name>name>
        <password>456password>
    query>
iq>

参照上面第一个客户端请求的分析,这里最后会到达handleIQ,但是由于本次请求的type为set,因此这里重新贴一遍该函数,

    public IQ handleIQ(IQ packet) throws PacketException, UnauthorizedException {
        ClientSession session = sessionManager.getSession(packet.getFrom());
        IQ reply = null;

        if (IQ.Type.get.equals(packet.getType())) {

            ...

        }
        else if (IQ.Type.set.equals(packet.getType())) {
            try {
                Element iqElement = packet.getChildElement();
                if (iqElement.element("remove") != null) {

                    ...

                }
                else {
                    String username;
                    String password = null;
                    String email = null;
                    String name = null;
                    User newUser;
                    DataForm registrationForm;
                    FormField field;

                    Element formElement = iqElement.element("x");
                    if (formElement != null) {

                        ...

                    }
                    else {
                        username = iqElement.elementText("username");
                        password = iqElement.elementText("password");
                        email = iqElement.elementText("email");
                        name = iqElement.elementText("name");
                    }

                    ...

                    if (session.getStatus() == Session.STATUS_AUTHENTICATED) {

                        ...

                    }
                    else {
                        if (!registrationEnabled) {

                            ...

                        }
                        else if (password == null || password.trim().length() == 0) {

                            ...

                        }
                        else {
                            newUser = userManager.createUser(username, password, name, email);
                        }
                    }
                    if (newUser != null && name != null && !name.equals(newUser.getName())) {
                        newUser.setName(name);
                    }

                    reply = IQ.createResultIQ(packet);
                }
            }
            catch (Exception e) {

            }
        }
        if (reply != null) {
            session.process(reply);
        }
        return null;
    }

这里省略了大部分代码,剩下一些关键代码,因此可以很容易看出这里就是调用UserManager的create函数,然后构造返回信息。

    public User createUser(String username, String password, String name, String email)
            throws UserAlreadyExistsException
    {

        ...

        User user = provider.createUser(username, password, name, email);
        userCache.put(username, user);

        Map params = Collections.emptyMap();
        UserEventDispatcher.dispatchEvent(user, UserEventDispatcher.EventType.user_created, params);

        return user;
    }

provider默认为DefaultUserProvider,其createUser函数如下,

    public User createUser(String username, String password, String name, String email)
            throws UserAlreadyExistsException
    {
        try {
            loadUser(username);
        }
        catch (UserNotFoundException unfe) {

            ...

            Date now = new Date();
            Connection con = null;
            PreparedStatement pstmt = null;
            try {
                con = DbConnectionManager.getConnection();
                pstmt = con.prepareStatement(INSERT_USER);
                pstmt.setString(1, username);
                if (password == null) {
                    pstmt.setNull(2, Types.VARCHAR);
                }
                else {
                    pstmt.setString(2, password);
                }
                if (encryptedPassword == null) {
                    pstmt.setNull(3, Types.VARCHAR);
                }
                else {
                    pstmt.setString(3, encryptedPassword);
                }
                if (name == null || name.matches("\\s*")) {
                    pstmt.setNull(4, Types.VARCHAR);
                }
                else {
                    pstmt.setString(4, name);
                }
                if (email == null || email.matches("\\s*")) {
                    pstmt.setNull(5, Types.VARCHAR);
                }
                else {
                    pstmt.setString(5, email);
                }
                pstmt.setString(6, StringUtils.dateToMillis(now));
                pstmt.setString(7, StringUtils.dateToMillis(now));
                pstmt.execute();
            }
            catch (Exception e) {

            }
            finally {
                DbConnectionManager.closeConnection(pstmt, con);
            }
            return new User(username, name, email, now, now);
        }
    }

这里首先调用loadUser查询数据库是否已经有该用户名对应的用户,这里假设没有,然后下面就是将用户的数据存入数据库,接着创建一个User并返回,用来存入缓存。值得注意的是,这里都是一些openfire的默认实现,实际项目中,肯定需要根据具体业务定义一些特定的类或者函数。
返回到UserManager中,接下来就是将User实例添加进缓存,并触发监听函数,例如openfire中存在一些公共的群组,需要更新这些群组信息。
再回到handleIQ,接下来通过createResultIQ构造返回信息,最后返回的信息是,

type="result" id="#id" from="#from" to="#to"/>

到此,关于openfire注册的全部逻辑就分析到这了,下一章分析登录。

你可能感兴趣的:(openfire源码分析)