From 67ccfcad88266aa58066d5427e35820f162b9b55 Mon Sep 17 00:00:00 2001 From: Timothy Bish Date: Wed, 4 Mar 2015 14:06:04 -0500 Subject: [PATCH] https://issues.apache.org/jira/browse/AMQ-5470 Allow for early SASL authentication and failure if credentials not valid. --- .../transport/amqp/AmqpProtocolConverter.java | 64 +++++++++++++++- .../amqp/JMSClientSimpleAuthTest.java | 34 +++++++-- .../AbstractAuthenticationBroker.java | 6 +- .../security/AuthenticationBroker.java | 42 +++++++++++ .../security/DefaultAuthorizationMap.java | 17 +++-- .../security/JaasAuthenticationBroker.java | 41 ++++++----- .../JaasCertificateAuthenticationBroker.java | 55 +++++++------- .../JaasDualAuthenticationBroker.java | 17 ++++- .../security/SimpleAuthenticationBroker.java | 73 ++++++++++--------- 9 files changed, 248 insertions(+), 101 deletions(-) create mode 100644 activemq-broker/src/main/java/org/apache/activemq/security/AuthenticationBroker.java diff --git a/activemq-amqp/src/main/java/org/apache/activemq/transport/amqp/AmqpProtocolConverter.java b/activemq-amqp/src/main/java/org/apache/activemq/transport/amqp/AmqpProtocolConverter.java index a2a5ee5de3..fa0e024861 100644 --- a/activemq-amqp/src/main/java/org/apache/activemq/transport/amqp/AmqpProtocolConverter.java +++ b/activemq-amqp/src/main/java/org/apache/activemq/transport/amqp/AmqpProtocolConverter.java @@ -19,6 +19,8 @@ package org.apache.activemq.transport.amqp; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; +import java.security.Principal; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -27,6 +29,7 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.jms.Destination; @@ -65,6 +68,8 @@ import org.apache.activemq.command.SessionInfo; import org.apache.activemq.command.ShutdownInfo; import org.apache.activemq.command.SubscriptionInfo; import org.apache.activemq.command.TransactionInfo; +import org.apache.activemq.security.AuthenticationBroker; +import org.apache.activemq.security.SecurityContext; import org.apache.activemq.selector.SelectorParser; import org.apache.activemq.store.PersistenceAdapterSupport; import org.apache.activemq.transport.amqp.message.AMQPNativeInboundTransformer; @@ -139,6 +144,7 @@ class AmqpProtocolConverter implements IAmqpProtocolConverter { private final AmqpTransport amqpTransport; private final AmqpWireFormat amqpWireFormat; private final BrokerService brokerService; + private AuthenticationBroker authenticator; protected int prefetch; protected int producerCredit; @@ -310,14 +316,22 @@ class AmqpProtocolConverter implements IAmqpProtocolConverter { if (parts.length > 1) { connectionInfo.setPassword(parts[1].utf8().toString()); } - // We can't really auth at this point since we don't - // know the client id yet.. :( - sasl.done(Sasl.SaslOutcome.PN_SASL_OK); + + if (tryAuthenticate(connectionInfo, amqpTransport.getPeerCertificates())) { + sasl.done(Sasl.SaslOutcome.PN_SASL_OK); + } else { + sasl.done(Sasl.SaslOutcome.PN_SASL_AUTH); + } + amqpTransport.getWireFormat().resetMagicRead(); sasl = null; LOG.debug("SASL [PLAIN] Handshake complete."); } else if ("ANONYMOUS".equals(sasl.getRemoteMechanisms()[0])) { - sasl.done(Sasl.SaslOutcome.PN_SASL_OK); + if (tryAuthenticate(connectionInfo, amqpTransport.getPeerCertificates())) { + sasl.done(Sasl.SaslOutcome.PN_SASL_OK); + } else { + sasl.done(Sasl.SaslOutcome.PN_SASL_AUTH); + } amqpTransport.getWireFormat().resetMagicRead(); sasl = null; LOG.debug("SASL [ANONYMOUS] Handshake complete."); @@ -1690,4 +1704,46 @@ class AmqpProtocolConverter implements IAmqpProtocolConverter { return subscriptions; } + + public boolean tryAuthenticate(ConnectionInfo info, X509Certificate[] peerCertificates) { + try { + if (getAuthenticator().authenticate(info.getUserName(), info.getPassword(), peerCertificates) != null) { + return true; + } + + return false; + } catch (Throwable error) { + return false; + } + } + + private AuthenticationBroker getAuthenticator() { + if (authenticator == null) { + try { + authenticator = (AuthenticationBroker) brokerService.getBroker().getAdaptor(AuthenticationBroker.class); + } catch (Exception e) { + LOG.debug("Failed to lookup AuthenticationBroker from Broker, will use a default Noop version."); + } + + if (authenticator == null) { + authenticator = new DefaultAuthenticationBroker(); + } + } + + return authenticator; + } + + private class DefaultAuthenticationBroker implements AuthenticationBroker { + + @Override + public SecurityContext authenticate(String username, String password, X509Certificate[] peerCertificates) throws SecurityException { + return new SecurityContext(username) { + + @Override + public Set getPrincipals() { + return null; + } + }; + } + } } diff --git a/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/JMSClientSimpleAuthTest.java b/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/JMSClientSimpleAuthTest.java index d710be005d..d4d5903ca9 100644 --- a/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/JMSClientSimpleAuthTest.java +++ b/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/JMSClientSimpleAuthTest.java @@ -69,8 +69,6 @@ public class JMSClientSimpleAuthTest { try { Connection connection = JMSClientContext.INSTANCE.createConnection(amqpURI, "", ""); connection.start(); - Thread.sleep(500); - connection.createSession(false, Session.AUTO_ACKNOWLEDGE); fail("Expected JMSException"); } catch (JMSSecurityException ex) { LOG.debug("Failed to authenticate connection with no user / password."); @@ -91,8 +89,6 @@ public class JMSClientSimpleAuthTest { try { Connection connection = JMSClientContext.INSTANCE.createConnection(amqpURI, "nosuchuser", "blah"); connection.start(); - Thread.sleep(500); - connection.createSession(false, Session.AUTO_ACKNOWLEDGE); fail("Expected JMSException"); } catch (JMSSecurityException ex) { LOG.debug("Failed to authenticate connection with no user / password."); @@ -113,8 +109,6 @@ public class JMSClientSimpleAuthTest { try { Connection connection = JMSClientContext.INSTANCE.createConnection(amqpURI, "user", "wrongPassword"); connection.start(); - Thread.sleep(500); - connection.createSession(false, Session.AUTO_ACKNOWLEDGE); fail("Expected JMSException"); } catch (JMSSecurityException ex) { LOG.debug("Failed to authenticate connection with no user / password."); @@ -130,6 +124,33 @@ public class JMSClientSimpleAuthTest { } } + @Test(timeout = 30000) + public void testRepeatedWrongPasswordAttempts() throws Exception { + for (int i = 0; i < 25; ++i) { + Connection connection = null; + try { + connection = JMSClientContext.INSTANCE.createConnection(amqpURI, "user", "wrongPassword"); + connection.start(); + fail("Expected JMSException"); + } catch (JMSSecurityException ex) { + LOG.debug("Failed to authenticate connection with no user / password."); + } catch (JMSException e) { + Exception linkedException = e.getLinkedException(); + if (linkedException != null && linkedException instanceof ConnectionClosedException) { + ConnectionClosedException cce = (ConnectionClosedException) linkedException; + assertEquals("Error{condition=unauthorized-access,description=User name [user] or password is invalid.}", cce.getRemoteError().toString()); + } else { + LOG.error("Unexpected Exception", e); + fail("Unexpected exception: " + e.getMessage()); + } + } finally { + if (connection != null) { + connection.close(); + } + } + } + } + @Test(timeout = 30000) public void testSendReceive() throws Exception { Connection connection = JMSClientContext.INSTANCE.createConnection(amqpURI, "user", "userPassword"); @@ -205,4 +226,3 @@ public class JMSClientSimpleAuthTest { brokerService.waitUntilStarted(); } } - diff --git a/activemq-broker/src/main/java/org/apache/activemq/security/AbstractAuthenticationBroker.java b/activemq-broker/src/main/java/org/apache/activemq/security/AbstractAuthenticationBroker.java index c13b1bdb95..622a4f6585 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/security/AbstractAuthenticationBroker.java +++ b/activemq-broker/src/main/java/org/apache/activemq/security/AbstractAuthenticationBroker.java @@ -24,7 +24,7 @@ import org.apache.activemq.broker.ConnectionContext; import org.apache.activemq.command.ActiveMQDestination; import org.apache.activemq.command.ConnectionInfo; -public class AbstractAuthenticationBroker extends BrokerFilter { +public abstract class AbstractAuthenticationBroker extends BrokerFilter implements AuthenticationBroker { protected final CopyOnWriteArrayList securityContexts = new CopyOnWriteArrayList(); @@ -51,10 +51,6 @@ public class AbstractAuthenticationBroker extends BrokerFilter { } } - /** - * Previously logged in users may no longer have the same access anymore. - * Refresh all the logged into users. - */ public void refresh() { for (SecurityContext sc : securityContexts) { sc.getAuthorizedReadDests().clear(); diff --git a/activemq-broker/src/main/java/org/apache/activemq/security/AuthenticationBroker.java b/activemq-broker/src/main/java/org/apache/activemq/security/AuthenticationBroker.java new file mode 100644 index 0000000000..fd241f908c --- /dev/null +++ b/activemq-broker/src/main/java/org/apache/activemq/security/AuthenticationBroker.java @@ -0,0 +1,42 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.activemq.security; + +import java.security.cert.X509Certificate; + +/** + * Base for all broker plugins that wish to provide connection authentication services + */ +public interface AuthenticationBroker { + + /** + * Authenticate the given user using the mechanism provided by this service. + * + * @param username + * the given user name to authenticate, null indicates an anonymous user. + * @param password + * the given password for the user to authenticate. + * @param peerCertificates + * for an SSL channel the certificates from remote peer. + * + * @return a new SecurityContext for the authenticated user. + * + * @throws SecurityException if the user cannot be authenticated. + */ + SecurityContext authenticate(String username, String password, X509Certificate[] peerCertificates) throws SecurityException; + +} \ No newline at end of file diff --git a/activemq-broker/src/main/java/org/apache/activemq/security/DefaultAuthorizationMap.java b/activemq-broker/src/main/java/org/apache/activemq/security/DefaultAuthorizationMap.java index 2d8ae49896..e2a3d8eb3a 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/security/DefaultAuthorizationMap.java +++ b/activemq-broker/src/main/java/org/apache/activemq/security/DefaultAuthorizationMap.java @@ -16,10 +16,6 @@ */ package org.apache.activemq.security; -import org.apache.activemq.command.ActiveMQDestination; -import org.apache.activemq.filter.DestinationMap; -import org.apache.activemq.filter.DestinationMapEntry; - import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.security.Principal; @@ -29,6 +25,10 @@ import java.util.Iterator; import java.util.List; import java.util.Set; +import org.apache.activemq.command.ActiveMQDestination; +import org.apache.activemq.filter.DestinationMap; +import org.apache.activemq.filter.DestinationMapEntry; + /** * Represents a destination based configuration of policies so that individual * destinations or wildcard hierarchies of destinations can be configured using @@ -64,6 +64,7 @@ public class DefaultAuthorizationMap extends DestinationMap implements Authoriza return this.tempDestinationAuthorizationEntry; } + @Override public Set getTempDestinationAdminACLs() { if (tempDestinationAuthorizationEntry != null) { Set answer = new WildcardAwareSet(); @@ -74,6 +75,7 @@ public class DefaultAuthorizationMap extends DestinationMap implements Authoriza } } + @Override public Set getTempDestinationReadACLs() { if (tempDestinationAuthorizationEntry != null) { Set answer = new WildcardAwareSet(); @@ -84,6 +86,7 @@ public class DefaultAuthorizationMap extends DestinationMap implements Authoriza } } + @Override public Set getTempDestinationWriteACLs() { if (tempDestinationAuthorizationEntry != null) { Set answer = new WildcardAwareSet(); @@ -94,6 +97,7 @@ public class DefaultAuthorizationMap extends DestinationMap implements Authoriza } } + @Override public Set getAdminACLs(ActiveMQDestination destination) { Set entries = getAllEntries(destination); Set answer = new WildcardAwareSet(); @@ -106,6 +110,7 @@ public class DefaultAuthorizationMap extends DestinationMap implements Authoriza return answer; } + @Override public Set getReadACLs(ActiveMQDestination destination) { Set entries = getAllEntries(destination); Set answer = new WildcardAwareSet(); @@ -118,6 +123,7 @@ public class DefaultAuthorizationMap extends DestinationMap implements Authoriza return answer; } + @Override public Set getWriteACLs(ActiveMQDestination destination) { Set entries = getAllEntries(destination); Set answer = new WildcardAwareSet(); @@ -150,7 +156,7 @@ public class DefaultAuthorizationMap extends DestinationMap implements Authoriza * matching values. */ @Override - @SuppressWarnings({ "rawtypes", "unchecked" }) + @SuppressWarnings("rawtypes") public synchronized Set get(ActiveMQDestination key) { if (key.isComposite()) { ActiveMQDestination[] destinations = key.getCompositeDestinations(); @@ -184,6 +190,7 @@ public class DefaultAuthorizationMap extends DestinationMap implements Authoriza this.defaultEntry = defaultEntry; } + @Override @SuppressWarnings("rawtypes") protected Class getEntryClass() { return AuthorizationEntry.class; diff --git a/activemq-broker/src/main/java/org/apache/activemq/security/JaasAuthenticationBroker.java b/activemq-broker/src/main/java/org/apache/activemq/security/JaasAuthenticationBroker.java index a082e6f721..148c2e2b76 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/security/JaasAuthenticationBroker.java +++ b/activemq-broker/src/main/java/org/apache/activemq/security/JaasAuthenticationBroker.java @@ -17,6 +17,7 @@ package org.apache.activemq.security; import java.security.Principal; +import java.security.cert.X509Certificate; import java.util.Set; import javax.security.auth.Subject; @@ -58,32 +59,36 @@ public class JaasAuthenticationBroker extends AbstractAuthenticationBroker { @Override public void addConnection(ConnectionContext context, ConnectionInfo info) throws Exception { - if (context.getSecurityContext() == null) { - // Set the TCCL since it seems JAAS needs it to find the login - // module classes. + // Set the TCCL since it seems JAAS needs it to find the login module classes. ClassLoader original = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(JaasAuthenticationBroker.class.getClassLoader()); - try { - // Do the login. - try { - JassCredentialCallbackHandler callback = new JassCredentialCallbackHandler(info - .getUserName(), info.getPassword()); - LoginContext lc = new LoginContext(jassConfiguration, callback); - lc.login(); - Subject subject = lc.getSubject(); - SecurityContext s = new JaasSecurityContext(info.getUserName(), subject); - context.setSecurityContext(s); - securityContexts.add(s); - } catch (Exception e) { - throw (SecurityException)new SecurityException("User name [" + info.getUserName() + "] or password is invalid.") - .initCause(e); - } + try { + SecurityContext s = authenticate(info.getUserName(), info.getPassword(), null); + context.setSecurityContext(s); + securityContexts.add(s); } finally { Thread.currentThread().setContextClassLoader(original); } } super.addConnection(context, info); } + + @Override + public SecurityContext authenticate(String username, String password, X509Certificate[] certificates) throws SecurityException { + SecurityContext result = null; + JassCredentialCallbackHandler callback = new JassCredentialCallbackHandler(username, password); + try { + LoginContext lc = new LoginContext(jassConfiguration, callback); + lc.login(); + Subject subject = lc.getSubject(); + + result = new JaasSecurityContext(username, subject); + } catch (Exception ex) { + throw new SecurityException("User name [" + username + "] or password is invalid.", ex); + } + + return result; + } } diff --git a/activemq-broker/src/main/java/org/apache/activemq/security/JaasCertificateAuthenticationBroker.java b/activemq-broker/src/main/java/org/apache/activemq/security/JaasCertificateAuthenticationBroker.java index bd75174a95..bf80d69ce9 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/security/JaasCertificateAuthenticationBroker.java +++ b/activemq-broker/src/main/java/org/apache/activemq/security/JaasCertificateAuthenticationBroker.java @@ -37,15 +37,13 @@ import org.apache.activemq.jaas.UserPrincipal; * grant JAAS access to incoming connections' SSL certificate chains. NOTE: * There is a chance that the incoming connection does not have a valid * certificate (has null). - * - * @author sepandm@gmail.com (Sepand) */ -public class JaasCertificateAuthenticationBroker extends BrokerFilter { +public class JaasCertificateAuthenticationBroker extends BrokerFilter implements AuthenticationBroker { private final String jaasConfiguration; /** * Simple constructor. Leaves everything to superclass. - * + * * @param next The Broker that does the actual work for this Filter. * @param jassConfiguration The JAAS domain configuration name (refere to * JAAS documentation). @@ -62,11 +60,12 @@ public class JaasCertificateAuthenticationBroker extends BrokerFilter { * chain and the JAAS module specified through the JAAS framework. NOTE: The * security context's username will be set to the first UserPrincipal * created by the login module. - * + * * @param context The context for the incoming Connection. * @param info The ConnectionInfo Command representing the incoming * connection. */ + @Override public void addConnection(ConnectionContext context, ConnectionInfo info) throws Exception { if (context.getSecurityContext() == null) { @@ -79,26 +78,8 @@ public class JaasCertificateAuthenticationBroker extends BrokerFilter { ClassLoader original = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(JaasAuthenticationBroker.class.getClassLoader()); try { - // Do the login. - try { - CallbackHandler callback = new JaasCertificateCallbackHandler((X509Certificate[])info.getTransportContext()); - LoginContext lc = new LoginContext(jaasConfiguration, callback); - lc.login(); - Subject subject = lc.getSubject(); - - String dnName = ""; - - for (Principal principal : subject.getPrincipals()) { - if (principal instanceof UserPrincipal) { - dnName = ((UserPrincipal)principal).getName(); - break; - } - } - SecurityContext s = new JaasCertificateSecurityContext(dnName, subject, (X509Certificate[])info.getTransportContext()); - context.setSecurityContext(s); - } catch (Exception e) { - throw new SecurityException("User name [" + info.getUserName() + "] or password is invalid. " + e.getMessage(), e); - } + SecurityContext s = authenticate(info.getUserName(), info.getPassword(), (X509Certificate[]) info.getTransportContext()); + context.setSecurityContext(s); } finally { Thread.currentThread().setContextClassLoader(original); } @@ -109,9 +90,33 @@ public class JaasCertificateAuthenticationBroker extends BrokerFilter { /** * Overriding removeConnection to make sure the security context is cleaned. */ + @Override public void removeConnection(ConnectionContext context, ConnectionInfo info, Throwable error) throws Exception { super.removeConnection(context, info, error); context.setSecurityContext(null); } + + @Override + public SecurityContext authenticate(String username, String password, X509Certificate[] peerCertificates) throws SecurityException { + try { + CallbackHandler callback = new JaasCertificateCallbackHandler(peerCertificates); + LoginContext lc = new LoginContext(jaasConfiguration, callback); + lc.login(); + Subject subject = lc.getSubject(); + + String dnName = ""; + + for (Principal principal : subject.getPrincipals()) { + if (principal instanceof UserPrincipal) { + dnName = ((UserPrincipal)principal).getName(); + break; + } + } + + return new JaasCertificateSecurityContext(dnName, subject, peerCertificates); + } catch (Exception e) { + throw new SecurityException("User name [" + username + "] or password is invalid. " + e.getMessage(), e); + } + } } diff --git a/activemq-broker/src/main/java/org/apache/activemq/security/JaasDualAuthenticationBroker.java b/activemq-broker/src/main/java/org/apache/activemq/security/JaasDualAuthenticationBroker.java index 7931c63175..28d8fefaf0 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/security/JaasDualAuthenticationBroker.java +++ b/activemq-broker/src/main/java/org/apache/activemq/security/JaasDualAuthenticationBroker.java @@ -17,6 +17,8 @@ package org.apache.activemq.security; +import java.security.cert.X509Certificate; + import org.apache.activemq.broker.Broker; import org.apache.activemq.broker.BrokerFilter; import org.apache.activemq.broker.ConnectionContext; @@ -56,7 +58,7 @@ import org.apache.activemq.transport.tcp.SslTransportServer; * }; * */ -public class JaasDualAuthenticationBroker extends BrokerFilter { +public class JaasDualAuthenticationBroker extends BrokerFilter implements AuthenticationBroker { private final JaasCertificateAuthenticationBroker sslBroker; private final JaasAuthenticationBroker nonSslBroker; @@ -87,13 +89,11 @@ public class JaasDualAuthenticationBroker extends BrokerFilter { @Override public void addConnection(ConnectionContext context, ConnectionInfo info) throws Exception { if (context.getSecurityContext() == null) { - boolean isSSL; + boolean isSSL = false; Connector connector = context.getConnector(); if (connector instanceof TransportConnector) { TransportConnector transportConnector = (TransportConnector) connector; isSSL = transportConnector.getServer().isSslServer(); - } else { - isSSL = false; } if (isSSL) { @@ -134,4 +134,13 @@ public class JaasDualAuthenticationBroker extends BrokerFilter { super.removeDestination(context, destination, timeout); } + + @Override + public SecurityContext authenticate(String username, String password, X509Certificate[] peerCertificates) throws SecurityException { + if (peerCertificates != null) { + return this.sslBroker.authenticate(username, password, peerCertificates); + } else { + return this.nonSslBroker.authenticate(username, password, peerCertificates); + } + } } diff --git a/activemq-broker/src/main/java/org/apache/activemq/security/SimpleAuthenticationBroker.java b/activemq-broker/src/main/java/org/apache/activemq/security/SimpleAuthenticationBroker.java index 46544f72e3..ff67ec0db9 100644 --- a/activemq-broker/src/main/java/org/apache/activemq/security/SimpleAuthenticationBroker.java +++ b/activemq-broker/src/main/java/org/apache/activemq/security/SimpleAuthenticationBroker.java @@ -17,6 +17,7 @@ package org.apache.activemq.security; import java.security.Principal; +import java.security.cert.X509Certificate; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -65,46 +66,52 @@ public class SimpleAuthenticationBroker extends AbstractAuthenticationBroker { @Override public void addConnection(ConnectionContext context, ConnectionInfo info) throws Exception { - - SecurityContext s = context.getSecurityContext(); - if (s == null) { - // Check the username and password. - if (anonymousAccessAllowed && info.getUserName() == null && info.getPassword() == null) { - info.setUserName(anonymousUser); - s = new SecurityContext(info.getUserName()) { - @Override - public Set getPrincipals() { - Set groups = new HashSet(); - groups.add(new GroupPrincipal(anonymousGroup)); - return groups; - } - }; - } else { - String pw = userPasswords.get(info.getUserName()); - if (pw == null || !pw.equals(info.getPassword())) { - throw new SecurityException( - "User name [" + info.getUserName() + "] or password is invalid."); - } - - final Set groups = userGroups.get(info.getUserName()); - s = new SecurityContext(info.getUserName()) { - @Override - public Set getPrincipals() { - return groups; - } - }; - } - - context.setSecurityContext(s); - securityContexts.add(s); + SecurityContext securityContext = context.getSecurityContext(); + if (securityContext == null) { + securityContext = authenticate(info.getUserName(), info.getPassword(), null); + context.setSecurityContext(securityContext); + securityContexts.add(securityContext); } try { super.addConnection(context, info); } catch (Exception e) { - securityContexts.remove(s); + securityContexts.remove(securityContext); context.setSecurityContext(null); throw e; } } + + @Override + public SecurityContext authenticate(String username, String password, X509Certificate[] certificates) throws SecurityException { + SecurityContext securityContext = null; + + // Check the username and password. + if (anonymousAccessAllowed && username == null && password == null) { + username = anonymousUser; + securityContext = new SecurityContext(username) { + @Override + public Set getPrincipals() { + Set groups = new HashSet(); + groups.add(new GroupPrincipal(anonymousGroup)); + return groups; + } + }; + } else { + String pw = userPasswords.get(username); + if (pw == null || !pw.equals(password)) { + throw new SecurityException("User name [" + username + "] or password is invalid."); + } + + final Set groups = userGroups.get(username); + securityContext = new SecurityContext(username) { + @Override + public Set getPrincipals() { + return groups; + } + }; + } + + return securityContext; + } }