Skip to content

Commit b83a79a

Browse files
author
Simon MacMullen
committed
Implement Java client SASL using javax.security.sasl, since it's actually fairly non-crazy.
1 parent 174af39 commit b83a79a

14 files changed

+237
-210
lines changed

src/com/rabbitmq/client/AuthMechanism.java

Lines changed: 0 additions & 19 deletions
This file was deleted.

src/com/rabbitmq/client/AuthMechanismFactory.java

Lines changed: 0 additions & 16 deletions
This file was deleted.

src/com/rabbitmq/client/ConnectionFactory.java

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import java.io.IOException;
3434
import java.security.KeyManagementException;
3535
import java.security.NoSuchAlgorithmException;
36-
import java.util.List;
3736
import java.util.Map;
3837

3938
import java.net.Socket;
@@ -46,7 +45,6 @@
4645

4746
import com.rabbitmq.client.impl.AMQConnection;
4847
import com.rabbitmq.client.impl.FrameHandler;
49-
import com.rabbitmq.client.impl.PlainMechanismFactory;
5048
import com.rabbitmq.client.impl.SocketFrameHandler;
5149

5250
/**
@@ -86,10 +84,6 @@ public class ConnectionFactory implements Cloneable {
8684
/** The default port to use for AMQP connections when using SSL */
8785
public static final int DEFAULT_AMQP_OVER_SSL_PORT = 5671;
8886

89-
/** The default list of authentication mechanisms to use */
90-
public static final AuthMechanismFactory[] DEFAULT_AUTH_MECHANISMS =
91-
new AuthMechanismFactory[] { new PlainMechanismFactory() };
92-
9387
/**
9488
* The default SSL protocol (currently "SSLv3").
9589
*/
@@ -105,7 +99,7 @@ public class ConnectionFactory implements Cloneable {
10599
private int requestedHeartbeat = DEFAULT_HEARTBEAT;
106100
private Map<String, Object> _clientProperties = AMQConnection.defaultClientProperties();
107101
private SocketFactory factory = SocketFactory.getDefault();
108-
private AuthMechanismFactory[] authMechanismFactories = DEFAULT_AUTH_MECHANISMS;
102+
private SaslConfig saslConfig = new DefaultSaslConfig(this);
109103

110104
/**
111105
* Instantiate a ConnectionFactory with a default set of parameters.
@@ -268,29 +262,19 @@ public void setClientProperties(Map<String, Object> clientProperties) {
268262
}
269263

270264
/**
271-
* Given a list of mechanism names supported by the server, select a
272-
* preferred mechanism, or null if we have none in common.
273-
* @param serverMechanisms
265+
* Gets the sasl config to use when authenticating
274266
* @return
275267
*/
276-
public AuthMechanismFactory getAuthMechanismFactory(List<String> serverMechanisms) {
277-
// Our list is in order of preference, the server one is not.
278-
for (AuthMechanismFactory f : authMechanismFactories) {
279-
if (serverMechanisms.contains(f.getName())) {
280-
return f;
281-
}
282-
}
283-
284-
return null;
268+
public SaslConfig getSaslConfig() {
269+
return saslConfig;
285270
}
286271

287272
/**
288-
* Set authentication mechanisms to use (in descending preference order)
289-
* @param factories
290-
* @see #DEFAULT_AUTH_MECHANISMS
273+
* Sets the sasl config to use when authenticating
274+
* @param saslConfig
291275
*/
292-
public void setAuthMechanismFactories(AuthMechanismFactory[] factories) {
293-
this.authMechanismFactories = factories;
276+
public void setSaslConfig(SaslConfig saslConfig) {
277+
this.saslConfig = saslConfig;
294278
}
295279

296280
/**
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.rabbitmq.client;
2+
3+
import javax.security.auth.callback.CallbackHandler;
4+
import javax.security.sasl.Sasl;
5+
import javax.security.sasl.SaslClient;
6+
import javax.security.sasl.SaslException;
7+
import java.util.Map;
8+
9+
/**
10+
*
11+
*/
12+
public class DefaultSaslConfig implements SaslConfig {
13+
private ConnectionFactory factory;
14+
private String authorizationId;
15+
private Map<String,?> mechanismProperties;
16+
private CallbackHandler callbackHandler;
17+
18+
public DefaultSaslConfig(ConnectionFactory factory) {
19+
this.factory = factory;
20+
callbackHandler = new UsernamePasswordCallbackHandler(factory);
21+
}
22+
23+
public void setAuthorizationId(String authorizationId) {
24+
this.authorizationId = authorizationId;
25+
}
26+
27+
public void setMechanismProperties(Map<String, ?> mechanismProperties) {
28+
this.mechanismProperties = mechanismProperties;
29+
}
30+
31+
public void setCallbackHandler(CallbackHandler callbackHandler) {
32+
this.callbackHandler = callbackHandler;
33+
}
34+
35+
public SaslClient getSaslClient(String[] mechanisms) throws SaslException {
36+
return Sasl.createSaslClient(mechanisms, authorizationId, "AMQP",
37+
factory.getHost(), mechanismProperties, callbackHandler);
38+
}
39+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.rabbitmq.client;
2+
3+
import javax.security.sasl.SaslClient;
4+
import javax.security.sasl.SaslException;
5+
6+
/**
7+
*
8+
*/
9+
public interface SaslConfig {
10+
SaslClient getSaslClient(String[] mechanisms) throws SaslException;
11+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.rabbitmq.client;
2+
3+
import javax.security.auth.callback.Callback;
4+
import javax.security.auth.callback.CallbackHandler;
5+
import javax.security.auth.callback.NameCallback;
6+
import javax.security.auth.callback.PasswordCallback;
7+
import javax.security.auth.callback.UnsupportedCallbackException;
8+
import java.io.IOException;
9+
10+
public class UsernamePasswordCallbackHandler implements CallbackHandler {
11+
private ConnectionFactory factory;
12+
public UsernamePasswordCallbackHandler(ConnectionFactory factory) {
13+
this.factory = factory;
14+
}
15+
16+
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
17+
for (Callback callback: callbacks) {
18+
if (callback instanceof NameCallback) {
19+
NameCallback nc = (NameCallback)callback;
20+
nc.setName(factory.getUsername());
21+
22+
} else if (callback instanceof PasswordCallback) {
23+
PasswordCallback pc = (PasswordCallback)callback;
24+
pc.setPassword(factory.getPassword().toCharArray());
25+
26+
} else {
27+
throw new UnsupportedCallbackException
28+
(callback, "Unrecognized Callback");
29+
}
30+
}
31+
}
32+
}

src/com/rabbitmq/client/impl/AMQConnection.java

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,12 @@
3434
import java.io.EOFException;
3535
import java.io.IOException;
3636
import java.net.SocketException;
37-
import java.util.Arrays;
38-
import java.util.List;
3937
import java.util.Map;
4038
import java.util.HashMap;
4139
import java.util.concurrent.TimeoutException;
4240

4341
import com.rabbitmq.client.AMQP;
4442
import com.rabbitmq.client.AlreadyClosedException;
45-
import com.rabbitmq.client.AuthMechanism;
46-
import com.rabbitmq.client.AuthMechanismFactory;
4743
import com.rabbitmq.client.Channel;
4844
import com.rabbitmq.client.Command;
4945
import com.rabbitmq.client.Connection;
@@ -53,6 +49,8 @@
5349
import com.rabbitmq.utility.BlockingCell;
5450
import com.rabbitmq.utility.Utility;
5551

52+
import javax.security.sasl.SaslClient;
53+
5654
/**
5755
* Concrete class representing and managing an AMQP connection to a broker.
5856
* <p>
@@ -273,22 +271,21 @@ public void start()
273271
throw AMQChannel.wrap(sse);
274272
}
275273

276-
List<String> mechanisms = Arrays.asList(
277-
connStart.getMechanisms().toString().split(" "));
278-
AuthMechanismFactory mechanismFactory = _factory.getAuthMechanismFactory(mechanisms);
279-
if (mechanismFactory == null) {
274+
String[] mechanisms = connStart.getMechanisms().toString().split(" ");
275+
SaslClient sc = _factory.getSaslConfig().getSaslClient(mechanisms);
276+
if (sc == null) {
280277
throw new IOException("No compatible authentication mechanism found - " +
281278
"server offered [" + connStart.getMechanisms() + "]");
282279
}
283-
AuthMechanism mechanism = mechanismFactory.getInstance();
284280

285281
LongString challenge = null;
282+
LongString response = LongStringHelper.asLongString(
283+
sc.hasInitialResponse() ? sc.evaluateChallenge(new byte[0]) : null);
286284
AMQP.Connection.Tune connTune = null;
287285
do {
288-
LongString response = mechanism.handleChallenge(challenge, _factory);
289286
Method method = (challenge == null)
290287
? new AMQImpl.Connection.StartOk(_clientProperties,
291-
mechanismFactory.getName(),
288+
sc.getMechanismName(),
292289
response, "en_US")
293290
: new AMQImpl.Connection.SecureOk(response);
294291

@@ -298,12 +295,20 @@ public void start()
298295
connTune = (AMQP.Connection.Tune) serverResponse;
299296
} else {
300297
challenge = ((AMQP.Connection.Secure) serverResponse).getChallenge();
298+
response = LongStringHelper.asLongString(sc.evaluateChallenge(challenge.getBytes()));
301299
}
302300
} catch (ShutdownSignalException e) {
303301
throw AMQChannel.wrap(e, "Possibly caused by authentication failure");
304302
}
305303
} while (connTune == null);
306304

305+
sc.dispose();
306+
307+
if (!sc.isComplete()) {
308+
throw new RuntimeException(sc.getMechanismName() +
309+
" did not complete, server thought it did");
310+
}
311+
307312
int channelMax =
308313
negotiatedMaxValue(_factory.getRequestedChannelMax(),
309314
connTune.getChannelMax());

src/com/rabbitmq/client/impl/ExternalMechanism.java

Lines changed: 0 additions & 13 deletions
This file was deleted.

src/com/rabbitmq/client/impl/ExternalMechanismFactory.java

Lines changed: 0 additions & 17 deletions
This file was deleted.

src/com/rabbitmq/client/impl/PlainMechanism.java

Lines changed: 0 additions & 15 deletions
This file was deleted.

0 commit comments

Comments
 (0)