My system is based on camel and use Apache QPID 0.37.0 to consume messages from a remote AMQPS sever. Our system authenticates via a client certificate. Thus I have this piece of configuration:
<bean id="jmsConnectionFactory" class="org.apache.qpid.jms.JmsConnectionFactory">
<constructor-arg name="remoteURI" value="amqps://some-location:5671?transport.keyStoreLocation=/very/long/path/nnn-openssl.p12&transport.keyStorePassword=*******&transport.trustStoreLocation=/very/long/path/server.keystore&transport.trustStorePassword=*******"/>
</bean>
This is just working fine. However, configuring key/trust store this way (i.e. in the URI) has several drawbacks:
First, it is not easy to read and maintain.
Some components log the URI, so the paths (I can live with it) and the passwords (ouch...) get logged.
I know it's possible to configure via a system property (javax.net.ssl.keyStore, and son on), but it's not an option because different modules may use different key and trust store, and we want to keep them in separate files.
Is there a way to configure those transport properties of JmsConnectionFactory in a different way ?
Something like:
<bean id="jmsConnectionFactory" class="org.apache.qpid.jms.JmsConnectionFactory">
<constructor-arg name="remoteURI" value="amqps://some-location:5671"/>
<property name="transport.keyStoreLocation" value="/very/long/path/nnn-openssl.p12"/>
...
</bean>
Note that this factory is used in a JMSConfig, which in turn is used within an AMQPComponent:
<bean id="jmsConfig" class="org.apache.camel.component.jms.JmsConfiguration" >
<property name="connectionFactory" ref="jmsConnectionFactory" />
...
</bean>
<bean id="amqp" class="org.apache.camel.component.amqp.AMQPComponent">
<property name="testConnectionOnStartup" value="true"/>
<property name="configuration" ref="jmsConfig" />
...
</bean>
The short answer is no, that's all that class supports. However you could write a configuration bean which has the properties you require, and have that create the bean. Off the top of my head, something like this:
#Configuration
public class QpidConfiguration {
// Add setters for these
private String host;
private int port = 5671;
private String keyStore;
private String keyStorePassword;
private String trustStoreLocation;
private String trustStorePassword;
#Bean
public JmsConnectionFactory createConnectionFactory() {
return new JmsConnectionFactory("amqps://" + host" + ":" + port + "?transport.keyStoreLocation=" + keyStoreLocation + "&transport.keyStorePassword=" + keyStorePassword + "&transport.trustStoreLocation=" + trustStoreLocation + "&transport.trustStorePassword=" + trustStorePassword);
}
}
You probably want to add some parameter validation in there to be safe.
You can then define the bean using more convenient properties.
<bean id="jmsConnectionFactory" class="com.example.QpidConfiguration">
<property name="host" value="some-location"/>
<property name="keyStoreLocation" value="/very/long/path/nnn-openssl.p12"/>
...
</bean>
You can also leverage encrypted property placeholders, if your heart so desires.
Related
As per the documentation - https://cwiki.apache.org/confluence/display/CXF20DOC/JAX-RS+Failover#JAX-RSFailover-Code.1, I tried running the following code along with associated configurations, but the circuit breaker mechanism is not opening up once the threshold count for connectivity failures have exceeded. As the circuit stays closed, the invocation attempts are still being accepted which is against expected behaviour.
public class CustomerRestClient {
private CustomerRestClientFactory customerRestClientFactory;
public List<Customer> filterByFirstName(String firstName) {
List<Customer> filteredCustomers = new ArrayList<>();
CircuitBreakerFailoverFeature cbFailoverFeature = new CircuitBreakerFailoverFeature(4, 180000L);
SequentialStrategy strategy = new SequentialStrategy();
cbFailoverFeature.setStrategy(strategy);
List<Feature> featureList = new ArrayList<Feature>();
featureList.add(cbFailoverFeature);
WebClient client = customerRestClientFactory.getClient(featureList).path("/");
// Call service to get all customers
List<Customer> customers = client.get(new GenericType<List<Customer>>() {});
return filteredCustomers;
}
public void setCustomerRestClientFactory(CustomerRestClientFactory customerRestClientFactory) {
this.customerRestClientFactory = customerRestClientFactory;
}
}
public class CustomerRestClientFactory implements InitializingBean {
private List providerList; // Value is injected by Spring
private String serviceUrl; // Value is injected by Spring
public WebClient getClient(List<? extends Feature> featureList) {
if (featureList == null || featureList.isEmpty()) {
throw new IllegalArgumentException("featureList is not initialized.");
}
JAXRSClientFactoryBean bean = new JAXRSClientFactoryBean();
bean.setAddress(serviceUrl);
bean.setServiceClass(WebClient.class);
bean.setProviders(providerList);
bean.setFeatures(featureList);
return bean.createWebClient();
}
}
<bean id="objectMapper" class="com.fasterxml.jackson.databind.ObjectMapper">
<property name="dateFormat">
<bean class="java.text.SimpleDateFormat"> <constructor-arg type="java.lang.String" value="yyyy-MM-dd'T'HH:mm:ss"/>
</bean>
</property>
<property name="serializationInclusion">
<value type="com.fasterxml.jackson.annotation.JsonInclude.Include">NON_NULL</value>
</property>
</bean>
<bean id="jsonProvider" class="com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider">
<property name="mapper" ref="objectMapper"/>
</bean>
<util:list id="providerList">
<ref bean="jsonProvider" />
<bean name="exceptionHandler" class="com.mycompany.refapp.exception.AppExceptionHandler" />
</util:list>
<bean id="customerRestClientFactory" class="com.mycompany.refapp.client.CustomerRestClientFactory">
<property name="providerList" ref="providerList" />
<property name="serviceUrl" value="${customer.rest.service.url}" />
</bean>
Log containing stack traces (available here).
After a lot of debugging, I came to an understanding that the counter for connection failures never exceeds the threshold limit because the state of the data (including the counter) is specific to each of the WebClient objects, instantiated for each call. I assumed that if the same instance of a WebClient is used across multiple calls that fail, then the counter would have been updated and eventually open the circuit. Please find attached screenshot for details.
I would like to get a second opinion on whether my understanding is correct.
You have opened issue https://issues.apache.org/jira/browse/CXF-7663 in parallel, so I share the response from Colm here:
If you want to use the Circuit-Breaker feature then you need to use
the same Webclient instance for all of the invocations - it won't work
if you are creating a new WebClient per-call. Here's a test that shows
how it works:
https://github.com/coheigea/testcases/blob/218044e3126cff9339e27a69cd8d3c5f3fe308ea/apache/cxf/cxf-failover/src/test/java/org/apache/coheigea/cxf/failover/feature/FailoverTest.java#L90
I'm trying to use in the BasicDataSource the username and password as derived from a Bean class:
<bean class="org.apache.commons.dbcp.BasicDataSource" id="myDataSource">
<property name="driverClassName" value="org.teiid.jdbc.TeiidDriver"/>
<property name="url" value="jdbc:myurl"/>
<property name="username" value="${bean:quickstartConfiguration?method=getQueueUsername}"/>
<property name="password" value="${bean:quickstartConfiguration?method=getQueuePassword}"/>
<property name="maxIdle" value="1"/>
</bean>
In my project I have a Bean class named QuickStartConfiguration with methods getQueueUser and getQueuePassword.
Unfortunately, when the route gets instantiated, it seems that the username and password is not parsed:
The username "quickstartConfiguration?method=getQueueUsername" and/or password and/or payload token could not be authenticated by security domain ldap-security.)
What is weird is that however using the log component, I'm able to retrieve the actual value from the Bean:
<log id="async-queue-cred-log" message="----> Using username '${bean:quickstartConfiguration?method=getQueueUsername}' for the async queue"/>
Is there any limitation in the BasicDataSource class which prevents from evaluate the Bean expression?
Thanks!
I set up a jms component with spring like this way:
<bean id="jmsConnectionFactory"
class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616" />
</bean>
<bean id="cachedConnectionFactory"
class="org.springframework.jms.connection.CachingConnectionFactory">
<property name="targetConnectionFactory" ref="jmsConnectionFactory"/>
<property name="sessionCacheSize" value="10"/>
<property name="exceptionListener" ref="exceptionListener"/>
</bean>
<bean id="jmsConfig"
class="org.apache.camel.component.jms.JmsConfiguration">
<property name="connectionFactory" ref="cachedConnectionFactory"/>
<property name="concurrentConsumers" value="10"/>
</bean>
I notice that all these 4 beans have exceptionListener property. So I wonder what's the difference setting exceptionListener in different beans.
During my test, only setting in CachingConnectionFactory can work, it can go into my ExceptionListener, while in other cases, the exception will be logged somewhere else, but can't go into my code, the exception is like below.
WARN CachingConnectionFactory.onException(322) - Encountered a JMSException - resetting the underlying JMS Connection
javax.jms.JMSException: java.io.EOFException
at org.apache.activemq.util.JMSExceptionSupport.create(JMSExceptionSupport.java:54)
at org.apache.activemq.ActiveMQConnection.onAsyncException(ActiveMQConnection.java:1983)
at org.apache.activemq.ActiveMQConnection.onException(ActiveMQConnection.java:2002)
at org.apache.activemq.transport.TransportFilter.onException(TransportFilter.java:101)
at org.apache.activemq.transport.ResponseCorrelator.onException(ResponseCorrelator.java:126)
at org.apache.activemq.transport.TransportFilter.onException(TransportFilter.java:101)
at org.apache.activemq.transport.TransportFilter.onException(TransportFilter.java:101)
at org.apache.activemq.transport.WireFormatNegotiator.onException(WireFormatNegotiator.java:160)
at org.apache.activemq.transport.AbstractInactivityMonitor.onException(AbstractInactivityMonitor.java:314)
at org.apache.activemq.transport.TransportSupport.onException(TransportSupport.java:96)
at org.apache.activemq.transport.tcp.TcpTransport.run(TcpTransport.java:200)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.io.EOFException
at java.io.DataInputStream.readInt(DataInputStream.java:392)
at org.apache.activemq.openwire.OpenWireFormat.unmarshal(OpenWireFormat.java:275)
at org.apache.activemq.transport.tcp.TcpTransport.readCommand(TcpTransport.java:221)
at org.apache.activemq.transport.tcp.TcpTransport.doRun(TcpTransport.java:213)
at org.apache.activemq.transport.tcp.TcpTransport.run(TcpTransport.java:196)
... 1 more
I can't understand how this works, please give some explanation or hint.
org.springframework.jms.connection.CachingConnectionFactory.exceptionListener set the org.apache.activemq.ActiveMQConnectionFactory.exceptionListener , so this is the same.
org.apache.camel.component.jms.JmsConfiguration.exceptionListener set up the org.springframework.jms.listener.AbstractMessageListenerContainer.exceptionListener
Set the JMS ExceptionListener to notify in case of a JMSException
thrown by the registered message listener or the invocation infrastructure.
so it is nearly the same use of that listener at any level but it is better to set it at the org.apache.camel.component.jms.JmsConfiguration.exceptionListener level to be managed by the spring Container.
I'm trying to configure a cxf soap webservice with authorization and authentication to be deployed on Servicemix.
I configured the LDAP authentication module as follows:
<!-- Bean to allow the $[karaf.base] property to be correctly resolved -->
<ext:property-placeholder placeholder-prefix="$[" placeholder-suffix="]"/>
<jaas:config name="myRealm">
<jaas:module className="org.apache.karaf.jaas.modules.ldap.LDAPLoginModule" flags="required">
connection.url = ldap://srv-ldap:389
user.base.dn = ou=people,dc=intranet,dc=company,dc=com
user.filter = (uid=%u)
user.search.subtree = false
role.base.dn = ou=groups,dc=intranet,dc=company,dc=com
role.filter = (member:=uid=%u,ou=people,dc=intranet,dc=company,dc=com)
role.name.attribute = cn
role.search.subtree = true
authentication = simple
</jaas:module>
</jaas:config>
<service interface="org.apache.karaf.jaas.modules.BackingEngineFactory">
<bean class="org.apache.karaf.jaas.modules.properties.PropertiesBackingEngineFactory"/>
</service>
And here is the beans.xml file
<jaxws:endpoint id="myService"
implementor="com.myorg.services.impl.MyServiceWSImpl"
address="/myService">
<jaxws:inInterceptors>
<bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<constructor-arg>
<map>
<entry key="action" value="UsernameToken" />
<entry key="passwordType" value="PasswordText" />
</map>
</constructor-arg>
</bean>
<ref bean="authenticationInterceptor" />
<ref bean="authorizationInterceptor" />
</jaxws:inInterceptors>
<jaxws:properties>
<entry key="ws-security.validate.token" value="false" />
</jaxws:properties>
</jaxws:endpoint>
<bean id="authenticationInterceptor"
class="org.apache.cxf.interceptor.security.JAASLoginInterceptor">
<property name="contextName" value="myRealm" />
</bean>
<bean id="authorizationInterceptor"
class="org.apache.cxf.interceptor.security.SecureAnnotationsInterceptor">
<property name="securedObject" ref="securedBean"/>
</bean>
Finally, in my WebService implementation I annotated a method with #RolesAllowed.
#RolesAllowed("Role1")
public Department get(String name) throws IdMException {
return service.get(name);
}
The authentication interceptor is retrieving the user, authenticating it and retrieving the groups as RolePrincipal instances.
Then, in the authorization interceptor (SecureAnnotationsInterceptor), the method configuration is read, the expectedRoles are "Role1", but the SimpleAuthorizingInterceptor.isUserInRole method returns false.
I haven't found any example trying to do more or less the same and the few information I found was from the CXF documentation page http://cxf.apache.org/docs/security.html#Security-Authorization
I have to be missing something important, hope somebody could help me.
Thanks in advance and kind regards.
Your problem is because of Karaf's RolePricipal do not implements Group as CXF expected. Instead of it, it implements Pricipal so CXF thinks that 1st role name is a username. That is why "SimpleAuthorizingInterceptor.isUserInRole method returns false".
A solution is to wait for fixed versions of CXF (2.7.11 and 3.0.0).
If not possible to update to newer version, then an odd and temporary solution (simply workaround) is to add more than one role to a user in LDAP and to method.
You can find more about that bug here: CXF-5603
Have config (applicationContext-security.xml):
<authentication-manager alias="authenticationManager">
<authentication-provider>
<password-encoder hash="sha"/>
<jdbc-user-service data-source-ref="dataSource"/>
</authentication-provider>
</authentication-manager>
from other side have SQLs from my dataSource(it's JdbcDaoImpl):
...
public static final String DEF_USERS_BY_USERNAME_QUERY =
"select username,password,enabled " +
"from users " +
"where username = ?";
...
There is now word about sha in this code,so password selected from standard Spring Security users table not encoded.
Perhaps, I should provide some sha attribute for password column in my hibernate mapping config here:
<class name="model.UserDetails" table="users">
<id name="id">
<generator class="increment"/>
</id>
<property name="username" column="username"/>
<property name="password" column="password"/>
<property name="enabled" column="enabled"/>
<property name="mail" column="mail"/>
<property name="city" column="city"/>
<property name="confirmed" column="confirmed"/>
<property name="confirmationCode" column="confirmation_code"/>
<set name="authorities" cascade="all" inverse="true">
<key column="id" not-null="true"/>
<one-to-many class="model.Authority"/>
</set>
</class>
For now password saved to DB as is,but should be encoded.
How to friend applicationContext config and DB queries to be the same password encoding?
If you are choosing a hashing system yourself, rather than building an app using an existing database which already contains hashed passwords, then you should make sure your hashing algorithm also uses a salt. Don't just use a plain digest.
A good choice is bcrypt, which we now support directly in Spring Security 3.1 via the BCryptPasswordEncoder (implemented using jBCrypt). This automatically generates a salt and concatenates it with the hash value in a single String.
Some databases have built-in support for hashing (e.g. Postgres). Otherwise, you need to hash the password yourself before passing it to JDBC:
String password = "plaintextPassword";
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String hashedPassword = passwordEncoder.encode(password);
That's all you need to do to encode the passwords when you create a user.
For authentication, you would use something like:
<bean id="encoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<bean id="authProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="yourJdbcUserService" />
<property name="passwordEncoder" ref="encoder" />
</bean>
A little more explanation on the accepted answer. Hope it helps someone.
Hash the password yourself before putting it to database:
String password = "plaintextPassword";
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String hashedPassword = passwordEncoder.encode(password);
Add BCryptPasswordEncoder bean to your security-config.xml
Add passwordEncoder as a property to Authentication Provider class. Autowire it or provide setter and getter methods.
#AutoWired
private BCryptPasswordEncoder passwordEncoder;
Get the property while you authendicate user for login
<bean id="dbAuthenticationProvider" class="mypackage.auth.spring.DBAuthenticationProvider" >
<property name="dataSource" ref="routingDataSource"></property>
<property name="passwordEncoder" ref="encoder" />
<property name="passwordQuery"
value="select password as password from tbl where username=:username">
</property>
</bean>
And in the authenticating class match both passwords
new BCryptPasswordEncoder().matches(plainTextPasswdFromUserInput, hashedPasswdFromDb)
In a simple way can you do something like in applicationContext-security.xml
<authentication-manager alias="authenticationManager">
<authentication-provider>
<password-encoder ref="encoder"/>
<jdbc-user-service data-source-ref="dataSource"
users-by-username-query="
select username,password, enabled
from principal where username=?"
authorities-by-username-query="
select p.username, a.authority from principal p, authority a
where p.id = a.principal_id and p.username=?"
/>
</authentication-provider>
</authentication-manager>
<beans:bean id="encoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
In Java
public static String encodePasswordWithBCrypt(String plainPassword){
return new BCryptPasswordEncoder().encode(plainPassword);
}
Then test it
System.out.println(encodePasswordWithBCrypt("fsdfd"));
Using Spring Security 3.1, try this:
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="service">
<password-encoder hash="sha"/>
<jdbc-user-service data-source-ref="dataSource"/>
</authentication-provider>
</authentication-manager>
<beans:bean id="dataSource" ...>
...
</beans:bean>
<beans:bean id="service" class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
<beans:property name="dataSource" ref="dataSource"/>
...
</beans:bean>
What's new: authentication-provider points to service and service points to datasource.
Edit: In Java you will have to encode the password with something like this:
DigestUtils.sha(request.getParameter("password"));
Warn: Be careful! Do not mix SHA with MD5!
If you set the password-encoder of the authentication-provider as SHA, you need to encode in Java the same way to keep consistent. But if you enconde in Java as MD5, as the sample you found, do not forget to set the hash to "md5". DigestUtils also provides md5 encoder:
DigestUtils.md5(request.getParameter("password"));
Just a tip for doing it with annotations
#Configuration
#EnableWebSecurity
#PropertySource("classpath://configs.properties")
public class SecurityContextConfig extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("userDetailsService")
private UserDetailsService userDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(getPasswordEncoder());
}
#Bean(name = "passwordEncoder")
public PasswordEncoder getPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
The accepted answer is right.
I tested it with spring 3.1 and BCrypt encode algorithm.
When create a user.
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
userEntity.setPassword(passwordEncoder.encode(userEntity.getPassword()));
userDao.save(userEntity);
When the user login, Remember, use the plain password (not hashed). just like:
Authentication request = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());
Authentication result = authenticationManager.authenticate(request);
SecurityContextHolder.getContext().setAuthentication(result);
Here is security-config:
<bean id="encoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="userService" />
<property name="hideUserNotFoundExceptions" value="false" />
<property name="passwordEncoder" ref="encoder" />
</bean>
Hope it will help somebody!
with 3.1.x this mapping doesnt work for auth.
Working way is:
<beans:bean id='bCryptPasswordEncoder' class='org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder'></beans:bean>
<authentication-manager>
<authentication-provider user-service-ref="userDetailsService">
<password-encoder ref="bCryptPasswordEncoder"/>
</authentication-provider>
</authentication-manager>