openfire 원본 분석 ---8

20446 단어

로그인


이 장에서는 사용자의 로그인을 분석합니다.

첫 번째 요청


사용자가 먼저sasl 검증에 대한 요청을 보내면,
<auth mechanism="DIGEST-MD5" xmlns="urn:ietf:params:xml:ns:xmpp-sasl"></auth>

앞의 몇 장의 분석에 따르면 사용자의 이 XMPP 메시지는 서버에 도착한 후 층층이 처리되어 클라이언트 StanzaHandler의 프로세스 함수에 도달한다.
    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);
        }
    }

여기에 태그로 서명되어 있기 때문에 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를 호출하여 챌린지를 얻은 다음sendChallenge를 통해 이 챌린지를 클라이언트에게 보내고 방금 구성된 Sasl Server를 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의 프로세스 함수에 도달합니다.
    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;
    }

여기에서 먼저 첫 번째 요청을 처리할 때 구성된 Sasl Server를 얻은 다음에 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을 만들어서 Local ClientSession에 설정하는 것입니다.코드에 따라 클라이언트가 되돌아오는 정보는 다음과 같다.
<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을 구성하고 되돌려줍니다.
사용자 검증이 성공하면 귀속 자원이 필요하고 다음 장은 계속 분석해야 합니다.

좋은 웹페이지 즐겨찾기