Skip to content

Commit 30c499c

Browse files
author
Simon MacMullen
committed
Make auth mechanisms pluggable in the Java client.
1 parent 1b59876 commit 30c499c

File tree

4 files changed

+105
-18
lines changed

4 files changed

+105
-18
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.rabbitmq.client;
2+
3+
import com.rabbitmq.client.impl.AMQChannel;
4+
5+
import java.io.IOException;
6+
7+
/**
8+
* A pluggable authentication mechanism
9+
*/
10+
public interface AuthMechanism {
11+
/**
12+
* Send and receive start-ok / secure / secure-ok until a connection is
13+
* established or an exception thrown.
14+
*
15+
* @param channel to send methods on
16+
* @param factory for reference to e.g. username and password.
17+
* @return the Connection.Tune method sent by the server after authentication
18+
* @throws IOException if the authentication failed or something else went wrong
19+
*/
20+
AMQP.Connection.Tune doLogin(AMQChannel channel, ConnectionFactory factory) throws IOException;
21+
22+
/**
23+
* The name of the authentication mechanism, as negotiated on the wire
24+
*/
25+
String getName();
26+
}

src/com/rabbitmq/client/ConnectionFactory.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
import java.io.IOException;
3434
import java.security.KeyManagementException;
3535
import java.security.NoSuchAlgorithmException;
36-
import java.util.HashMap;
36+
import java.util.List;
3737
import java.util.Map;
3838

3939
import java.net.Socket;
@@ -46,6 +46,7 @@
4646

4747
import com.rabbitmq.client.impl.AMQConnection;
4848
import com.rabbitmq.client.impl.FrameHandler;
49+
import com.rabbitmq.client.impl.PlainMechanism;
4950
import com.rabbitmq.client.impl.SocketFrameHandler;
5051

5152
/**
@@ -85,6 +86,10 @@ public class ConnectionFactory implements Cloneable {
8586
/** The default port to use for AMQP connections when using SSL */
8687
public static final int DEFAULT_AMQP_OVER_SSL_PORT = 5671;
8788

89+
/** The default list of authentication mechanisms to use */
90+
public static final AuthMechanism[] DEFAULT_AUTH_MECHANISMS =
91+
new AuthMechanism[] { new PlainMechanism() };
92+
8893
/**
8994
* The default SSL protocol (currently "SSLv3").
9095
*/
@@ -98,6 +103,7 @@ public class ConnectionFactory implements Cloneable {
98103
private int requestedChannelMax = DEFAULT_CHANNEL_MAX;
99104
private int requestedFrameMax = DEFAULT_FRAME_MAX;
100105
private int requestedHeartbeat = DEFAULT_HEARTBEAT;
106+
private AuthMechanism[] authMechanisms = DEFAULT_AUTH_MECHANISMS;
101107
private Map<String, Object> _clientProperties = AMQConnection.defaultClientProperties();
102108
private SocketFactory factory = SocketFactory.getDefault();
103109

@@ -261,6 +267,32 @@ public void setClientProperties(Map<String, Object> clientProperties) {
261267
_clientProperties = clientProperties;
262268
}
263269

270+
/**
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
274+
* @return
275+
*/
276+
public AuthMechanism getAuthMechanism(List<String> serverMechanisms) {
277+
// Our list is in order of preference, the server one is not.
278+
for (AuthMechanism mechanism : authMechanisms) {
279+
if (serverMechanisms.contains(mechanism.getName())) {
280+
return mechanism;
281+
}
282+
}
283+
284+
return null;
285+
}
286+
287+
/**
288+
* Set authentication mechanisms to use (in descending preference order)
289+
* @param mechanisms
290+
* @see #DEFAULT_AUTH_MECHANISMS
291+
*/
292+
public void setAuthMechanisms(AuthMechanism[] mechanisms) {
293+
this.authMechanisms = mechanisms;
294+
}
295+
264296
/**
265297
* Retrieve the socket factory used to make connections with.
266298
*/

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

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
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;
3739
import java.util.Map;
3840
import java.util.HashMap;
3941
import java.util.concurrent.TimeoutException;
@@ -44,6 +46,7 @@
4446
import com.rabbitmq.client.AMQP;
4547
import com.rabbitmq.client.Address;
4648
import com.rabbitmq.client.AlreadyClosedException;
49+
import com.rabbitmq.client.AuthMechanism;
4750
import com.rabbitmq.client.Channel;
4851
import com.rabbitmq.client.Command;
4952
import com.rabbitmq.client.Connection;
@@ -154,7 +157,7 @@ public void ensureIsOpen()
154157
*/
155158
private int _heartbeat;
156159

157-
private final String _username, _password, _virtualHost;
160+
private final String _virtualHost;
158161
private final int _requestedChannelMax, _requestedFrameMax, _requestedHeartbeat;
159162
private final Map<String, Object> _clientProperties;
160163

@@ -202,8 +205,6 @@ public AMQConnection(ConnectionFactory factory,
202205
{
203206
checkPreconditions();
204207

205-
_username = factory.getUsername();
206-
_password = factory.getPassword();
207208
_virtualHost = factory.getVirtualHost();
208209
_requestedChannelMax = factory.getRequestedChannelMax();
209210
_requestedFrameMax = factory.getRequestedFrameMax();
@@ -254,8 +255,9 @@ public void start()
254255
ml.setName("AMQP Connection " + getHost() + ":" + getPort());
255256
ml.start();
256257

258+
AMQP.Connection.Start connStart = null;
257259
try {
258-
AMQP.Connection.Start connStart =
260+
connStart =
259261
(AMQP.Connection.Start) connStartBlocker.getReply().getMethod();
260262

261263
_serverProperties = connStart.getServerProperties();
@@ -273,20 +275,15 @@ public void start()
273275
} catch (ShutdownSignalException sse) {
274276
throw AMQChannel.wrap(sse);
275277
}
276-
277-
LongString saslResponse = LongStringHelper.asLongString("\0" + _username +
278-
"\0" + _password);
279-
AMQImpl.Connection.StartOk startOk =
280-
new AMQImpl.Connection.StartOk(_clientProperties, "PLAIN",
281-
saslResponse, "en_US");
282-
283-
AMQP.Connection.Tune connTune = null;
284278

285-
try {
286-
connTune = (AMQP.Connection.Tune) _channel0.rpc(startOk).getMethod();
287-
} catch (ShutdownSignalException e) {
288-
throw AMQChannel.wrap(e, "Possibly caused by authentication failure");
279+
List<String> mechanisms = Arrays.asList(
280+
connStart.getMechanisms().toString().split(" "));
281+
AuthMechanism mechanism = _factory.getAuthMechanism(mechanisms);
282+
if (mechanism == null) {
283+
throw new IOException("No compatible authentication mechanism found - " +
284+
"server offered [" + connStart.getMechanisms() + "]");
289285
}
286+
AMQP.Connection.Tune connTune = mechanism.doLogin(_channel0, _factory);
290287

291288
int channelMax =
292289
negotiatedMaxValue(_factory.getRequestedChannelMax(),
@@ -714,6 +711,6 @@ public void close(int closeCode,
714711
}
715712

716713
@Override public String toString() {
717-
return "amqp://" + _username + "@" + getHost() + ":" + getPort() + _virtualHost;
714+
return "amqp://" + _factory.getUsername() + "@" + getHost() + ":" + getPort() + _virtualHost;
718715
}
719716
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.rabbitmq.client.impl;
2+
3+
import com.rabbitmq.client.AMQP;
4+
import com.rabbitmq.client.AuthMechanism;
5+
import com.rabbitmq.client.ConnectionFactory;
6+
import com.rabbitmq.client.ShutdownSignalException;
7+
8+
import java.io.IOException;
9+
10+
/**
11+
* The PLAIN auth mechanism
12+
*/
13+
public class PlainMechanism implements AuthMechanism {
14+
public AMQP.Connection.Tune doLogin(AMQChannel channel,
15+
ConnectionFactory factory) throws IOException {
16+
LongString saslResponse = LongStringHelper.asLongString(
17+
"\0" + factory.getUsername() + "\0" + factory.getPassword());
18+
AMQImpl.Connection.StartOk startOk =
19+
new AMQImpl.Connection.StartOk(factory.getClientProperties(), getName(),
20+
saslResponse, "en_US");
21+
22+
try {
23+
return (AMQP.Connection.Tune) channel.rpc(startOk).getMethod();
24+
} catch (ShutdownSignalException e) {
25+
throw AMQChannel.wrap(e, "Possibly caused by authentication failure");
26+
}
27+
}
28+
29+
public String getName() {
30+
return "PLAIN";
31+
}
32+
}

0 commit comments

Comments
 (0)