1. 程式人生 > >openfire源碼解讀--用戶登錄

openfire源碼解讀--用戶登錄

users lns log requested ipa ram ins 需要 either

根據xmpp協議

客戶端發送:

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

其中,xmlns是命名空間,mechanism是用戶名密碼的加密方式,auth 標簽的text內容為用戶名密碼通過PLAIN方式加密的字符串。

服務端接收:

  通過ConnectionHandler類的messageReceived方法接收,process中處理

     else if ("auth".equals(tag)) {
            // User is trying to authenticate using SASL
            startedSASL = true;
            // Process authentication stanza
            saslStatus = SASLAuthentication.handle(session, doc);
        }

  判斷xml標簽為auth時進行登錄驗證。

  下面來看SASLAuthentication的處理

  首先判斷加密方式,然後解密,通過下面這個方法來驗證登錄。

final byte[] challenge = saslServer.evaluateResponse( decoded ); // Either a challenge or success data.

  根據加密方式不同,驗證處理方法不同。PLAIN加密的,那就看SaslServerPlainImpl中是怎麽實現的。

	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});
		if(acb.isAuthorized()) {
			username = acb.getAuthorizedID();
			completed = true;
		} else {
			completed = true;
			username = null;
			throw new SaslException("PLAIN: user not authorized: "+principal);
		}
	} else {
		throw new SaslException("PLAIN: user not authorized: "+principal);
	}

  可以看到openfire是通過callback來驗證的,而且還進行了2層驗證。第一次是驗證用戶名密碼,第二次是加載用戶信息

(自己有需要修改源碼時,這裏就可以優化了,第一步登錄驗證時就可以獲取用戶信息了,沒必要重新查詢一次)。

  callback是通過XMPPCallbackHandler實現的。

for (Callback callback : callbacks) {

            if (callback instanceof RealmCallback) {

                ((RealmCallback) callback).setText( XMPPServer.getInstance().getServerInfo().getXMPPDomain() );

            }

            else if (callback instanceof NameCallback) {

                name = ((NameCallback) callback).getName();

                if (name == null) {

                    name = ((NameCallback) callback).getDefaultName();

                }

                //Log.debug("XMPPCallbackHandler: NameCallback: " + name);

            }

            else if (callback instanceof PasswordCallback) {

                try {

                    // Get the password from the UserProvider. Some UserProviders may not support

                    // this operation

                    ((PasswordCallback) callback)

                            .setPassword(AuthFactory.getPassword(name).toCharArray());



                    //Log.debug("XMPPCallbackHandler: PasswordCallback");

                }

                catch (UserNotFoundException | UnsupportedOperationException e) {

                    throw new IOException(e.toString());

                }



            }

            else if (callback instanceof VerifyPasswordCallback) {

                //Log.debug("XMPPCallbackHandler: 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) {

                //Log.debug("XMPPCallbackHandler: AuthorizeCallback");

                AuthorizeCallback authCallback = ((AuthorizeCallback) callback);

                // Principal that authenticated

                String principal = authCallback.getAuthenticationID();

                // Username requested (not full JID)

                String username = authCallback.getAuthorizationID();

                // Remove any REALM from the username. This is optional in the spec and it may cause

                // a lot of users to fail to log in if their clients is sending an incorrect value

                if (username != null && username.contains("@")) {

                    username = username.substring(0, username.lastIndexOf("@"));

                }

                if (principal.equals(username)) {

                    //client perhaps made no request, get default username

                    username = AuthorizationManager.map(principal);

                    if (Log.isDebugEnabled()) {

                        //Log.debug("XMPPCallbackHandler: no username requested, using " + username);

                    }

                }

                if (AuthorizationManager.authorize(username, principal)) {

                    if (Log.isDebugEnabled()) {

                        //Log.debug("XMPPCallbackHandler: " + principal + " authorized to " + username);

                    }

                    authCallback.setAuthorized(true);

                    authCallback.setAuthorizedID(username);

                }

                else {

                    if (Log.isDebugEnabled()) {

                        //Log.debug("XMPPCallbackHandler: " + principal + " not authorized to " + username);

                    }

                    authCallback.setAuthorized(false);

                }

            }

  第一次驗證用戶名密碼是通過 AuthToken at = AuthFactory.authenticate(name, new String(vpcb.getPassword()));驗證的

根據數據庫配置provider.auth.className的類實現登錄驗證。

第二次驗證AuthorizationManager.authorize(username, principal)會加載用戶信息。
2次驗證通過就會返回客戶端 <success xmlns=‘urn:ietf:params:xml:ns:xmpp-sasl‘/> 表示登錄成功。

openfire源碼解讀--用戶登錄