Spring rabbit does not allow overriding defaults in caching connection factory configuration : release 1.1.4 - rabbitmq

I trying a spring amqp application to send JSON data as messages to a consumer (written in Ruby).
I get connection reset errors with any setting that I try
NOTE: I deleted the default guest user and added admin with the same privileges over default virtual host.
Here's the configuration:
<beans:bean id="cachingConnectionFactory"
class="org.springframework.amqp.rabbit.connection.CachingConnectionFactory">
<beans:property name="channelCacheSize" value="5" />
<beans:property name="username" value="admin" />
<beans:property name="password" value="admin" />
</beans:bean>
<rabbit:queue id='analytics.persistence.queue' name='analytics.persistence.queue' />
<rabbit:direct-exchange name="amq.direct">
<rabbit:bindings>
<rabbit:binding queue="analytics.persistence.queue">
</rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>
<rabbit:connection-factory id="cachingConnectionFactory" />
<rabbit:admin connection-factory="cachingConnectionFactory" />
<beans:bean id="rabbitTemplate"
class="org.springframework.amqp.rabbit.core.RabbitTemplate">
<beans:property name="connectionFactory" ref="cachingConnectionFactory" />
<beans:property name="exchange" value="amq.direct" />
<beans:property name="routingKey" value="analytics.persistence.queue"/>
</beans:bean>
<beans:bean id="analyticsMessageProducer"
class="hoodibaba.analytics.publish.AnalyticsPersistenceMessageProducer">
<beans:property name="rabbitTemplate" ref="rabbitTemplate" />
</beans:bean>
And in my producer class
public class AnalyticsPersistenceMessageProducer extends RabbitGatewaySupport implements
AnalyticsPersistenceMessageGateway {
private static final Logger logger = LoggerFactory.getLogger(AnalyticsPersistenceMessageProducer.class);
#Override
public void sendAnalyticsMessage(String jsonData) {
try {
getRabbitTemplate().convertAndSend("analytics.persistence.queue",jsonData);
} catch (AmqpException e) {
logger.error(e.getMessage());
logger.error("Exception Stacktrace",e);
logger.error("flushing JSON data to logs: " + jsonData);
}
}
}
I get a
com.rabbitmq.client.PossibleAuthenticationFailureException: Possibly
caused by authentication failure exception
the stack trace shows connection reset
Caused by: com.rabbitmq.client.PossibleAuthenticationFailureException: Possibly caused by authentication failure
at com.rabbitmq.client.impl.AMQConnection.start(AMQConnection.java:348)
at com.rabbitmq.client.ConnectionFactory.newConnection(ConnectionFactory.java:516)
at com.rabbitmq.client.ConnectionFactory.newConnection(ConnectionFactory.java:545)
at org.springframework.amqp.rabbit.connection.AbstractConnectionFactory.createBareConnection(AbstractConnectionFactory.java:160)
... 43 more
Caused by: com.rabbitmq.client.ShutdownSignalException: connection error; reason: java.net.SocketException: Connection reset
at com.rabbitmq.utility.ValueOrException.getValue(ValueOrException.java:67)
at com.rabbitmq.utility.BlockingValueOrException.uninterruptibleGetValue(BlockingValueOrException.java:33)
at com.rabbitmq.client.impl.AMQChannel$BlockingRpcContinuation.getReply(AMQChannel.java:343)
at com.rabbitmq.client.impl.AMQChannel.privateRpc(AMQChannel.java:216)
at com.rabbitmq.client.impl.AMQChannel.rpc(AMQChannel.java:202)
at com.rabbitmq.client.impl.AMQConnection.start(AMQConnection.java:340)
... 46 more
Caused by: java.net.SocketException: Connection reset
at java.net.SocketInputStream.read(SocketInputStream.java:189)
at java.net.SocketInputStream.read(SocketInputStream.java:121)
at java.io.BufferedInputStream.fill(BufferedInputStream.java:235)
at java.io.BufferedInputStream.read(BufferedInputStream.java:254)
at java.io.DataInputStream.readUnsignedByte(DataInputStream.java:288)
at com.rabbitmq.client.impl.Frame.readFrom(Frame.java:95)
at com.rabbitmq.client.impl.SocketFrameHandler.readFrame(SocketFrameHandler.java:131)
at com.rabbitmq.client.impl.AMQConnection$MainLoop.run(AMQConnection.java:508)
The message consumer is as follows
#!/usr/bin/env ruby
# encoding: UTF-8
require 'bunny'
conn = Bunny.new("amqp://admin:admin#localhost:5672")
conn.start
ch = conn.create_channel
puts 'channel created'
q = ch.queue("analytics.persistence.queue",:exclusive => false, :auto_delete => false)
x = ch.direct("amq.direct")
q.bind(x)
q.subscribe(:block => true, :ack => true) do |delivery_info, properties, payload|
puts "Received #{payload}, message properties are #{properties.inspect}"
end
ch.close
con.close
Update: it seems the updated username and password settings are not propagated, rabbit logs show my application still uses guest
=ERROR REPORT==== 19-Mar-2013::00:16:12 ===
closing AMQP connection <0.13815.0> (127.0.0.1:44736 -> 127.0.0.1:5672):
{handshake_error,starting,0,
{amqp_error,access_refused,
"PLAIN login refused: user 'guest' - invalid credentials",
'connection.start_ok'}}

(The OP edited the answer in the post. See Question with no answers, but issue solved in the comments (or extended in chat) )
The OP wrote:
The problem is in my configuration; caching connection factory is defined using both the beans namespace and rabbit namespcace, latter using the defaults. Changed to configuration to following and I do not get connection reset errors anymore
<rabbit:queue id='analytics.persistence.queue' name='analytics.persistence.queue' />
<rabbit:direct-exchange name="amq.direct">
<rabbit:bindings>
<rabbit:binding queue="analytics.persistence.queue">
</rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>
<rabbit:connection-factory id="cachingConnectionFactory"
username="analytics" password="analytics" channel-cache-size="5"
virtual-host="analytics" />
<rabbit:admin connection-factory="cachingConnectionFactory" />
<beans:bean id="rabbitTemplate"
class="org.springframework.amqp.rabbit.core.RabbitTemplate">
<beans:property name="connectionFactory" ref="cachingConnectionFactory" />
<beans:property name="exchange" value="amq.direct" />
<beans:property name="queue" value="analytics.persistence.queue" />
<beans:property name="routingKey" value="save.analytics"></beans:property>
</beans:bean>
<beans:bean id="analyticsMessageProducer"
class="hoodibaba.analytics.publish.AnalyticsPersistenceMessageProducer"
autowire="byName" />

Related

RabbitMQ listener stops listening messages when MessageListener throws exception first time

I am facing a unusual problem, after an exception is thrown during processing of first message, Spring Amqp MessageListener stops picking up any further messages.In my code I am not explicitly doing any resource locking. Can anyone please suggest what is the problem.Any help will be appreciated as I am falling short of ideas.
MQ configuraion:
<rabbit:admin id="rabbitAdmin" connection-factory="rabbitConnectionFactory" />
<rabbit:connection-factory
id="rabbitConnectionFactory"
host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"/>
<!--
Configure the rabbitTemplate helper class to simplify rabbitMQ
access (sending and receiving message).
The default Exchange is used here.
-->
<rabbit:template id="rabbitTemplate" connection-factory="rabbitConnectionFactory" message-converter="rabbitSimpleMessageConverter" retry-template="rabbitRetryTemplate" />
<bean id="rabbitRetryTemplate" class="org.springframework.retry.support.RetryTemplate">
<property name="backOffPolicy">
<bean class="org.springframework.retry.backoff.ExponentialBackOffPolicy">
<property name="initialInterval" value="500" />
<property name="multiplier" value="5" />
<property name="maxInterval" value="90000" />
</bean>
</property>
<property name="retryPolicy">
<bean class="org.springframework.retry.policy.SimpleRetryPolicy">
<property name="maxAttempts" value="5"/>
</bean>
</property>
</bean>
<bean id="rabbitRetryInterceptor" class="org.springframework.amqp.rabbit.config.StatefulRetryOperationsInterceptorFactoryBean">
<property name="messageRecoverer" ref="rejectAndDontRequeueRecoverer"/>
<property name="retryOperations" ref="rabbitRetryTemplate" />
</bean>
<!-- Consumers -->
<bean id="genericMessageConsumer" class="org.bla.GenericMessageConsumer" />
<rabbit:listener-container
connection-factory="rabbitConnectionFactory"
advice-chain="rabbitRetryInterceptor"
concurrency="${rabbitmq.concurrency}"
max-concurrency="${rabbitmq.maxconcurrency}"
acknowledge="auto" >
<rabbit:listener ref="genericMessageConsumer" queue-names="${rabbitmq.orion.genericNotificationdata.queueName}" />
</rabbit:listener-container>
MessageListener class
public class GenericMessageConsumer implements MessageListener{
....
#Override
public void onMessage(Message message) {
try{
...Some logic...
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
After exception is thrown while processing first message, The container remains up but the remaining messages are not processed until i restart the container. Also the first messages stays in unacknowledged state.
Tried checking other posts same as my issue:
1) RabbitMQ listener stops listening messages when MessageListener throws exception
but am unable to figure out the solution.

Query regarding Spring message-driven-channel-adapter

I am using Spring's message-driven-channel-adapter.
My component is consuming message from Tibco Topic and Publishing to RabbitMQ topic
So The message flow is as follows:
Tibco-> (subscribed by )Component (Published to)-> RabbitMQ
The service activator is shown below: as we see there is a input-channel and an output-channel. The bean storeAndForwardActivator will have the business logic (within the method createIssueOfInterestOratorRecord)
<int:service-activator input-channel="inboundOratorIssueOfInterestJmsInputChannel"
ref="storeAndForwardActivator" method="createIssueOfInterestOratorRecord"
output-channel="outboundIssueOfInterestRabbitmqOratorJmsOutputChannel" />
I also have a message=driven-channel-adapter. This adapter will be invoked before the service adapter is invoked.
<int-jms:message-driven-channel-adapter
id="oratorIssueOfInterestInboundChannel" channel="inboundOratorIssueOfInterestJmsInputChannel"
container="oratorIssueOfInterestmessageListenerContainer" />
i.e. specifically the container (shown below) will hold the Topic name to be used - this is the DefaultMessageListenerContainer
<bean id="oratorIssueOfInterestmessageListenerContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="oratorIssueOfInterestTibcoConnectionFactory" />
<property name="destination" ref="oratorTibcojmsDestination" />
<property name="sessionTransacted" value="true" />
<property name="maxConcurrentConsumers" value="1" />
<property name="concurrentConsumers" value="1" />
<property name="receiveTimeout" value="5000" />
<property name="recoveryInterval" value="60000" />
<property name="autoStartup" value="true" />
<property name="exposeListenerSession" value="false" />
<property name="subscriptionDurable" value="true" />
<property name="durableSubscriptionName" value="${topic.orator.durable-subscription-name}" />
<property name="messageSelector" value="${topic.orator.selector}" />
</bean>
This set up works perfectly fine. However in some cases my consumer/component receives a 'rogue' message. i.e. an empty payload or a message type of HashMap (instead of plain TextMessage) - when we get this - what I observe is - an exception is caught at the DefaultMessageListener level (i.e. I don't go as far as my business bean i.e. storeAndForwardActivator), because of this my component is not sending ACK back - and since this is a durable Topic - there is a build of messages at the Topic - which is undesirable.
Is there a way for me to ACK the message straight away irrespective of weather an exception is caught at the DefaultMessageListener level?
Or should I introduce an error handler at the DefaultMessageListener?
What's the best way to handle this, any suggestions?
regards
D
Update:
I tried adding a errorHandler to the org.springframework.jms.listener.DefaultMessageListenerContainer
as shown below
<bean id="oratorIssueOfInterestmessageListenerContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="oratorIssueOfInterestTibcoConnectionFactory" />
<property name="destination" ref="oratorTibcojmsDestination" />
<property name="sessionTransacted" value="true" />
<property name="maxConcurrentConsumers" value="1" />
<property name="concurrentConsumers" value="1" />
<property name="receiveTimeout" value="5000" />
<property name="recoveryInterval" value="60000" />
<property name="autoStartup" value="true" />
<property name="exposeListenerSession" value="false" />
<property name="subscriptionDurable" value="true" />
<property name="durableSubscriptionName" value="${topic.orator.durable-subscription-name}" />
<property name="messageSelector" value="${topic.orator.selector}" />
<property name="errorHandler" ref="myErrorHandler"/>
</bean>
myErrorHandler is a bean as shpwn below
<bean id="myErrorHandler"
class="com.igate.firds.icmf.activators.concentrator.MyErrorHandler" />
MyErroHandler implements ErrorHandler
#Service
public class MyErrorHandler implements ErrorHandler{
private static Log log = LogFactory.getLog(MyErrorHandler.class);
#Override
public void handleError(Throwable t) {
if (t instanceof MessageHandlingException) {
MessageHandlingException exception = (MessageHandlingException) t;
if (exception != null) {
org.springframework.messaging.Message<?> message = exception.getFailedMessage();
Object payloadObject = message.getPayload();
if (null != payloadObject) {
log.info("Payload is not null, type is: " + payloadObject.getClass());
}
}
} else {
log.info("Exception is not of type: MessageHandlingException ");
}
}
}
What I notice is that the exception is caught (when the subscriber consumes a rogue message). I keep on seeing this log in a loop
Exception is not of type: MessageHandlingException
Exception is not of type: MessageHandlingException
Exception is not of type: MessageHandlingException
i.e. since the transaction is not committed - the same message from durable topic is consumed again and again. My aim is to send an ACK back to the broker after consuming the message (irrespective of weather an exception is caught or not).
I will try the error-channel tomorrow.
regards
D
Add an error-channel to the message-driven adapter; the ErrorMessage will contain a MessagingException payload that has two fields; the cause (exception) and failedMessage.
If you use the default error-channel="errorChannel", the exception is logged.
If you want to do more than that you can configure your own error channel and add some flow to it.
EDIT:
Further to your comments below...
payload must not be null is not a stack trace; it's a message.
That said, payload must not be null looks like a Spring Integration message; it is probably thrown in the message listener adapter during message conversion, which is before we get to a point where the failure can go to the error-channel; such an exception will be thrown back to the container.
Turn on DEBUG logging and look for this log entry:
logger.debug("converted JMS Message [" + jmsMessage + "] to integration Message payload [" + result + "]");
Also, provide a FULL stack trace.
EDIT#2
So, I reproduced your issue by forcing the converted payload to null in a custom MessageConverter.
The DMLC error handler is called by the container after the transaction is rolled back so there's no way to stop the rollback.
We can add an option to the adapter to handle such errors differently but that will take some work.
In the meantime, a work-around would be to write a custom MessageConverter; something like the one in this Gist.
Then, your service will have to deal with handling the "Bad Message Received" payload.
You then provide the custom converter like this...
<jms:message-driven-channel-adapter id="jmsIn"
destination="requestQueue" acknowledge="transacted"
message-converter="converter"
channel="jmsInChannel" />
<beans:bean id="converter" class="foo.MyMessageConverter" />

Spring security 3.2: Does a custom UserDetails & UserDetailsService need a custom AuthenticationManager?

I'm working with spring security 3.2, JSF2 , Hibernate4.
I'have done 3/4 of the work :) but my authentication system doesn't work yet.
I have a UserService who implements UserDetailsService, a domain Class User who implements UserDetails.
THe login system never stop user to access secured pages, i tried user name and password who doesn't exist in my database...
Thanks for the help.
I have a loginBean who is trying to authenticate the user when he connects via login form :
public String login() {
try {
Authentication request = new UsernamePasswordAuthenticationToken(this.getUsername(), this.getPassword());
Authentication result = authenticationManager.authenticate(request);
SecurityContextHolder.getContext().setAuthentication(result);
} catch (AuthenticationException e) { e.printStackTrace();}
return "secured";
}
My spring security looks like this :
`<security:global-method-security jsr250-annotations="enabled" pre-post-annotations="enabled" secured-annotations="enabled" />
<security:http auto-config="true" use-expressions="true">
<security:intercept-url pattern="/Admin" access="isAuthenticated()" />
<security:form-login login-page="/login.xhtml" authentication-failure-url="/" > </security:form-login>
</security:http>
<!-- User Data Access Object -->
<beans:bean id="userDao" class="com.clb.genomic.lyon.dao.UserDaoImpl" >
<beans:property name="sessionFactory" ref="sessionFactory"></beans:property>
</beans:bean>
<!-- User Business Object -->
<beans:bean id="userBo" class="com.clb.genomic.lyon.bo.UserBoImpl" >
<beans:property name="userDao" ref="userDao" />
</beans:bean>
<beans:bean id="login" class="com.clb.genomic.lyon.beans.LoginBean" scope ="request">
<beans:property name="authenticationManager" ref="authenticationManager" />
</beans:bean>
<beans:bean id="standardPasswordEncoder" class="org.springframework.security.crypto.password.StandardPasswordEncoder"/>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider user-service-ref="userBo" >
<security:password-encoder ref="standardPasswordEncoder"/>
</security:authentication-provider>
</security:authentication-manager>`
This is the error who show up...
org.springframework.security.authentication.AuthenticationServiceException: 1
at org.springframework.security.authentication.dao.DaoAuthenticationProvider.retrieveUser(DaoAuthenticationProvider.java:109)
at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:132)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:156)
at com.clb.genomic.lyon.beans.LoginBean.login(LoginBean.java:47).....
Caused by: java.lang.ArrayIndexOutOfBoundsException: 1
at com.clb.genomic.lyon.dao.UserDaoImpl.loadUserByUsername(UserDaoImpl.java:59)
at com.clb.genomic.lyon.bo.UserBoImpl.loadUserByUsername(UserBoImpl.java:68)
at com.clb.genomic.lyon.bo.UserBoImpl$$FastClassByCGLIB$$9ea98abf.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204).....
The exception from stack trace shows you are getting ArrayIndexOutOfBoundsException and it seems that you are reading from an empty array.
You should also check what value is being passed to loadUserByUsername() method, and if that user exists.

Spring Security 3.1: Active Directory Authentication and local DB Authorization

I am using Spring Security 3.1 for Active Directory authentication and a local db for loading the authorities. I have seen similar examples but it is still not clear for me what exactly I should use. My current settings in spring-security.xml is:
<!-- LDAP server details -->
<security:authentication-manager>
<security:authentication-provider ref="ldapActiveDirectoryAuthProvider" />
</security:authentication-manager>
<beans:bean id="ldapActiveDirectoryAuthProvider" class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
<beans:constructor-arg value="${ldap.domain}" />
<beans:constructor-arg value="${ldap.url}" />
<beans:property name="useAuthenticationRequestCredentials" value="true" />
<beans:property name="convertSubErrorCodesToExceptions" value="true" />
</beans:bean>
I have a class let's call it: "BookStoreDbAuthPopulator.java". Inside this class, I am calling this method:
// Load additional authorities and create an Authentication object
final List<GrantedAuthority> authorities = loadRolesFromDatabaseHere();
What is not still clear for me: Which interface should "BookStoreDbAuthPopulator.java" implements in order to add the loaded authorities from db to the UserDetails? "UserDetailsContextMapper" or "GrantedAuthoritiesMapper" or "AuthenticationProvider"?
Based on this solution: Spring Security 3 Active Directory Authentication, Database Authorization
"BookStoreDbAuthPopulator.java" should implement "AuthenticationProvider". My doubt is if I should use "BookStoreDbAuthPopulator.java" as a property for "ldapActiveDirectoryAuthProvider" bean?
Many thanks in advance.
My final solution is "BookStoreDbAuthPopulator.java" implements "UserDetailsContextMapper".
public class BookStoreDbAuthPopulator implements UserDetailsContextMapper {
// populating roles assigned to the user from AUTHORITIES table in DB
private List<SimpleGrantedAuthority> loadRolesFromDatabase(String username) {
//"SELECT ROLE FROM AUTHORITIES WHERE LCASE(USERNAME) LIKE ?"
...
}
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
List<SimpleGrantedAuthority> allAuthorities = new ArrayList<SimpleGrantedAuthority>();
for (GrantedAuthority auth : authorities) {
if (auth != null && !auth.getAuthority().isEmpty()) {
allAuthorities.add((SimpleGrantedAuthority) auth);
}
}
// add additional roles from the database table
allAuthorities.addAll(loadRolesFromDatabase(username));
return new User(username, "", true, true, true, true, allAuthorities);
}
#Override
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
}
}
Then in spring-security.xml
<!-- AuthenticationManager: AuthenticationProvider, LDAP server details -->
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="ldapActiveDirectoryAuthProvider" />
</security:authentication-manager>
<beans:bean id="ldapActiveDirectoryAuthProvider" class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
<!-- the domain name (may be null or empty). If no domain name is configured, it is assumed that the username will always contain the domain name. -->
<beans:constructor-arg value="${ldap.domain}" />
<!-- an LDAP url (or multiple URLs) -->
<beans:constructor-arg value="${ldap.url}" />
<!-- Determines whether the supplied password will be used as the credentials in the successful authentication token. -->
<beans:property name="useAuthenticationRequestCredentials" value="true" />
<!-- by setting this property to true, when the authentication fails the error codes will also be used to control the exception raised. -->
<beans:property name="convertSubErrorCodesToExceptions" value="true" />
<!-- for customizing user authorities -->
<beans:property name="userDetailsContextMapper" ref="myUserDetailsContextMapper" />
</beans:bean>
<!-- Customizing UserDetail -->
<beans:bean id="myUserDetailsContextMapper" class="com.mybookstore.mywebcomp.w.BookStoreDbAuthPopulator">
</beans:bean>

How to populate LDAP authorities from Active Directory LDAP using Spring security?

We are using spring security to authenticate users from LDAP in our application. The authentication part is working properly but the authorization part is not working.
We are not able to retrieve the roles of the user from the LDAP.
From the book "Spring Security 3" by Peter Mularien
"This is because Active Directory stores group membership as attributes on
the LDAP entries of users themselves. Out of the box (as of the time of publishing),
Spring Security does not offer an LdapAuthoritiesPopulator that can be
configured to support the structure of a typical Active Directory LDAP tree."
Below is my spring-security configuration file.
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">
<http use-expressions="true" >
<intercept-url pattern="/resources/**" filters="none" />
<intercept-url pattern="/login" access="permitAll"/>
<intercept-url pattern="/**" access="isAuthenticated()" />
<form-login login-page="/login"
default-target-url="/home"
always-use-default-target="true"
authentication-failure-url="/login?login_error=1" />
<logout invalidate-session="true"
logout-success-url="/"
logout-url="/logout"/>
</http>
<authentication-manager alias="ldapAuthenticationManager">
<authentication-provider ref="ldapAuthenticationProvider"/>
</authentication-manager>
<beans:bean id="ldapAuthenticationProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
<beans:constructor-arg ref="ldapBindAuthenticator"/>
<beans:constructor-arg ref="ldapAuthoritiesPopulator"/>
<beans:property name="userDetailsContextMapper" ref="ldapUserDetailsContextMapper"/>
</beans:bean>
<beans:bean id="ldapServer" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
<!-- MS Active Directory -->
<beans:constructor-arg value="ldap://localhost:389/dc=myOrg,dc=net"/>
<beans:property name="userDn" value="admin"/>
<beans:property name="password" value="admin"/>
<beans:property name="baseEnvironmentProperties">
<beans:map>
<beans:entry key="java.naming.referral" value="follow" />
</beans:map>
</beans:property>
</beans:bean>
<beans:bean id="ldapBindAuthenticator" class="org.springframework.security.ldap.authentication.BindAuthenticator">
<beans:constructor-arg ref="ldapServer"/>
<beans:property name="userSearch" ref="ldapSearchBean"/>
</beans:bean>
<beans:bean id="ldapSearchBean" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
<!-- MS Active Directory -->
<!-- user-search-base; relative to base of configured context source -->
<beans:constructor-arg value="ou=Software OU"/>
<!-- user-search-filter -->
<beans:constructor-arg value="(sAMAccountName={0})"/>
<beans:constructor-arg ref="ldapServer"/>
</beans:bean>
<beans:bean id="ldapAuthoritiesPopulator" class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator">
<beans:constructor-arg ref="ldapServer" />
<beans:constructor-arg value="" />
<beans:property name="groupSearchFilter" value="(sAMAccountName={0})"/>
<beans:property name="groupRoleAttribute" value="memberOf" />
<beans:property name="rolePrefix" value=""/>
<beans:property name="searchSubtree" value="true"/>
<beans:property name="convertToUpperCase" value="false"/>
<beans:property name="ignorePartialResultException" value="true"/>
</beans:bean>
<beans:bean class="org.springframework.security.ldap.userdetails.InetOrgPersonContextMapper" id="ldapUserDetailsContextMapper"/>
</beans:beans>
Please help.
You might want to take a look here: https://jira.springsource.org/browse/SEC-876. Although this code contribution was declined, with a reasonable answer, it might give you hints.
We use the following config:
Spring XML
<bean id="ldapUserService" class="MyUserDetailService">
<constructor-arg ref="ldapUserSearch"/>
<constructor-arg ref="ldapAuthoritiesPopulator"/>
</bean>
<bean id="ldapUserSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
<constructor-arg value="OU=FOO-Accounts,OU=FOO,OU=OU-GLOBAL"/> <!-- user search base, RELATIVE TO SERVER CONTEXT (URL & base of configured LDAP server)! -->
<constructor-arg value="(sAMAccountName={0})"/> <!-- user search filter -->
<constructor-arg ref="ldapServer"/>
</bean>
<bean id="ldapAuthoritiesPopulator" class="MyLdapAuthoritiesPopulator">
<constructor-arg ref="ldapServer" />
<constructor-arg value="=OU=SomeFooBar,OU=FOO-Global-Security,OU=FOO-Groups,OU=FOO,OU=OU-GLOBAL" /> <!-- group search base, RELATIVE TO SERVER CONTEXT (URL & base of configured LDAP server)! -->
<constructor-arg ref="roleMappings"/>
<property name="groupRoleAttribute" value="cn" />
<property name="groupSearchFilter" value="(member={0})" />
</bean>
Populator
There's a lot of proprietary code I cannot share because our customer has extra information in the AD we need to extract. I removed that as its of no concern for the question. Hence, this code won't compile.
public class MyLdapAuthoritiesPopulator extends DefaultLdapAuthoritiesPopulator {
/**
* Prefix assigned by Spring Security to each group/role from LDAP.
*/
public static final String AUTHORITY_ROLE_PREFIX = "ROLE_";
private Properties roleMappings;
private Properties invertedRoleMappings;
/**
*
* #param contextSource supplies the contexts used to search for user roles.
* #param groupSearchBase if this is an empty string the search will be performed from the root DN
* of the context factory. If null, no search will be performed.
* #param roleMappings maps logical (internal) role names to names as delivered by LDAP
*/
#SuppressWarnings("deprecation")
public MyLdapAuthoritiesPopulator(final ContextSource contextSource,
final String groupSearchBase,
final Properties roleMappings) {
super(contextSource, groupSearchBase);
setConvertToUpperCase(false);
setRolePrefix("");
this.roleMappings = roleMappings;
this.invertedRoleMappings = invertRoleMappings();
logger.info("Processing LDAP roles based on the following mapping: {}.", roleMappings);
}
.....
#Override
public Set<GrantedAuthority> getGroupMembershipRoles(final String userDn, final String username) {
final Set<GrantedAuthority> effectiveGroupMembershipRoles = super.getGroupMembershipRoles(
userDn, username);
return mapEffectiveRolesToApplicationRoles(effectiveGroupMembershipRoles);
}
/**
* Maps effective LDAP roles such as 'foo_boston_dispatcher' or 'foo_boston_readonly' to
* FOO internal roles. The internal role (i.e. the {#link GrantedAuthority}) is a combination
* of the 'ROLE_' prefix and a {#link Role} enum value. .........
*/
Set<GrantedAuthority> mapEffectiveRolesToApplicationRoles(final Set<GrantedAuthority> effectiveGroupMembershipRoles) {
logger.info("Processing effective roles from LDAP: {}.", effectiveGroupMembershipRoles);
final Set<GrantedAuthority> internalRoles = new HashSet<GrantedAuthority>();
final List<String> effectiveRoleNames = extractRoleNamesFrom(effectiveGroupMembershipRoles);
final List<String> unmappedGroupMembershipRoles = new ArrayList<String>();
......
// in a method invoked here we do something like internalRoles.add(new GrantedAuthority(AUTHORITY_ROLE_PREFIX + role));
......
logger.info("Created internal roles {}.", internalRoles);
logger.trace(
"The following group membership roles were not mapped to an internal equivalent: {}",
unmappedGroupMembershipRoles);
return internalRoles;
}
......
private List<String> extractRoleNamesFrom(final Collection<GrantedAuthority> authorities) {
final List<String> authorityNames = new ArrayList<String>(authorities.size());
for (GrantedAuthority authority : authorities) {
authorityNames.add(authority.getAuthority());
}
return authorityNames;
}
}