From b5d51f84e3a36a214bcb82dc82da19a123838639 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Wed, 5 May 2010 20:02:53 +0000 Subject: [PATCH] Add ActiveMQ security support git-svn-id: https://svn.argeo.org/commons/trunk@3553 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- .../runtime/org.argeo.security.core/pom.xml | 6 + .../ActiveMqSecurityBrokerPlugin.java | 66 ++++++ .../ActiveMqSpringSecurityContext.java | 31 +++ .../SecuredActiveMqConnectionFactory.java | 203 ++++++++++++++++++ .../security/core/InternalAuthentication.java | 17 +- .../core/MatchingAuthenticationProvider.java | 78 +++++++ .../security/core/UserPasswordDialog.java | 92 ++++++++ .../org/argeo/security/activemq/osLogin.conf | 12 ++ 8 files changed, 499 insertions(+), 6 deletions(-) create mode 100644 security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/activemq/ActiveMqSecurityBrokerPlugin.java create mode 100644 security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/activemq/ActiveMqSpringSecurityContext.java create mode 100644 security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/activemq/SecuredActiveMqConnectionFactory.java create mode 100644 security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/MatchingAuthenticationProvider.java create mode 100644 security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/UserPasswordDialog.java create mode 100644 security/runtime/org.argeo.security.core/src/main/resources/org/argeo/security/activemq/osLogin.conf diff --git a/security/runtime/org.argeo.security.core/pom.xml b/security/runtime/org.argeo.security.core/pom.xml index 03f27740f..221bdfebd 100644 --- a/security/runtime/org.argeo.security.core/pom.xml +++ b/security/runtime/org.argeo.security.core/pom.xml @@ -106,6 +106,12 @@ com.springsource.org.antlr + + + org.argeo.dep.osgi + org.argeo.dep.osgi.activemq + + org.junit diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/activemq/ActiveMqSecurityBrokerPlugin.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/activemq/ActiveMqSecurityBrokerPlugin.java new file mode 100644 index 000000000..4e074aadc --- /dev/null +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/activemq/ActiveMqSecurityBrokerPlugin.java @@ -0,0 +1,66 @@ +package org.argeo.security.activemq; + +import org.apache.activemq.broker.BrokerPluginSupport; +import org.apache.activemq.broker.ConnectionContext; +import org.apache.activemq.command.ConnectionInfo; +import org.argeo.ArgeoException; +import org.argeo.security.core.InternalAuthentication; +import org.springframework.security.Authentication; +import org.springframework.security.AuthenticationManager; +import org.springframework.security.context.SecurityContext; +import org.springframework.security.context.SecurityContextHolder; +import org.springframework.security.providers.UsernamePasswordAuthenticationToken; + +public class ActiveMqSecurityBrokerPlugin extends BrokerPluginSupport { +// private final static Log log = LogFactory +// .getLog(ActiveMqSecurityBrokerPlugin.class); + + private AuthenticationManager authenticationManager; + private String systemUsername = InternalAuthentication.DEFAULT_SYSTEM_USERNAME; + private String systemRole = InternalAuthentication.DEFAULT_SYSTEM_ROLE; + + @Override + public void addConnection(ConnectionContext context, ConnectionInfo info) + throws Exception { + String username = info.getUserName(); + if (username == null) + throw new ArgeoException("No user name provided"); + String password = info.getPassword(); + if (password == null) { + password = context.getConnection().getRemoteAddress().substring(1); + password = password.substring(0, password.lastIndexOf(':')); + } + + SecurityContext securityContext = SecurityContextHolder.getContext(); + + final Authentication authRequest; + if (username.equals(systemUsername)) + authRequest = new InternalAuthentication(password, username, + systemRole); + else + authRequest = new UsernamePasswordAuthenticationToken(username, + password); + + final Authentication auth = authenticationManager + .authenticate(authRequest); + securityContext.setAuthentication(auth); + context.setSecurityContext(new ActiveMqSpringSecurityContext( + securityContext)); + + super.addConnection(context, info); + } + + public void setAuthenticationManager( + AuthenticationManager authenticationManager) { + this.authenticationManager = authenticationManager; + } + + public void setSystemUsername(String systemUsername) { + this.systemUsername = systemUsername; + } + + public void setSystemRole(String systemRole) { + this.systemRole = systemRole; + } + +} diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/activemq/ActiveMqSpringSecurityContext.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/activemq/ActiveMqSpringSecurityContext.java new file mode 100644 index 000000000..1c7db3f12 --- /dev/null +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/activemq/ActiveMqSpringSecurityContext.java @@ -0,0 +1,31 @@ +package org.argeo.security.activemq; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.security.GrantedAuthority; +import org.springframework.security.context.SecurityContext; + +public class ActiveMqSpringSecurityContext extends + org.apache.activemq.security.SecurityContext { + + private final SecurityContext springSecurityContext; + + public ActiveMqSpringSecurityContext(SecurityContext springSecurityContext) { + super(springSecurityContext.getAuthentication().getName()); + this.springSecurityContext = springSecurityContext; + } + + @Override + public Set getPrincipals() { + return new HashSet(Arrays + .asList(springSecurityContext.getAuthentication() + .getAuthorities())); + } + + public SecurityContext getSpringSecurityContext() { + return springSecurityContext; + } + +} diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/activemq/SecuredActiveMqConnectionFactory.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/activemq/SecuredActiveMqConnectionFactory.java new file mode 100644 index 000000000..440a0226c --- /dev/null +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/activemq/SecuredActiveMqConnectionFactory.java @@ -0,0 +1,203 @@ +package org.argeo.security.activemq; + +import java.io.InputStream; +import java.net.URL; +import java.security.KeyStore; +import java.security.Principal; +import java.security.SecureRandom; + +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.JMSException; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.TrustManagerFactory; +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +import org.apache.activemq.ActiveMQSslConnectionFactory; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.ArgeoException; +import org.argeo.security.core.UserPasswordDialog; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.io.Resource; +import org.springframework.jms.connection.CachingConnectionFactory; +import org.springframework.jms.connection.UserCredentialsConnectionFactoryAdapter; + +public class SecuredActiveMqConnectionFactory implements ConnectionFactory, + InitializingBean, DisposableBean { + + public final static String AUTHMODE_UI = "ui"; + public final static String AUTHMODE_OS = "os"; + public final static String AUTHMODE_DEFAULT = AUTHMODE_OS; + private final static String LOGIN_CONFIG_PROPERTY = "java.security.auth.login.config"; + + private final static Log log = LogFactory + .getLog(SecuredActiveMqConnectionFactory.class); + + private String keyStorePassword; + private Resource keyStore; + private String keyStoreType = "JKS";// "PKCS12" + private String brokerURL; + + private String authenticationMode; + + private CachingConnectionFactory cachingConnectionFactory; + + public Connection createConnection() throws JMSException { + return cachingConnectionFactory.createConnection(); + } + + public Connection createConnection(String userName, String password) + throws JMSException { + throw new UnsupportedOperationException(); + } + + public void afterPropertiesSet() throws Exception { + ActiveMQSslConnectionFactory activeMQSslConnectionFactory = new ActiveMQSslConnectionFactory(); + prepareActiveMqSslConnectionFactory(activeMQSslConnectionFactory); + activeMQSslConnectionFactory.setBrokerURL(brokerURL); + UserCredentialsConnectionFactoryAdapter uccfa = new UserCredentialsConnectionFactoryAdapter(); + uccfa.setTargetConnectionFactory(activeMQSslConnectionFactory); + cachingConnectionFactory = new CachingConnectionFactory(); + cachingConnectionFactory.setTargetConnectionFactory(uccfa); + + initConnectionFactoryCredentials(uccfa); + cachingConnectionFactory.initConnection(); + log.info("Connected to " + brokerURL); + uccfa.setUsername(null); + uccfa.setPassword(null); + + } + + protected void initConnectionFactoryCredentials( + final UserCredentialsConnectionFactoryAdapter uccfa) { + if (authenticationMode == null) + authenticationMode = AUTHMODE_DEFAULT; + + if (AUTHMODE_OS.equals(authenticationMode)) { + // Cache previous value of login conf location + String oldLoginConfLocation = System + .getProperty(LOGIN_CONFIG_PROPERTY); + // Find OS family + String osName = System.getProperty("os.name"); + final String auth; + if (osName.startsWith("Windows")) + auth = "Windows"; + else if (osName.startsWith("SunOS") || osName.startsWith("Solaris")) + auth = "Solaris"; + else + auth = "Unix"; + + Subject subject; + try { + + URL url = getClass().getResource( + "/org/argeo/security/activemq/osLogin.conf"); + + System.setProperty(LOGIN_CONFIG_PROPERTY, url.toString()); + LoginContext lc = new LoginContext(auth); + lc.login(); + subject = lc.getSubject(); + } catch (LoginException le) { + throw new ArgeoException("OS authentication failed", le); + } finally { + if (oldLoginConfLocation != null) + System.setProperty(LOGIN_CONFIG_PROPERTY, + oldLoginConfLocation); + } + + // Extract user name + String osUsername = null; + for (Principal principal : subject.getPrincipals()) { + String className = principal.getClass().getName(); + if ("Unix".equals(auth) + && "com.sun.security.auth.UnixPrincipal" + .equals(className)) + osUsername = principal.getName(); + else if ("Windows".equals(auth) + && "com.sun.security.auth.NTUserPrincipal" + .equals(className)) + osUsername = principal.getName(); + else if ("Solaris".equals(auth) + && "com.sun.security.auth.SolarisPrincipal" + .equals(className)) + osUsername = principal.getName(); + } + + if (osUsername == null) + throw new ArgeoException("Could not find OS user name"); + + uccfa.setUsername(osUsername); + uccfa.setPassword(null); + + } else if (AUTHMODE_UI.equals(authenticationMode)) { + UserPasswordDialog dialog = new UserPasswordDialog() { + private static final long serialVersionUID = -891646559691412088L; + + protected void useCredentials(String username, char[] password) { + uccfa.setUsername(username); + uccfa.setPassword(new String(password)); + } + }; + dialog.setVisible(true); + } else { + throw new ArgeoException("Authentication mode '" + + authenticationMode + "' is not supported"); + } + + } + + protected void prepareActiveMqSslConnectionFactory( + ActiveMQSslConnectionFactory connectionFactory) { + try { + KeyStore keyStoreKs = KeyStore.getInstance(keyStoreType); + + InputStream keyInput = keyStore.getInputStream(); + keyStoreKs.load(keyInput, + keyStorePassword != null ? keyStorePassword.toCharArray() + : null); + keyInput.close(); + + TrustManagerFactory tmf = TrustManagerFactory + .getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(keyStoreKs); + + KeyManagerFactory keyManagerFactory = KeyManagerFactory + .getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStoreKs, keyStorePassword.toCharArray()); + + connectionFactory.setKeyAndTrustManagers(keyManagerFactory + .getKeyManagers(), tmf.getTrustManagers(), + new SecureRandom()); + } catch (Exception e) { + throw new ArgeoException( + "Cannot initailize JMS conneciton factory", e); + } + + } + + public void destroy() throws Exception { + if (cachingConnectionFactory != null) + cachingConnectionFactory.destroy(); + } + + public void setKeyStorePassword(String keyStorePassword) { + this.keyStorePassword = keyStorePassword; + } + + public void setKeyStore(Resource keyStore) { + this.keyStore = keyStore; + } + + public void setKeyStoreType(String keyStoreType) { + this.keyStoreType = keyStoreType; + } + + public void setBrokerURL(String brokerUrl) { + this.brokerURL = brokerUrl; + } + +} diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/InternalAuthentication.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/InternalAuthentication.java index 99ac3ad4e..c6a657cd0 100644 --- a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/InternalAuthentication.java +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/InternalAuthentication.java @@ -6,16 +6,21 @@ import org.springframework.security.adapters.PrincipalSpringSecurityUserToken; public class InternalAuthentication extends PrincipalSpringSecurityUserToken { private static final long serialVersionUID = -6783376375615949315L; - private final static String SYSTEM_USERNAME = "system"; - private final static String SYSTEM_ROLE = "ROLE_SYSTEM"; + public final static String DEFAULT_SYSTEM_USERNAME = "system"; + public final static String DEFAULT_SYSTEM_ROLE = "ROLE_SYSTEM"; - public InternalAuthentication(String key) { + public InternalAuthentication(String key, String systemUsername, + String systemRole) { super( key, - SYSTEM_USERNAME, + systemUsername, key, - new GrantedAuthority[] { new GrantedAuthorityImpl(SYSTEM_ROLE) }, - SYSTEM_USERNAME); + new GrantedAuthority[] { new GrantedAuthorityImpl(systemRole) }, + systemUsername); + } + + public InternalAuthentication(String key) { + this(key, DEFAULT_SYSTEM_USERNAME, DEFAULT_SYSTEM_ROLE); } } diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/MatchingAuthenticationProvider.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/MatchingAuthenticationProvider.java new file mode 100644 index 000000000..d53bf7871 --- /dev/null +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/MatchingAuthenticationProvider.java @@ -0,0 +1,78 @@ +package org.argeo.security.core; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import org.apache.commons.io.IOUtils; +import org.springframework.core.io.Resource; +import org.springframework.security.AuthenticationException; +import org.springframework.security.BadCredentialsException; +import org.springframework.security.GrantedAuthority; +import org.springframework.security.GrantedAuthorityImpl; +import org.springframework.security.providers.UsernamePasswordAuthenticationToken; +import org.springframework.security.providers.dao.AbstractUserDetailsAuthenticationProvider; +import org.springframework.security.userdetails.User; +import org.springframework.security.userdetails.UserDetails; + +public class MatchingAuthenticationProvider extends + AbstractUserDetailsAuthenticationProvider { + + private Resource mapping; + private Properties properties; + + private List defaultRoles = new ArrayList(); + + @Override + protected void doAfterPropertiesSet() throws Exception { + properties = new Properties(); + InputStream propIn = mapping.getInputStream(); + try { + properties.load(propIn); + } finally { + IOUtils.closeQuietly(propIn); + } + } + + @Override + protected void additionalAuthenticationChecks(UserDetails userDetails, + UsernamePasswordAuthenticationToken authentication) + throws AuthenticationException { + if (!userDetails.getPassword().equals(authentication.getCredentials())) + throw new BadCredentialsException( + "Invalid credentails provided by " + + authentication.getName()); + } + + @Override + protected UserDetails retrieveUser(String username, + UsernamePasswordAuthenticationToken authentication) + throws AuthenticationException { + String value = properties.getProperty(username); + if (value == null) + throw new BadCredentialsException("User " + username + + " is not registered"); + List grantedAuthorities = new ArrayList(); + for (String role : defaultRoles) + grantedAuthorities.add(new GrantedAuthorityImpl(role)); + return new User( + username, + value, + true, + true, + true, + true, + grantedAuthorities + .toArray(new GrantedAuthority[grantedAuthorities.size()])); + } + + public void setMapping(Resource mapping) { + this.mapping = mapping; + } + + public void setDefaultRoles(List defaultRoles) { + this.defaultRoles = defaultRoles; + } + +} diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/UserPasswordDialog.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/UserPasswordDialog.java new file mode 100644 index 000000000..b43d6b0dc --- /dev/null +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/core/UserPasswordDialog.java @@ -0,0 +1,92 @@ +package org.argeo.security.core; + +import java.awt.Container; +import java.awt.GridLayout; +import java.awt.Panel; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Arrays; + +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JTextField; + +public class UserPasswordDialog extends JDialog implements ActionListener { + private static final long serialVersionUID = -9052993072210981198L; + private static String OK = "ok"; + + private JTextField username = new JTextField("", 10); + private JPasswordField password = new JPasswordField("", 10); + + private JButton okButton; + private JButton cancelButton; + + public UserPasswordDialog() { + setTitle("Credentials"); + setModal(true); + setLocationRelativeTo(null); + setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + + JPanel p1 = new JPanel(new GridLayout(2, 2, 3, 3)); + p1.add(new JLabel("User")); + p1.add(username); + p1.add(new JLabel("Password")); + password.setActionCommand(OK); + password.addActionListener(this); + p1.add(password); + add("Center", p1); + + Panel p2 = new Panel(); + okButton = addButton(p2, "OK"); + okButton.setActionCommand(OK); + cancelButton = addButton(p2, "Cancel"); + add("South", p2); + setSize(240, 120); + + pack(); + } + + /** To be overridden */ + protected void useCredentials(String username, char[] password) { + // does nothing + } + + private JButton addButton(Container c, String name) { + JButton button = new JButton(name); + button.addActionListener(this); + c.add(button); + return button; + } + + public final void actionPerformed(ActionEvent evt) { + Object source = evt.getSource(); + if (source == okButton || evt.getActionCommand().equals(OK)) { + char[] p = password.getPassword(); + useCredentials(username.getText(), p); + Arrays.fill(p, '0'); + cleanUp(); + } else if (source == cancelButton) + cleanUp(); + } + + private void cleanUp() { + password.setText(""); + dispose(); + } + + public static void main(String[] args) { + UserPasswordDialog dialog = new UserPasswordDialog() { + private static final long serialVersionUID = -891646559691412088L; + + protected void useCredentials(String username, char[] password) { + System.out.println(username + "/" + new String(password)); + } + }; + dialog.setVisible(true); + System.out.println("After show"); + } +} diff --git a/security/runtime/org.argeo.security.core/src/main/resources/org/argeo/security/activemq/osLogin.conf b/security/runtime/org.argeo.security.core/src/main/resources/org/argeo/security/activemq/osLogin.conf new file mode 100644 index 000000000..17df63ca9 --- /dev/null +++ b/security/runtime/org.argeo.security.core/src/main/resources/org/argeo/security/activemq/osLogin.conf @@ -0,0 +1,12 @@ +Unix { + com.sun.security.auth.module.UnixLoginModule required; +}; + +Solaris { + com.sun.security.auth.module.SolarisLoginModule required; +}; + +Windows { + com.sun.security.auth.module.NTLoginModule required; +}; + -- 2.30.2