openfire源碼解讀--使用者登入,openfire源碼
根據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'/> 表示登入成功。