I want to cache LDAP user data locally to allow faster queries. Do the Spring LDAP offers such a functionality? How can I do this?
I am using Spring Security 3.1 and Spring LDAP 1.3.1 for authentication and authorization. It would be nice to have a cache for LDAP using built-in mechanism if exists..
Spring LDAP configuration:
applicationContext-ldap.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee.xsd
">
<!-- Ldap -->
<jee:jndi-lookup id="ldapUrl" jndi-name="appName/ldapUrl" expected-type="java.lang.String" />
<jee:jndi-lookup id="ldapUser" jndi-name="appName/ldapUser" expected-type="java.lang.String" />
<jee:jndi-lookup id="ldapPassword" jndi-name="appName/ldapPassword" expected-type="java.lang.String" />
<!-- for authentication and search purpose -->
<bean id="ldapContextSource" class="org.springframework.ldap.core.support.LdapContextSource">
<property name="url" ref="ldapUrl" />
<property name="userDn" ref="ldapUser" />
<property name="password" ref="ldapPassword" />
<property name="pooled" value="true" />
</bean>
<bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate">
<property name="contextSource" ref="ldapContextSource" />
</bean>
<!-- for pagination search purpose -->
<bean id="dirContext" factory-bean="ldapContextSource" factory-method="getReadOnlyContext" scope="session"/>
<bean id="singleLdapContextSource" class="org.springframework.ldap.core.support.SingleContextSource" scope="session">
<constructor-arg ref="dirContext"/>
</bean>
<bean id="singleLdapTemplate" class="org.springframework.ldap.core.LdapTemplate" scope="session">
<property name="contextSource" ref="singleLdapContextSource" />
</bean>
</beans>
Spring Security configuration:
spring-security.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<!-- This is where we configure Spring-Security -->
<security:http
auto-config="true"
use-expressions="true"
access-denied-page="/auth/denied"
>
<security:intercept-url pattern="/login" access="permitAll"/>
<security:intercept-url pattern="/app/admin" access="permitAll"/>
<security:intercept-url pattern="/app/common" access="hasRole('User')"/>
<security:intercept-url pattern="/viol/home" access="permitAll"/>
<security:intercept-url pattern="/app/users" access="permitAll"/>
<security:intercept-url pattern="/admin/edit/*" access="hasRole('Administrator')"/>
<security:form-login
login-page="/auth/login"
authentication-failure-url="/auth/loginFailure"
default-target-url="/auth/authorize"/>
<security:logout
invalidate-session="true"
logout-success-url="/auth/login"
logout-url="/logout"/>
</security:http>
<security:authentication-manager>
<security:ldap-authentication-provider
server-ref="ldapContextSource"
user-search-filter="(sAMAccountName={0})"
user-search-base="dc=myDomain,dc=com"
/>
</security:authentication-manager>
</beans>
Thank you very much for your help!
If you configure EhCacheBasedUserCache and use ldap-user-service then you can use cache as:
<authentication-manager>
<authentication-provider>
<ldap-user-service
user-search-filter="(sAMAccountName={0})" user-search-base="dc=myDomain,dc=com" cache-ref="userCache" />
</authentication-provider>
</authentication-manager>
I don't think Spring offers client side LDAP caching out of the box, as caching LDAP query results on the client would pose a security risk. The cache will certainly hold stale data at some point, which is not a huge problem if it's e.g. the email/home address of the user, but much worse when it comes to e.g. role assignments and other authentication/authorization related data. You will be much better off by scaling up the server side, so that it's able to handle the load.
That's being said, introducing caching is pretty easy since Spring 3.1, because it provides excellent support for it. In your case it would be enough to use a custom LdapContextSource like the following:
public class CachingLdapContextSource extends AbstractContextSource {
#Override
protected DirContext getDirContextInstance(Hashtable environment)
throws NamingException
{
InitialLdapContext context = new InitialLdapContext(environment, null);
return new CachingDirContextWrapper(context);
}
}
The wrapper class simply delegates all DirContext methods to the underlying implementation and decorates methods to be cached with #Cacheable.
class CachingDirContextWrapper implements DirContext {
private final DirContext delegate;
CachingDirContextWrapper(DirContext delegate) {
this.delegate = delegate;
}
#Override
#Cacheable(value = "search")
public NamingEnumeration<SearchResult> search(...)
{
return delegate.search(name, matchingAttributes, attributesToReturn);
}
...
}
Refer to the official documentation, and this tutorial on details about how to configure a cache storage to be used by Spring.
But once again, you'd better not do this, I think.
Related
I'm trying to integrate two simple Spring Web Applications with Spring Integration, and I want to propagate the Spring SecurityContext between the two applications, but I've not yet found a working solution.
The two applications are very similar between each other, there are only a few differences in the configuration: one of them has to be the "caller", the other one has to be the "receiver".
In order to getting the expected result, I'm using Spring Security and I've configured both applications with the following Spring Security configuration:
<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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.1.xsd">
<http auto-config="true">
<intercept-url pattern="/" access="permitAll" />
<intercept-url pattern="/service/**" access="permitAll" />
<intercept-url pattern="/home" access="permitAll" />
<intercept-url pattern="/admin**" access="hasRole('ADMIN')" />
<intercept-url pattern="/dba**" access="hasRole('ADMIN') and hasRole('DBA')" />
<form-login authentication-failure-url="/accessDenied" />
</http>
<authentication-manager id="authenticationManager">
<authentication-provider>
<user-service>
<user name="user" password="user" authorities="ROLE_USER" />
<user name="admin" password="admin" authorities="ROLE_ADMIN" />
<user name="dba" password="dba" authorities="ROLE_ADMIN,ROLE_DBA" />
</user-service>
</authentication-provider>
</authentication-manager>
<beans:bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
<beans:constructor-arg>
<beans:list>
<beans:bean class="org.springframework.security.access.vote.RoleVoter" />
<beans:bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
</beans:list>
</beans:constructor-arg>
</beans:bean>
</beans:beans>
This is working: if I go to the url http://localhost:8080/caller/login, the login process is correctly managed by the Spring Security layer.
So I'm trying to configure the Spring Integration module in order to "integrate" the two applications; my idea (wrong?) is to use an http:outbound-gateway from the "caller", that invoke the "receiver" at a specific URL.
On the other side, there is an http:inbound-gateway that manage the requests.
Here is the configuration of the "caller" side:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-http="http://www.springframework.org/schema/integration/http"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:int-security="http://www.springframework.org/schema/integration/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-4.3.xsd
http://www.springframework.org/schema/integration/http http://www.springframework.org/schema/integration/http/spring-integration-http-4.3.xsd
http://www.springframework.org/schema/integration/security http://www.springframework.org/schema/integration/security/spring-integration-security-4.2.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.3.xsd">
<int:channel id="requestChannel">
<int:dispatcher task-executor="executor"/>
</int:channel>
<int:channel-interceptor ref="requestChannelInterceptor" pattern="requestChannel">
</int:channel-interceptor>
<task:executor id="executor" pool-size="5"/>
<bean id="requestChannelInterceptor" class="org.springframework.integration.security.channel.SecurityContextPropagationChannelInterceptor"></bean>
<bean id="requestChannelBean" class="test.spring.webapp.client.MessagingChannel">
<property name="requestChannel" ref="requestChannel"></property>
</bean>
<int-http:outbound-gateway id="gateway" request-channel="requestChannel"
encode-uri="true" url="http://localhost:8080/receiver/service/{request}"
http-method="GET" >
<int-http:uri-variable name="request" expression="payload"/>
</int-http:outbound-gateway>
<int-security:secured-channels access-decision-manager="accessDecisionManager">
<int-security:access-policy pattern="requestChannel" send-access="ROLE_ADMIN,ROLE_USER,ROLE_DBA,ROLE_ANONYMOUS"/>
</int-security:secured-channels>
</beans>
Here is the configuration of the "receiver" side instead:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-http="http://www.springframework.org/schema/integration/http"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:int-security="http://www.springframework.org/schema/integration/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-4.3.xsd
http://www.springframework.org/schema/integration/http http://www.springframework.org/schema/integration/http/spring-integration-http-4.3.xsd
http://www.springframework.org/schema/integration/security http://www.springframework.org/schema/integration/security/spring-integration-security-4.2.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.3.xsd">
<int:channel id="requestChannel">
<int:dispatcher task-executor="executor"/>
</int:channel>
<int:channel-interceptor ref="requestChannelInterceptor" pattern="requestChannel">
</int:channel-interceptor>
<task:executor id="executor" pool-size="5"/>
<bean id="requestChannelInterceptor" class="org.springframework.integration.security.channel.SecurityContextPropagationChannelInterceptor"></bean>
<int-http:inbound-gateway id="gateway" request-channel="requestChannel"
path="/service/**"
supported-methods="GET">
</int-http:inbound-gateway>
<int-security:secured-channels access-decision-manager="accessDecisionManager">
<int-security:access-policy pattern="requestChannel" send-access="ROLE_ADMIN,ROLE_USER,ROLE_DBA,ROLE_ANONYMOUS"/>
</int-security:secured-channels>
<bean id="channelService"
class="test.spring.webapp.server.ChannelService"/>
<int:service-activator id="channelServiceActivator"
ref="channelService"
input-channel="requestChannel"
method="manage"/>
</beans>
As you can see, I'm using the component SecurityContextPropagationChannelInterceptor of Spring Integration, that is intended to make the "propagation" of the security context.
I'm also using the int-security:secured-channels in the configuration, as illustrated in the documentation.
But this configuration doesn't work: when I call the URL http://localhost:8080/caller/service/hello (logged or not logged is the same), I'm getting the following Exception:
org.springframework.messaging.MessageHandlingException: HTTP request execution failed for URI [http://localhost:8080/receiver/service/hello];
nested exception is org.springframework.web.client.HttpServerErrorException: 500 Internal Server Error
Furthermore, the class org.springframework.integration.channel.interceptor.ThreadStatePropagationChannelInterceptor has the following method:
#Override
#SuppressWarnings("unchecked")
public final Message<?> postReceive(Message<?> message, MessageChannel channel) {
if (message instanceof MessageWithThreadState) {
MessageWithThreadState<S> messageWithThreadState = (MessageWithThreadState<S>) message;
Message<?> messageToHandle = messageWithThreadState.message;
populatePropagatedContext(messageWithThreadState.state, messageToHandle, channel);
return messageToHandle;
}
return message;
}
The message instance contains the logged Principal.
Debugging this method, I noticed that the logged Principal (for example "admin") is obtained only on the "caller" side, not in the "receiver" side (where there is always the "anonymousUser"): why the propagation of the SecurityContext is not working?
I believe that something is totally wrong or missing in my configuration...
It can't be propagated to the another process (application) by its premise.
See the implementation of SecurityContextPropagationChannelInterceptor. It is based on ThreadStatePropagationChannelInterceptor. One more time Thread State, not one process to another via network.
Since there is no one common solution for inter-process SecurityContext transfer, there is no anything as out-of-the-box.
As I answered you in another your question (Spring Integration: the SecurityContext propagation), you should transfer credential via HTTP only as headers. I even think that much better would be to do that using standard Apache HTTPClient CredentialProvider (or BasicAuthorizationInterceptor from Spring Framework 4.3.1) approach to send request with the Basic Authentication header. Really to achieve security via network with Base64 encoding for credentials.
On the receiver side your should do something similar what is in the https://github.com/spring-projects/spring-security/blob/master/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java#L224:
byte[] base64Token = header.substring(6).getBytes("UTF-8");
byte[] decoded;
try {
decoded = Base64.decode(base64Token);
}
catch (IllegalArgumentException e) {
throw new BadCredentialsException(
"Failed to decode basic authentication token");
}
String token = new String(decoded, getCredentialsCharset(request));
int delim = token.indexOf(":");
if (delim == -1) {
throw new BadCredentialsException("Invalid basic authentication token");
}
return new String[] { token.substring(0, delim), token.substring(delim + 1) };
First of all I kept the listener in web.xml
<listener>
<listener-class>
org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>
Then my springSecurity.xml goes like
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:security="http://www.springframework.org/schema/security"
xmlns:context="http://www.springframework.org/schema/context"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.2.xsd">
<security:http auto-config="true" use-expressions="true">
<security:intercept-url pattern="/*" access="permitAll" />
<security:session-management invalid-session-url="/" session-fixation-protection="newSession">
<security:concurrency-control max-sessions="1" error-if-maximum-exceeded="true" session-registry-alias="sessionRegistry"/>
</security:session-management>
<!-- access denied page -->
<security:access-denied-handler error-page="/loginerror" />
<security:form-login
login-page="/login?login_error=1"
default-target-url="/employee/listEmployee"
authentication-failure-url="/login/error"
/>
<security:logout invalidate-session="true" logout-success-url="/login" delete-cookies="JSESSIONID" />
<!-- enable csrf protection -->
<!-- <csrf/>-->
</security:http>
<!-- Select users and user_roles from database -->
<security:authentication-manager>
<security:authentication-provider ref="authenticationProvider"></security:authentication-provider>
</security:authentication-manager>
<bean id="authenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService">
<bean id="userAuthenticationService" class="com.elitenet.los.security.UserDetailsServiceImpl" />
</property>
<property name="passwordEncoder">
<bean class="org.springframework.security.authentication.encoding.ShaPasswordEncoder" />
</property>
</bean>
The controller goes like:I need the list of userNames which are logged in. But the sessionRegistry isn't working.
#Autowired
#Qualifier("sessionRegistry")
private SessionRegistry sessionRegistry;
#RequestMapping(value = "/showUserStatus",method = RequestMethod.GET)
public ModelAndView showUserStatus() {
List<String> usersNamesList = new ArrayList<String>();
List<User> userList = new ArrayList<User>();
try {
List<Object> principals =sessionRegistry.getAllPrincipals();//the principals here is empty
for (Object principal: principals) {
//import org.springframework.security.core.userdetails for User class
//User is a built in class of spring security core
if (principal instanceof User) {
getLog().info(((User) principal).getUserName());
getLog().info("going to list userNameList");
usersNamesList.add(((User) principal).getUserName());
}
}
getLog().info("going to list user");
userList = getUserService().getList();
} catch (Exception er) {
getLog().error("error while listing userList" + er);
}
return new ModelAndView("/user/showUserStatus", "userList", userList);
}
Can anyone help me what am I doing wrong
Please try mentioning in xml file
<bean id="sessionRegistry"
class="org.springframework.security.core.session.SessionRegistryImpl" />
#Controller class
Try injecting like below
#Resource(name="sessionRegistry")
private SessionRegistryImpl sessionRegistry;
I think you are almost there. The only thing you've probably missed is the use of session-registry-alias. By using that attribute on the concurrency-control element you expose the session registry, so that it can be injected to your own beans.
Now you have a reference to the session registry that will be populated by the ConcurrentSessionControlStrategy which is set up implicitly by the above configuration. To use it you would just inject it to your bean as normal:
<security:session-management>
<security:concurrency-control max-sessions="10" session-registry-ref="sessionRegistry"/>
</security:session-management>
<bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl"/>
or something like below
<bean id="sas" class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy"
p:maximumSessions="1" >
<constructor-arg name="sessionRegistry" ref="sessionRegistry" />
</bean>
I am a newbie to spring beans. I'm referring books and blogs. In some the context configuration is given as <beans:bean> and in some just <beans>. What is the difference? Should we give the XML namespace in the context file? Will it refer the acutal site at the time of application deployment?
It doesn't matter as far as Spring is concerned - the XML must be valid so that Spring can understand it, that's it. It is up to you which format you choose. Normally you use default namespace to avoid typing too much (all examples based on Appendix C. XML Schema-based configuration):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="..."/>
</beans>
xmlns="..." attribute defines default namespace (the one used if you don't specify any namespace at all, like <beans/>. This is fine as long as you use only single beans namespace and occasionally few declarations from other namespaces:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd">
<bean id="..." class="...">
<property name="isolation">
<util:constant static-field="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
</property>
</bean>
</beans>
But sometimes you'll notice you are using way more nodes from different namespaces compared to default beans namespace. A good example is Spring Security configuration file:
<beans xmlns:security="http://www.springframework.org/schema/security"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.3.xsd">
<security:http auto-config='true'>
<security:intercept-url pattern="/login.jsp*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<security:intercept-url pattern="/**" access="ROLE_USER" />
<security:form-login login-page='/login.jsp'/>
<security:session-management>
<security:concurrency-control max-sessions="1" />
</security:session-management>
<security:openid-login>
<security:attribute-exchange>
<security:openid-attribute name="email" type="http://axschema.org/contact/email" required="true" />
<security:openid-attribute name="name" type="http://axschema.org/namePerson" />
</security:attribute-exchange>
</security:openid-login>
</security:http>
<security:authentication-manager>
<security:authentication-provider user-service-ref='myUserDetailsService'/>
</security:authentication-manager>
<bean id="myUserDetailsService" class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
You see how clumsy it is because beans default namespace is used so rarely but the whole file must be cluttered with security: prefix? What about this (notice how xmlns namespace declarations have changed):
<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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.3.xsd">
<http auto-config='true'>
<intercept-url pattern="/login.jsp*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<intercept-url pattern="/**" access="ROLE_USER" />
<form-login login-page='/login.jsp'/>
<session-management>
<concurrency-control max-sessions="1" />
</session-management>
<openid-login>
<attribute-exchange>
<openid-attribute name="email" type="http://axschema.org/contact/email" required="true" />
<openid-attribute name="name" type="http://axschema.org/namePerson" />
</attribute-exchange>
</openid-login>
</http>
<authentication-manager>
<authentication-provider user-service-ref='myUserDetailsService'/>
</authentication-manager>
<beans:bean id="myUserDetailsService" class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
<beans:property name="dataSource" ref="dataSource"/>
</beans:bean>
</beans:beans>
These two configuration files are semantically equivalent (it is just a different way to encode the same information). But the latter is much more readable. Just use default namespace whichever namespace is used the most.
It depends on XML namespace configuration and is not Spring feature, makes no difference for the code, make some difference to coder, and in fact but effect of defining xmlns attribute on root xml element. Read more about <beans:bean> in this chapter of Spring Security (which isn't in fact the same as Spring Framework, but is very common and uses its own namespace in XML files). You can write:
<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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<global-method-security pre-post-annotations="enabled">
<expression-handler ref="expressionHandler"/>
</global-method-security>
<beans:bean id="expressionHandler" class=
"org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
<beans:property name="permissionEvaluator" ref="myPermissionEvaluator"/>
</beans:bean>
</beans:beans>
which is equivalent to:
<beans xmlns="http://www.springframework.org/schema/beans" <!-- see the difference here? -->
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<security:global-method-security pre-post-annotations="enabled">
<security:expression-handler ref="expressionHandler"/>
</security:global-method-security>
<bean id="expressionHandler" class=
"org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
<property name="permissionEvaluator" ref="myPermissionEvaluator"/>
</bean>
</beans>
Personally I always use beans as main namespace, but it's matter of habit.
I want to add the remember-me into my login page, by reading here , it needs a UserDetailsService. But my UserDetailsService is not getting called, can anyone point where i am wrong? Thanks.
The spring-security.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<!-- configure Spring-Security
auto-config is false.
use-expressions is true: see http://static.springsource.org/spring-security/site/docs/3.1.x/reference/el-access.html
access-denied-page: which page is redirected when login is denied
entry-point-ref: This attribute allows this behaviour to be overridden by defining a customized
AuthenticationEntryPoint bean which will start the authentication process
-->
<security:http auto-config="false" use-expressions="true" entry-point-ref="authenticationEntryPoint" >
<!-- define how to handle the url /auth/login, primitAll is used since we defined use-expressions=true -->
<security:intercept-url pattern="/login" access="permitAll"/>
<security:intercept-url pattern="/search" access="hasRole('ROLE_USER')"/>
<!-- The logout element adds support for logging out by navigating to a particular URL.
The default logout URL is /j_spring_security_logout,
but you can set it to something else using the logout-url attribute -->
<security:logout
invalidate-session="true"
logout-success-url="/login" />
<security:custom-filter ref="blacklistFilter" before="FILTER_SECURITY_INTERCEPTOR"/>
<security:custom-filter ref="authenticationFilter" position="FORM_LOGIN_FILTER"/>
</security:http>
<!-- Custom filter to deny unwanted users even though registered -->
<bean id="blacklistFilter" class="com.myapp.filter.BlacklistFilter" />
<!-- Custom filter for username and password. we need to create another 4 beans -->
<bean id="authenticationFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"
p:rememberMeServices-ref="rememberMeServices"
p:authenticationManager-ref="customAuthenticationManager"
p:authenticationFailureHandler-ref="customAuthenticationFailureHandler"
p:authenticationSuccessHandler-ref="customAuthenticationSuccessHandler" />
<!-- Bean 1: Custom authentication manager. -->
<bean id="customAuthenticationManager" class="com.myapp.manager.CustomAuthenticationManager" />
<!-- bean 2: set the default failure url here -->
<bean id="customAuthenticationFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"
p:defaultFailureUrl="/login?error=true" />
<!-- bean 3: set the default target url here -->
<bean id="customAuthenticationSuccessHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler"
p:defaultTargetUrl="/search" />
<!-- bean 4: remember me -->
<bean id="rememberMeServices"
class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
<property name="userDetailsService" ref="userDetailsService"/>
<property name="key" value="myapp"/>
</bean>
<bean id="userDetailsService" class="com.myapp.service.UserDetailsServiceImpl" />
<bean id="authenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"
p:loginFormUrl="/login"/>
<security:authentication-manager/></beans>
Thanks, Ralph
I added the filter, but UserDetailsServiceImpl is still not called, there is a stop point.
public UserDetails loadUserByUsername(String email)
throws UsernameNotFoundException {
logger.info("User details service is called");
return null;
}
and now the configuration is :
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<!-- configure Spring-Security
auto-config is false.
use-expressions is true: see http://static.springsource.org/spring-security/site/docs/3.1.x/reference/el-access.html
access-denied-page: which page is redirected when login is denied
entry-point-ref: This attribute allows this behaviour to be overridden by defining a customized
AuthenticationEntryPoint bean which will start the authentication process
-->
<security:http auto-config="false" use-expressions="true" entry-point-ref="authenticationEntryPoint" >
<!-- define how to handle the url /auth/login, primitAll is used since we defined use-expressions=true -->
<security:intercept-url pattern="/login" access="permitAll"/>
<security:intercept-url pattern="/search" access="hasRole('ROLE_USER')"/>
<!-- The logout element adds support for logging out by navigating to a particular URL.
The default logout URL is /j_spring_security_logout,
but you can set it to something else using the logout-url attribute -->
<security:logout
invalidate-session="true"
logout-success-url="/login" />
<security:custom-filter ref="blacklistFilter" before="FILTER_SECURITY_INTERCEPTOR"/>
<security:custom-filter ref="authenticationFilter" position="FORM_LOGIN_FILTER"/>
<security:custom-filter ref="rememberMeFilter" position="REMEMBER_ME_FILTER"/>
</security:http>
<!-- Custom filter to deny unwanted users even though registered -->
<bean id="blacklistFilter" class="com.myapp.filter.BlacklistFilter" />
<!-- Custom filter for username and password. we need to create another 4 beans -->
<bean id="authenticationFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"
p:rememberMeServices-ref="rememberMeServices"
p:authenticationManager-ref="customAuthenticationManager"
p:authenticationFailureHandler-ref="customAuthenticationFailureHandler"
p:authenticationSuccessHandler-ref="customAuthenticationSuccessHandler" />
<!-- Bean 1: Custom authentication manager. -->
<bean id="customAuthenticationManager" class="com.myapp.manager.CustomAuthenticationManager" />
<!-- bean 2: set the default failure url here -->
<bean id="customAuthenticationFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"
p:defaultFailureUrl="/login?error=true" />
<!-- bean 3: set the default target url here -->
<bean id="customAuthenticationSuccessHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler"
p:defaultTargetUrl="/search" />
<!-- bean 4: remember me -->
<bean id="rememberMeServices"
class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
<property name="userDetailsService" ref="userDetailsService"/>
<property name="key" value="myapp"/>
</bean>
<bean id="userDetailsService" class="com.myapp.service.UserDetailsServiceImpl" />
<bean id="rememberMeFilter" class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
<property name="rememberMeServices" ref="rememberMeServices"/>
<property name="authenticationManager" ref="customAuthenticationManager" />
</bean>
<bean id="authenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"
p:loginFormUrl="/login"/>
<security:authentication-manager alias="theAuthenticationManager"/></beans>
It looks like you forgot to add the RememberMeAuthenticationFilter. -- Have a look at the example in the doc you mentioned, you will see what I mean.
Try to reduce your configuration first, to the very default configuration like in http://www.i-develop.be/blog/2010/02/04/spring-security-remember-me/
Trying to make some experiments with Spring MVC and Spring Security:
#Controller
#RequestMapping("/auth")
public class AuthController {
#Autowired
// #Qualifier("userDetailsService") - tried adding this
private MyUserDetailsService userDetailsService;
...
}
// #Scope("singleton") - tried adding this
#Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
...
}
Complete context.xml that I have:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:security="http://www.springframework.org/schema/security"
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-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.xsd">
<!-- ORIGINAL springmvc-servlet.xml -->
<mvc:annotation-driven />
<mvc:resources mapping="/static/**" location="/static/" />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/jsp/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />
<context:annotation-config />
<context:component-scan base-package="com.xxxxxxxxx" />
<!-- end ORIGINAL springmvc-servlet.xml -->
<!-- FROM springmvc-security.xml -->
<security:global-method-security secured-annotations="enabled">
</security:global-method-security>
<security:http auto-config="true" access-denied-page="/auth/denied">
<security:intercept-url pattern="/admin/*" access="ROLE_ADMIN"/>
<security:intercept-url pattern="/user/*" access="ROLE_USER"/>
<security:intercept-url pattern="/auth/login" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<security:intercept-url pattern="/auth/register" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<security:intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<security:form-login login-page="/auth/login" authentication-failure-url="/auth/login?login_error=true" default-target-url="/user"/>
<security:logout logout-url="/auth/logout" logout-success-url="/" invalidate-session="true"/>
<security:openid-login authentication-failure-url="/login?login_error=t" user-service-ref="openIdUserDetailsService" />
</security:http>
<security:authentication-manager>
<security:authentication-provider user-service-ref="userDetailsService" />
</security:authentication-manager>
<!-- end FROM springmvc-security.xml -->
</beans>
For some reason, there are 2 instances of MyUserDetailsService created. The first one is used by Spring Security and the second one is injected to AuthController. What's the right approach in case I want to have a single instance of MyUserDetailsService?
You haven't shown enough configuration to be certain, but I'd bet money that you're confused about how Spring ApplicationContexts should be managed in a Spring MVC app. My answer to another question about the same problem is almost certainly what you need to read:
Declaring Spring Bean in Parent Context vs Child Context
You've most likely declared your service bean (either explicitly or with a component-scan) in both the root and child contexts of your app. Being a service bean, it should live only in the root context. You may also benefit from reading this answer:
Spring XML file configuration hierarchy help/explanation
this config works for me :
<security:authentication-manager>
<security:authentication-provider user-service-ref="userService">
and
<bean id="userService" class="com.mydomain.service.UserDetailsServiceImpl" />
A tutorial here.