Java tutorial
/** * $RCSfile$ * $Revision: $ * $Date: $ * * Copyright 2003-2007 Jive Software. * * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.rayo.client.auth.sasl; import java.io.IOException; import java.util.HashMap; import java.util.Map; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.sasl.RealmCallback; import javax.security.sasl.RealmChoiceCallback; import javax.security.sasl.Sasl; import javax.security.sasl.SaslClient; import javax.security.sasl.SaslException; import org.apache.commons.codec.binary.Base64; import com.rayo.client.XmppConnection; import com.rayo.client.XmppException; import com.rayo.client.xmpp.stanza.XmppObject; import com.rayo.client.xmpp.stanza.sasl.AuthMechanism; import com.rayo.client.xmpp.stanza.sasl.Response; /** * Base class for SASL mechanisms. Subclasses must implement these methods: * <ul> * <li>{@link #getName()} -- returns the common name of the SASL mechanism.</li> * </ul> * Subclasses will likely want to implement their own versions of these mthods: * <li>{@link #authenticate(String, String, String)} -- Initiate authentication stanza using the * deprecated method.</li> * <li>{@link #authenticate(String, String, CallbackHandler)} -- Initiate authentication stanza * using the CallbackHandler method.</li> * <li>{@link #challengeReceived(String)} -- Handle a challenge from the server.</li> * </ul> * * @author Jay Kline */ public abstract class SASLMechanism implements CallbackHandler { private XmppConnection connection; protected SaslClient sc; protected String authenticationId; protected String password; protected String hostname; public SASLMechanism(XmppConnection connection) { this.connection = connection; } /** * Builds and sends the <tt>auth</tt> stanza to the server. Note that this method of * authentication is not recommended, since it is very inflexable. Use * {@link #authenticate(String, String, CallbackHandler)} whenever possible. * * @param username the username of the user being authenticated. * @param host the hostname where the user account resides. * @param password the password for this account. * @throws IOException If a network error occurs while authenticating. * @throws XMPPException If a protocol error occurs or the user is not authenticated. */ public void authenticate(String username, String host, String password) throws IOException, XmppException { //Since we were not provided with a CallbackHandler, we will use our own with the given //information //Set the authenticationID as the username, since they must be the same in this case. this.authenticationId = username; this.password = password; this.hostname = host; String[] mechanisms = { getName().toString() }; Map<String, String> props = new HashMap<String, String>(); sc = Sasl.createSaslClient(mechanisms, username, "xmpp", host, props, this); authenticate(); } /** * Builds and sends the <tt>auth</tt> stanza to the server. The callback handler will handle * any additional information, such as the authentication ID or realm, if it is needed. * * @param username the username of the user being authenticated. * @param host the hostname where the user account resides. * @param cbh the CallbackHandler to obtain user information. * @throws IOException If a network error occures while authenticating. * @throws XMPPException If a protocol error occurs or the user is not authenticated. */ public void authenticate(String username, String host, CallbackHandler cbh) throws IOException, XmppException { String[] mechanisms = { getName().toString() }; Map<String, String> props = new HashMap<String, String>(); sc = Sasl.createSaslClient(mechanisms, username, "xmpp", host, props, cbh); authenticate(); } protected void authenticate() throws IOException, XmppException { String authenticationText = null; try { if (sc.hasInitialResponse()) { byte[] response = sc.evaluateChallenge(new byte[0]); //authenticationText = Base64.encodeBytes(response,Base64.DONT_BREAK_LINES); authenticationText = Base64.encodeBase64String(response); } } catch (SaslException e) { throw new XmppException("SASL authentication failed", e); } // Send the authentication to the server connection.send(new AuthMechanism(getName(), authenticationText)); } /** * The server is challenging the SASL mechanism for the stanza he just sent. Send a * response to the server's challenge. * * @param challenge a base64 encoded string representing the challenge. * @throws IOException if an exception sending the response occurs. */ public void challengeReceived(String challenge) throws IOException, XmppException { byte response[]; if (challenge != null) { response = sc.evaluateChallenge(org.apache.commons.codec.binary.Base64.decodeBase64(challenge)); } else { response = sc.evaluateChallenge(new byte[0]); } XmppObject responseStanza; if (response == null) { responseStanza = new Response(); } else { String text = org.apache.commons.codec.binary.Base64.encodeBase64String(response); responseStanza = new Response(text); } // Send the authentication to the server connection.send(responseStanza); } /** * Returns the common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or GSSAPI. * * @return the common name of the SASL mechanism. */ protected abstract AuthMechanism.Type getName(); /** * */ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (int i = 0; i < callbacks.length; i++) { if (callbacks[i] instanceof NameCallback) { NameCallback ncb = (NameCallback) callbacks[i]; ncb.setName(authenticationId); } else if (callbacks[i] instanceof PasswordCallback) { PasswordCallback pcb = (PasswordCallback) callbacks[i]; pcb.setPassword(password.toCharArray()); } else if (callbacks[i] instanceof RealmCallback) { RealmCallback rcb = (RealmCallback) callbacks[i]; rcb.setText(hostname); } else if (callbacks[i] instanceof RealmChoiceCallback) { //unused //RealmChoiceCallback rccb = (RealmChoiceCallback)callbacks[i]; } else { throw new UnsupportedCallbackException(callbacks[i]); } } } }