openfire源码分析---8

登录

本章分析用户的登录。

第一个请求

用户首先发送一个关于sasl验证的请求,

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

根据前面几章的分析,用户的这条XMPP消息到达服务器后会经过层层处理,最后到达ClientStanzaHandler的process函数,

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

        if (!sessionCreated || initialStream) {
            ...
        }


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

        String tag = doc.getName();
        if ("starttls".equals(tag)) {
            ...
        }
        else if ("auth".equals(tag)) {
            startedSASL = true;
            saslStatus = SASLAuthentication.handle(session, doc);
        } else if (startedSASL && "response".equals(tag) || "abort".equals(tag)) {
            ...
        }
        else if ("compress".equals(tag)) {
            ...
        }
        else {
            process(doc);
        }
    }

这里标签名为tag,因此调用SASLAuthentication的handle函数进一步处理,

    public static Status handle(LocalSession session, Element doc) throws UnsupportedEncodingException {
        Status status;
        String mechanism;
        if (doc.getNamespace().asXML().equals(SASL_NAMESPACE)) {
            ElementType type = ElementType.valueof(doc.getName());
            switch (type) {
                case ABORT:
                    ...
                case AUTH:
                    mechanism = doc.attributeValue("mechanism");
                    session.setSessionData("SaslMechanism", mechanism);
                    if (mechanism.equalsIgnoreCase("ANONYMOUS") &&
                            mechanisms.contains("ANONYMOUS")) {
                        ...
                    }
                    else if (mechanism.equalsIgnoreCase("EXTERNAL")) {
                        ...
                    }
                    else if (mechanisms.contains(mechanism)) {
                        try {
                            Map<String, String> props = new TreeMap<String, String>();
                            props.put(Sasl.QOP, "auth");

                            SaslServer ss = Sasl.createSaslServer(mechanism, "xmpp",
                                    JiveGlobals.getProperty("xmpp.fqdn", session.getServerName()), props, new XMPPCallbackHandler());

                            byte[] token = new byte[0];
                            String value = doc.getTextTrim();
                            if (value.length() > 0) {
                                ...
                            }
                            if (mechanism.equals("DIGEST-MD5")) {
                                token = new byte[0];
                            }
                            byte[] challenge = ss.evaluateResponse(token);
                            if (ss.isComplete()) {
                                ...
                            }
                            else {
                                sendChallenge(session, challenge);
                                status = Status.needResponse;
                            }
                            session.setSessionData("SaslServer", ss);
                        }
                        catch (SaslException e) {

                        }
                    }
                    else {

                    }
                    break;
                case RESPONSE:
                    ...
                default:
                    ...
            }
        }
        else {

        }
        return status;
    }

首先调用createSaslServer为客户端请求的 SASL 机制获得一个 SaslServer 实例,其中注册了用来验证用户名和密码的事件处理器XMPPCallbackHandler,最后面分析。然后就调用evaluateResponse获得challenge,然后通过sendChallenge把这个challenge发往客户端,并将刚刚构造的SaslServer设置进Session中。
sendChallenge定义在SASLAuthentication中,

    private static void sendChallenge(Session session, byte[] challenge) {
        StringBuilder reply = new StringBuilder(250);

        String challenge_b64 = StringUtils.encodeBase64(challenge).trim();
        if ("".equals(challenge_b64)) {
            challenge_b64 = "=";
        }
        reply.append(
                "<challenge xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
        reply.append(challenge_b64);
        reply.append("</challenge>");
        session.deliverRawText(reply.toString());
    }

根据该函数,服务器返回给客户端的XMPP消息如下,

<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
    #challenge
</response>

这里的#challenge代表一串通过编码后的字符串。

第二个请求

客户端收到刚刚服务器发来的challenge后,会根据用户名和密码组织消息,然后向服务器发送第二个消息,

<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
    #challenge
</response>

该消息中的#challenge包含了用户名和密码等信息,到达服务器后,会到达ClientStanzaHandler的process函数,

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

        if (!sessionCreated || initialStream) {
            ...
        }


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

        String tag = doc.getName();
        if ("starttls".equals(tag)) {
            ...
        }
        else if ("auth".equals(tag)) {
            ...
        } else if (startedSASL && "response".equals(tag) || "abort".equals(tag)) {
            saslStatus = SASLAuthentication.handle(session, doc);
        }
        else if ("compress".equals(tag)) {
            ...
        }
        else {
            process(doc);
        }
    }

和第一个请求一样,这里也是调用SASLAuthentication的handle函数进一步处理,

    public static Status handle(LocalSession session, Element doc) throws UnsupportedEncodingException {
        Status status;
        String mechanism;
        if (doc.getNamespace().asXML().equals(SASL_NAMESPACE)) {
            ElementType type = ElementType.valueof(doc.getName());
            switch (type) {
                case ABORT:
                    ...
                case AUTH:
                    ...
                case RESPONSE:
                    mechanism = (String) session.getSessionData("SaslMechanism");
                    if (mechanism.equalsIgnoreCase("EXTERNAL")) {
                        ...
                    }
                    else if (mechanism.equalsIgnoreCase("JIVE-SHAREDSECRET")) {
                        ...
                    }
                    else if (mechanisms.contains(mechanism)) {
                        SaslServer ss = (SaslServer) session.getSessionData("SaslServer");
                        if (ss != null) {
                            boolean ssComplete = ss.isComplete();
                            String response = doc.getTextTrim();
                            if (!BASE64_ENCODED.matcher(response).matches()) {
                                ...
                            }
                            try {
                                if (ssComplete) {
                                    ...
                                }
                                else {
                                    byte[] data = StringUtils.decodeBase64(response);
                                    byte[] challenge = ss.evaluateResponse(data);
                                    if (ss.isComplete()) {
                                        authenticationSuccessful(session, ss.getAuthorizationID(),
                                                challenge);
                                        status = Status.authenticated;
                                    }
                                    else {
                                        ...
                                    }
                                }
                            }
                            catch (SaslException e) {

                            }
                        }
                        else {
                            ...
                        }
                    }
                    else {
                        ...
                    }
                    break;
                default:
                    ...
            }
        }
        else {

        }
        if (status == Status.failed || status == Status.authenticated) {
            session.removeSessionData("SaslServer");
            session.removeSessionData("SaslMechanism");
        }
        return status;
    }

这里首先获得前面处理第一个请求时构造的SaslServer,然后调用evaluateResponse处理客户端的信息,这里假设验证成功,则会调用authenticationSuccessful,定义在SASLAuthentication中,

    private static void authenticationSuccessful(LocalSession session, String username,
            byte[] successData) {
        if (username != null && LockOutManager.getInstance().isAccountDisabled(username)) {
            ...
        }
        StringBuilder reply = new StringBuilder(80);
        reply.append("<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"");
        if (successData != null) {
            String successData_b64 = StringUtils.encodeBase64(successData).trim();
            reply.append(">").append(successData_b64).append("</success>");
        }
        else {
            reply.append("/>");
        }
        session.deliverRawText(reply.toString());
        if (session instanceof ClientSession) {
            ((LocalClientSession) session).setAuthToken(new AuthToken(username));
        }
        else if (session instanceof IncomingServerSession) {
            String hostname = username;
            ((LocalIncomingServerSession) session).addValidatedDomain(hostname);
        }
    }

这里就是构造返回信息并发给客户端,然后构造AuthToken并设置进LocalClientSession中。根据代码,客户端返回的信息如下,

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

XMPPCallbackHandler

Sasl框架会回调XMPPCallbackHandler中的handle函数,

    public void handle(final Callback[] callbacks)
            throws IOException, UnsupportedCallbackException {


        String realm;
        String name = null;

        for (Callback callback : callbacks) {
            if (callback instanceof RealmCallback) {
                realm = ((RealmCallback) callback).getText();
                if (realm == null) {
                    realm = ((RealmCallback) callback).getDefaultText();
                }
            }
            else if (callback instanceof NameCallback) {
                name = ((NameCallback) callback).getName();
                if (name == null) {
                    name = ((NameCallback) callback).getDefaultName();
                }
            }
            else if (callback instanceof PasswordCallback) {
                try {
                    ((PasswordCallback) callback)
                            .setPassword(AuthFactory.getPassword(name).toCharArray());
                }
                ...
            }
            else if (callback instanceof VerifyPasswordCallback) {
                VerifyPasswordCallback vpcb = (VerifyPasswordCallback) callback;
                try {
                    AuthToken at = AuthFactory.authenticate(name, new String(vpcb.getPassword()));
                    vpcb.setVerified((at != null));
                }
                catch (Exception e) {
                    vpcb.setVerified(false);
                }
            }
            else if (callback instanceof AuthorizeCallback) {
                ...
            }
            else {
                ...
            }
        }
    }

获得了用户名和密码之后,会调用AuthFactory.authenticate进行验证,输入为用户名和密码,

    public static AuthToken authenticate(String username, String password)
            throws UnauthorizedException, ConnectionException, InternalUnauthenticatedException {
        authProvider.authenticate(username, password);
        return new AuthToken(username);
    }

authProvider默认为DefaultAuthProvider,

    public void authenticate(String username, String password) throws UnauthorizedException {
        username = username.trim().toLowerCase();
        if (username.contains("@")) {
            int index = username.indexOf("@");
            String domain = username.substring(index + 1);
            if (domain.equals(XMPPServer.getInstance().getServerInfo().getXMPPDomain())) {
                username = username.substring(0, index);
            } else {
                ...
            }
        }
        try {
            if (!password.equals(getPassword(username))) {
                throw new UnauthorizedException();
            }
        }
        catch (UserNotFoundException unfe) {
            ...
        }
    }

该函数就是对用户名做了一些处理,主要是调用getPassword获取密码并比对,getPassword就是简单地从数据库中读取密码。这里假设验证成功,返回到上层的authenticate中,构造AuthToken并返回。

用户验证成功后,需要绑定资源,下一章继续分析。

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