Saturday, December 18, 2010

Spring Security 3 - MVC Integration Tutorial (Part 2)

In this tutorial we will add Spring Security 3 support to our previous Spring MVC 3 application. We will create a separate but configurable security layer, and authenticate our users using a custom service provider. This is Part 2 of our Spring Security 3 - MVC Integration Tutorial. If you haven't read part one, make sure to read it first: Spring Security 3 - MVC Integration Tutorial (Part 1)

Note: I suggest reading the following tutorial as well which uses the latest Spring Security 3.1
Spring Security 3.1 - Implement UserDetailsService with Spring Data JPA

What is Spring Security?
Spring Security provides comprehensive security services for J2EE-based enterprise software applications. There is a particular emphasis on supporting projects built using The Spring Framework, which is the leading J2EE solution for enterprise software development. If you're not using Spring for developing enterprise applications, we warmly encourage you to take a closer look at it. Some familiarity with Spring - and in particular dependency injection principles - will help you get up to speed with Spring Security more easily.

Source: http://static.springsource.org/spring-security/site/docs/3.1.x/reference/introduction.html#what-is-acegi-security
Let's start by creating a special controller that handles the login and logout requests.

LoginLogoutController
package org.krams.tutorial.controller;

import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * Handles and retrieves the login or denied page depending on the URI template
 */
@Controller
@RequestMapping("/auth")
public class LoginLogoutController {
        
 protected static Logger logger = Logger.getLogger("controller");

 /**
  * Handles and retrieves the login JSP page
  * 
  * @return the name of the JSP page
  */
 @RequestMapping(value = "/login", method = RequestMethod.GET)
 public String getLoginPage(@RequestParam(value="error", required=false) boolean error, 
   ModelMap model) {
  logger.debug("Received request to show login page");

  // Add an error message to the model if login is unsuccessful
  // The 'error' parameter is set to true based on the when the authentication has failed. 
  // We declared this under the authentication-failure-url attribute inside the spring-security.xml
  /* See below:
   <form-login 
    login-page="/krams/auth/login" 
    authentication-failure-url="/krams/auth/login?error=true" 
    default-target-url="/krams/main/common"/>*/
  if (error == true) {
   // Assign an error message
   model.put("error", "You have entered an invalid username or password!");
  } else {
   model.put("error", "");
  }
  
  // This will resolve to /WEB-INF/jsp/loginpage.jsp
  return "loginpage";
 }
 
 /**
  * Handles and retrieves the denied JSP page. This is shown whenever a regular user
  * tries to access an admin only page.
  * 
  * @return the name of the JSP page
  */
 @RequestMapping(value = "/denied", method = RequestMethod.GET)
  public String getDeniedPage() {
  logger.debug("Received request to show denied page");
  
  // This will resolve to /WEB-INF/jsp/deniedpage.jsp
  return "deniedpage";
 }
}
This controller declares two mappings:
/auth/login - shows the login page
/auth/denied - shows the denied access page
Each mapping will resolve to a specific JSP page.

Here are the JSP pages:

loginpage.jsp

This is a simple HTML form and input elements. If you need a review, please visit the HTML Forms and Input tutorials from w3schools. We have two text input elements:
j_username
j_password
These are Spring's placeholder for the username and password respectively.

When the form is submitted, it will be sent to the following action URL:
j_spring_security_check
Take note of the EL expression
${error}
This is used for displaying invalid credentials during login. The value is derived from the returned model of the controller. See our controller declaration below.

Next we create the access denied page. This page will be displayed if the user is trying to access an unauthorized page. For example, a regular user tries to access an admin only page will get an access denied page.

deniedpage.jsp


Here's how the JSP pages should look like:

loginpage.jsp


deniedpage.jsp


We've finished the login/logout controller and the associated JSP views. We will now enable Spring Security in our application.

To enable it, we need to the following steps:
1. Add a DelegatingFilterProxy in the web.xml
2. Declare a custom XML config named spring-security.xml

In the web.xml we declare an instance of a DelegatingFilterProxy. This basically filters requests based on the declared url-pattern.

web.xml

Notice the url-patterns for the DelegatingFilterProxy and DispatcherServlet. The Spring Security is placed at the root-path
/*
Whereas, Spring MVC is placed at a sub-path
/krams/*
We also referenced two important XML configuration files:
spring-security.xml 
applicationContext.xml
spring-security.xml contains configuration related to Spring Security.

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.0.xsd
   http://www.springframework.org/schema/security 
   http://www.springframework.org/schema/security/spring-security-3.0.xsd">
 
 <!-- This is where we configure Spring-Security  -->
 <security:http auto-config="true" use-expressions="true" access-denied-page="/krams/auth/denied" >
 
  <security:intercept-url pattern="/krams/auth/login" access="permitAll"/>
  <security:intercept-url pattern="/krams/main/admin" access="hasRole('ROLE_ADMIN')"/>
  <security:intercept-url pattern="/krams/main/common" access="hasRole('ROLE_USER')"/>
  
  <security:form-login
    login-page="/krams/auth/login" 
    authentication-failure-url="/krams/auth/login?error=true" 
    default-target-url="/krams/main/common"/>
   
  <security:logout 
    invalidate-session="true" 
    logout-success-url="/krams/auth/login" 
    logout-url="/krams/auth/logout"/>
 
 </security:http>
 
 <!-- Declare an authentication-manager to use a custom userDetailsService -->
 <security:authentication-manager>
         <security:authentication-provider user-service-ref="customUserDetailsService">
           <security:password-encoder ref="passwordEncoder"/>
         </security:authentication-provider>
 </security:authentication-manager>
 
 <!-- Use a Md5 encoder since the user's passwords are stored as Md5 in the database -->
 <bean class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" id="passwordEncoder"/>

 <!-- A custom service where Spring will retrieve users and their corresponding access levels  -->
 <bean id="customUserDetailsService" class="org.krams.tutorial.service.CustomUserDetailsService"/>
 
</beans>
The elements are self-documenting. If you're using an IDE, like Eclipse or STS, try pointing your mouse to any of these elements and you will see a short description of the element.

Notice that the bulk of the security configuration is inside the http element. Here's what we observe:

1. We declared the denied page URL in the
access-denied-page="/krams/auth/denied"

2. We provided three URLs with varying permissions. We use Spring Expression Language (SpEL) to specify the role access. For admin only access we specified hasRole('ROLE_ADMIN') and for regular users we use hasRole('ROLE_USER'). To enable SpEL, you need to set use-expressions to true

3. We declared the login URL
login-page="/krams/auth/login"

4. We declared the login failure URL
authentication-failure-url="/krams/auth/login?error=true"

5. We declared the URL where the user will be redirected if he logs out
logout-success-url="/krams/auth/login"

6. We declared the logout URL
logout-url="/krams/auth/logout"

7. We declared an authentication-manager that references a custom user-service

8. We declared a custom user-service

9. We also declared an Md5 password encoder:

When a user enters his password, it's plain string. The password we have in our database (in this case, an in-memory lists) is Md5 encoded. In order for Spring to match the passwords, it need's to encode the plain string to Md5. Once it has been encoded, then it can compare passwords.

Let's focus for a moment to the custom user-service.

To allow Spring to use our custom database schema, we need to provide a custom user-service. This service must implement Spring's UserDetailsService interface.

CustomUserDetailsService
package org.krams.tutorial.service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.log4j.Logger;
import org.krams.tutorial.dao.UserDAO;
import org.krams.tutorial.domain.DbUser;
import org.springframework.dao.DataAccessException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.transaction.annotation.Transactional;

/**
 * A custom service for retrieving users from a custom datasource, such as a database.
 * <p>
* This custom service must implement Spring's {@link UserDetailsService}
 */
@Transactional(readOnly = true)
public class CustomUserDetailsService implements UserDetailsService {
 
 protected static Logger logger = Logger.getLogger("service");

 private UserDAO userDAO = new UserDAO();
 
 /**
  * Retrieves a user record containing the user's credentials and access. 
  */
 public UserDetails loadUserByUsername(String username)
   throws UsernameNotFoundException, DataAccessException {
  
  // Declare a null Spring User
  UserDetails user = null;
  
  try {
   
   // Search database for a user that matches the specified username
   // You can provide a custom DAO to access your persistence layer
   // Or use JDBC to access your database
   // DbUser is our custom domain user. This is not the same as Spring's User
   DbUser dbUser = userDAO.searchDatabase(username);
   
   // Populate the Spring User object with details from the dbUser
   // Here we just pass the username, password, and access level
   // getAuthorities() will translate the access level to the correct role type

   user =  new User(
     dbUser.getUsername(), 
     dbUser.getPassword().toLowerCase(),
     true,
     true,
     true,
     true,
     getAuthorities(dbUser.getAccess()) );

  } catch (Exception e) {
   logger.error("Error in retrieving user");
   throw new UsernameNotFoundException("Error in retrieving user");
  }
  
  // Return user to Spring for processing.
  // Take note we're not the one evaluating whether this user is authenticated or valid
  // We just merely retrieve a user that matches the specified username
  return user;
 }
 
 /**
  * Retrieves the correct ROLE type depending on the access level, where access level is an Integer.
  * Basically, this interprets the access value whether it's for a regular user or admin.
  * 
  * @param access an integer value representing the access of the user
  * @return collection of granted authorities
  */
  public Collection<grantedauthority> getAuthorities(Integer access) {
   // Create a list of grants for this user
   List<grantedauthority> authList = new ArrayList<grantedauthority>(2);
   
   // All users are granted with ROLE_USER access
   // Therefore this user gets a ROLE_USER by default
   logger.debug("Grant ROLE_USER to this user");
   authList.add(new GrantedAuthorityImpl("ROLE_USER"));
   
   // Check if this user has admin access 
   // We interpret Integer(1) as an admin user
   if ( access.compareTo(1) == 0) {
    // User has admin access
    logger.debug("Grant ROLE_ADMIN to this user");
    authList.add(new GrantedAuthorityImpl("ROLE_ADMIN"));
   }

   // Return list of granted authorities
   return authList;
   }
}
This custom service implements the loadUserByUsername() method and provides a getAuthorities() method for retrieving authorities.

The purpose of loadUserByUsername() method is to return an instance of a fully populated Spring User object. It's up to you how you retrieve the data. In this tutorial, we retrieve the user by searching a custom DAO.

The purpose of getAuthorities() method is to translate our custom access level to a Spring Security GrantedAuthority representation. Remember our custom database stores access levels as integers. Spring Security interprets authorities based on GrantedAuthority() representations.

We just perform a simple if-else condition and return the corresponding authority:

Our custom database is accessible through a dummy DAO implementation:

UserDAO
package org.krams.tutorial.dao;

import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.krams.tutorial.domain.DbUser;

/**
 * A custom DAO for accessing data from the database.
 *
 */
public class UserDAO {

 protected static Logger logger = Logger.getLogger("dao");
 
 /**
  * Simulates retrieval of data from a database.
  */
 public DbUser searchDatabase(String username) {
  // Retrieve all users from the database
  List users = internalDatabase();
  
  // Search user based on the parameters
  for(DbUser dbUser:users) {
   if ( dbUser.getUsername().equals(username)  == true ) {
    logger.debug("User found");
    // return matching user
    return dbUser;
   }
  }
  
  logger.error("User does not exist!");
  throw new RuntimeException("User does not exist!");
 }

 /**
  * Our fake database. Here we populate an ArrayList with a dummy list of users.
  */
 private List internalDatabase() {
  // Dummy database
  
  // Create a dummy array list
  List users = new ArrayList();
  DbUser user = null;
  
  // Create a new dummy user
  user = new DbUser();
  user.setUsername("john");
  // Actual password: admin
  user.setPassword("21232f297a57a5a743894a0e4a801fc3");
  // Admin user
  user.setAccess(1);
  
  // Add to array list
  users.add(user);
  
  // Create a new dummy user
  user = new DbUser();
  user.setUsername("jane");
  // Actual password: user
  user.setPassword("ee11cbb19052e40b07aac0ca060c23ee");
  // Regular user
  user.setAccess(2);
  
  // Add to array list
  users.add(user);
  
  return users;
 }
 
}
This dummy DAO doesn't really connect to a database. It just provides an in-memory list of users. If you just need to setup a simple in-memory user-service, please read my other tutorial Spring Security 3 - MVC: Using a Simple User-Service Tutorial

Furthermore, we use a custom domain object DbUser to represent the user's credentials derived from the database. If you look at the CustomUserDetailsService again, we mapped DbUser to Spring's User object.

What's Spring User?
Models core user information retrieved by a UserDetailsService.

Implemented with value object semantics (immutable after construction, like a String). Developers may use this class directly, subclass it, or write their own UserDetails implementation from scratch.

Source: Spring Security 3 API for User
Here's DbUser:

DbUser
package org.krams.tutorial.domain;

/**
 * User domain
 */
public class DbUser {

 /**
  * The username
  */
 private String username;
 
 /**
  * The password as an MD5 value
  */
 private String password;
 
 /**
  * Access level of the user. 
  * 1 = Admin user
  * 2 = Regular user
  */
 private Integer access;
 
 public String getUsername() {
  return username;
 }
 public void setUsername(String username) {
  this.username = username;
 }
 public String getPassword() {
  return password;
 }
 public void setPassword(String password) {
  this.password = password;
 }
 public Integer getAccess() {
  return access;
 }
 public void setAccess(Integer access) {
  this.access = access;
 }
 
 
}
To access the common page, enter the following URL:
http://localhost:8080/spring-security-integrationkrams/main/common
To access the admin page, enter the following URL:
http://localhost:8080/spring-security-integrationkrams/main/admin
To login, enter the following URL:
http://localhost:8080/spring-security-integrationkrams/auth/login
To logout, enter the following URL:
http://localhost:8080/spring-security-integrationkrams/auth/logout

If you like to disable Spring Security in this application, just remove the following configuration in the web.xml:

That's it. We got a working Spring MVC 3 application that's secured by Spring Security. We've also managed to authenticate our users using a custom data provider.

The best way to learn further is to try the actual application.

Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/spring3-security-mvc-integration-tutorial/

You can download the project as a Maven build. Look for the spring-security-integration.zip in the Download sections.

You can run the project directly using an embedded server via Maven.
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run

If you want to learn more about Spring MVC and Spring Security, feel free to read my other tutorials in the Tutorials section.

References:
Spring Security 3.1.0.M2 API
Spring Security Reference Documentation
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring Security 3 - MVC Integration Tutorial (Part 2) ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

66 comments:

  1. Thank you so much for this.

    ReplyDelete
    Replies
    1. Indeed, great work here. I have used spring security and found it awesome. you may also like this LDAP authentication example in Spring Security. if you use declarative method give in article its just few lines of xml that's it.

      Delete
  2. Thank you very much! I've been looking for something explaining the proper use of spring-security, and your tutorial took care of everything I wanted to know.

    Well done!

    ReplyDelete
  3. Hi Krams, what is the different between
    spring-security.xml and spring-security-simplified.xml

    ReplyDelete
  4. Thank you!
    Thank you!
    Thank you!
    Krams for president :)

    ReplyDelete
  5. Is it possible to inject private UserDAO userDAO = new UserDAO(); via spring bean and @Resource . Because i tried that its not working

    ReplyDelete
  6. @Anonymous, the following will not work because it's not managed by Spring:

    private UserDAO userDAO = new UserDAO()

    You have to declare UserDAO as @Component or via XML config. Then you can use @Autowired or @Resource to inject the DAO

    ReplyDelete
  7. Good job!
    Very nice to read - with lots of explanations.
    I like how you manage to pace on with the details without skipping too much, like I have seen on many other blogs.

    ReplyDelete
  8. Hi krams, please write a book that summarize all of yours tutorials, you are great man !

    ReplyDelete
  9. great post. outstanding actually. By the way, on the 4 URL's you listed, shouldn't there be a "/" just prior to "krams" ?

    To access the common page, enter the following URL:
    http://localhost:8080/spring-security-integrationkrams/main/common
    To access the admin page, enter the following URL:
    http://localhost:8080/spring-security-integrationkrams/main/admin
    To login, enter the following URL:
    http://localhost:8080/spring-security-integrationkrams/auth/login
    To logout, enter the following URL:
    http://localhost:8080/spring-security-integrationkrams/auth/logout

    ReplyDelete
  10. Hats off to you Krams, this is an outstanding tutorial, very concise and easy to follow - you should take aphilippe's comment about a book seriously :)

    ReplyDelete
  11. Very Good Job...
    easy to understand.
    I am new to Spring security.
    I have one question here! We can authenticate with access permissions using a single helper class also. So, What is the main goal of using Spring security here ?
    Please let me know.

    ReplyDelete
  12. Thanks for the comments guys. Writing a book is big task :) That's why I preferred blogging for the meantime instead. But who knows it might happen. A compendium of common solutions (not just for Spring Security) sounds a good idea

    ReplyDelete
  13. @Anonymous, what helper class are you referring at? The power of Spring Security is you can augment different authentication mechanisms just by declaring appropriate configuration classes. For this tutorial, the main goal is to show how you integrate Spring Security and Spring MVC. The final goal will vary from user to user.

    ReplyDelete
  14. Hi krams,

    Very very good job. I am going to work on new project which uses Camel Integration framework for routing/mediation with Spring and Maven. If you could please provide me initial setup and some sample programs it will be really helpful for me.
    Thanks,
    Raj

    ReplyDelete
  15. Hi,

    I got working this example. I am facing one problem. I logged in with user 'jane' still I can access "http://localhost:8080/spring-security-integrationkrams/main/admin" page. Please help.

    ReplyDelete
  16. Hm... is it just me or a bug? I downloaded, unzipped the zip file, then run mvn tomcat:run. I logged in with john/admin at http://localhost:8080/spring-security-integration/krams/auth/login, then logged out at http://localhost:8080/spring-security-integration/krams/auth/logout. The browser was not re-directed to the login page, but showed this error:

    java.lang.NullPointerException
    java.util.Hashtable.get(Hashtable.java:334)
    org.apache.tomcat.util.http.Parameters.getParameterValues(Parameters.java:195)
    org.apache.tomcat.util.http.Parameters.getParameter(Parameters.java:240)
    org.apache.catalina.connector.Request.getParameter(Request.java:1065)
    org.apache.catalina.connector.RequestFacade.getParameter(RequestFacade.java:355)
    javax.servlet.ServletRequestWrapper.getParameter(ServletRequestWrapper.java:158)
    org.springframework.security.web.authentication.AbstractAuthenticationTargetUrlRequestHandler.determineTargetUrl(AbstractAuthenticationTargetUrlRequestHandler.java:86)
    org.springframework.security.web.authentication.AbstractAuthenticationTargetUrlRequestHandler.handle(AbstractAuthenticationTargetUrlRequestHandler.java:67)
    org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler.onLogoutSuccess(SimpleUrlLogoutSuccessHandler.java:28)
    org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:100)
    org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:381)
    org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:79)
    org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:381)
    org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:168)
    org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:237)
    org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:167)

    Does it happen to anybody?

    ReplyDelete
  17. You'll see the above exceptions if you use spring security 3.0.6.RELEASE, but the 3.0.5.RELEASE works fine. Interestingly, the code generated by spring roo 1.1.5 works fine with security 3.0.6.

    ReplyDelete
  18. Hi Krams,

    I am impressed by your comprehension on such a difficult subject. More importantly in the way you have interpreted it in such a clear and simple way and generously given it back to the community.

    Maybe you can setup a donation section so the community can help support and encourage great people like you.

    All the best and thank you for making my life easier.

    ReplyDelete
  19. Are you sure that it works?

    ReplyDelete
  20. I have alreayd a User object which have many fields , what you suggests to do? I must use Spring Secuitry User Object?
    Mapping it like you did?

    How can I know after a user loged-in, the user details? I want to do action with the user-session-object?

    THanks , and great site

    ReplyDelete
  21. I continue my question from above , does combine this example with Spring Security - MVC: Querying the SessionRegistry exmaple will get my user-object-session answer?

    ReplyDelete
  22. @Anonymous, if you have a custom User object with a different set of fields, I suggest you create a Custom Authentication manager. You can check the following as a starting point: http://krams915.blogspot.com/2010/12/spring-security-mvc-integration-using_26.html. After a user logs-in, you can access his details via the SecurityContextHolder. For example to get the name: SecurityContextHolder.getContext().getAuthentication().getName()

    ReplyDelete
  23. @Anonymous, you should be able to get the session information from the SessionRegistry as noted in the Spring docs: "You can list a user's sessions by calling the getAllSessions(Object principal, boolean includeExpiredSessions) method, which returns a list of SessionInformation objects."

    ReplyDelete
  24. krams , thanks for your reply, I think I might stay with customUserDetailsService without Custom Authentication and to get user from db just by mapping it back to UserDb from a Controller :
    User user = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    String name = user.getUsername(); //get logged in username
    DbUser u = userDAO.getUser(name);

    (source from mkyong)

    What your opinion on this?
    Does Custom Authentication way has a benefit than this?

    Thanks

    ReplyDelete
  25. Instead of mapping back to DbUser , which will cause many calls to the database , is there a way to save Dbuser for example into the session? attach my object? without using Custom Authentication Manager? ( using this guide with customUserDetailsService )

    thanks

    ReplyDelete
  26. Hi Krams,

    I get a null pointer exception for loadUserByUsername when I try to load the user information from an actual database.

    Based on this tutorial, I tried to store the user information in a mysql database using JPA/Hibernate. In the service layer I added the following:

    public Member getByUsername( String username ) {
    return (Member) entityManager.createQuery("FROM Member m where m.username = ?username").setParameter("username", username).getSingleResult();
    }

    I created a search method in the UserController which uses getByUsername with not problems. Yay!

    In CustomUserDetailsService the loadUserByUsername calls the same getByUsername and this time I get a NullPointerException. Boo!

    When I look into the entityManager query in the logs I can see it is now returning null.

    Is this something you have come across before?

    I don't get why the same query works with a search but not when authenticating a user on login using loadUserByUsername.

    ReplyDelete
  27. Okay - null pointer sorted. I hadn't initialized the entity manager in the CustomUserDetails.

    I have found that after logging out, I can hit the back button and still access the site normally. Is anyone else able to do this or have I screwed something up?

    ReplyDelete
  28. @Stryder, sorry for the delayed reply. I'm somewhat busy working on some projects. I'm checking right now one of my projects and yes even if you logged out, you can still access the page. But in my application, you only see the layout but you don't see the contents. That's because I added method level security (also Spring).

    Why is the page still appearing after pressing the back button of your browser? I think it's because it has been cached. Check also this thread:

    http://forum.springsource.org/showthread.php?107711-Spring-Security-Logout-Back-Button-Page-History

    Luke Taylor advises the reader to check the FAQ @http://static.springsource.org/spring-security/site/faq/faq.html#faq-cached-secure-page

    ReplyDelete
  29. @Stryder, from that FAQ, we have the following:

    1.4. Why can I still see a secured page even after I've logged out of my application?

    "The most common reason for this is that your browser has cached the page and you are seeing a copy which is being retrieved from the browsers cache. Verify this by checking whether the browser is actually sending the request (check your server access logs, the debug log or use a suitable browser debugging plugin such as “Tamper Data” for Firefox). This has nothing to do with Spring Security and you should configure your application or server to set the appropriate Cache-Control response headers. Note that SSL requests are never cached."

    ReplyDelete
  30. @krams, I found the issue with logging out and still having access to some pages using the browser back button. And guess what, the problem was me. I screwed up again! Your tutorial worked perfectly, I had a typo in my intercept-url pattern. Because there were no errors I didn't think I had anything wrong.

    I also had a read about the caching issue which is very interesting. Tampa Data is a useful tool too.

    I can only imagine how busy you would be and I don't expect anything if I post something here. Sometimes I get to sort out my issues while I am posting. I guess this one made it to a post. Either way I appreciate your thoughts.

    Thank you for the great tutorial.

    ReplyDelete
  31. very good tutorial, nice work!

    ReplyDelete
  32. When building with maven, maven seems to not see the security archives. In eclipse it works fine.

    Here is pom:

    3.0.5.RELEASE


    org.springframework.security
    spring-security-core
    ${org.springframework.version}
    jar
    compile


    org.springframework.security
    spring-security-config
    ${org.springframework.version}
    jar
    compile


    org.springframework.security
    spring-security-web
    ${org.springframework.version}
    jar
    compile

    The libs ar collected and lies in repository.

    ReplyDelete
  33. Frank Renè SorensenNovember 4, 2011 at 8:44 PM

    Hi

    I want to attach this to an real DB, like mysql, instead of using the internal database feature. How would you do this? And, do you have an example of it?

    Thanks for an very good tutorial site..!!

    Br

    Frank

    ReplyDelete
  34. Great work, like all tutorials i found here..Manny thx!

    ReplyDelete
  35. Thanks a lot, you're the best. I put your web into my bookmarks :)

    ReplyDelete
  36. I have uploaded a new tutorial using Spring Security 3.1 (see http://krams915.blogspot.com/2012/01/spring-security-31-implement_5023.html)

    ReplyDelete
  37. Hi Krams,

    I am getting 404 error when i submit the login page (/auth/login) the below is the uri it try to hit,

    http://localhost:8080/Spring3_security/j_spring_security_check

    can you please help if any more configuration or anything is needed?

    ReplyDelete
  38. Hi krams,

    Thanks for the article..

    I would like to know one thing, my application currently using org.springframework.security.userdetails.jdbc.JdbcDaoImpl for authentication.
    Directly injecting the usersByUsernameQuery and authoritiesByUsernameQuery in security.xml files.

    Initially we were using passwords as plain text, later changed to LdapSha encoding method.

    I would like to know what all things to be changed for handling this encoded password.

    - Hari

    ReplyDelete
  39. Load full user data before password check. Great idea.

    I prefer this way
    http://krams915.blogspot.com/2010/12/spring-security-mvc-integration-using_26.html

    ReplyDelete
    Replies
    1. You should also check my tutorial for Spring Security 3.1

      Delete
  40. Thank you, a thousand times thank you!

    ReplyDelete
  41. Hi Krams

    Each time I click on any link, it takes me back to login page. Any idea on how to do configuration for that?
    Basically i want to access multiple pages after login and each time i login and click on any link it send me back to login page. can u help?

    Thanks

    ReplyDelete
  42. There are a lot of tutorial out there, this is definitely one of the best.
    Thanks!

    ReplyDelete
  43. Great tutorial. Really helped me get started.

    Does it work right though?

    If I bring the web app up (in Eclipse Jave EE) and login as Jane, then try to go to the admin page I am denied access.

    I then "logout" by going to the logout page which brings me to the login page. If I login as John, and go to the admin page I am able to access the page.

    Finally, I logout John and login as Jane and I am now able to access the admin page (as Jane).

    I assume I should be denied access. Thoughts? Suggestions?

    ReplyDelete
    Replies
    1. If Jane is not an admin, she should be denied. Did you check the logs for any specific errors?

      Delete
  44. Hi ,
    Good work..
    I have an issue while using spring security 3.
    I am getting following exception

    Caused by: java.lang.NoSuchMethodException: $Proxy41.isEraseCredentialsAfterAuthentication()
    at java.lang.Class.getMethod(Class.java:1622)
    at org.springframework.util.MethodInvoker.prepare(MethodInvoker.java:178)
    at org.springframework.beans.factory.config.MethodInvokingFactoryBean.afterPropertiesSet(MethodInvokingFactoryBean.java:149)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1514)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1452)


    Can you please help here.

    ReplyDelete
  45. This comment has been removed by the author.

    ReplyDelete
  46. Hi Mark,

    You are doing amazing job.
    I tried many of your examples but this example, I couldn't implement properly due to some reason which I am not able to understand.
    No matter what I type in login form(user or admin)..it just route me to common page which also default-target-url.
    After playing with it I came to know that intercepts URL are not called so as a result it is getting routed to default url.
    Could you please explain what might be wrong?

    Thanks in advance

    ReplyDelete
    Replies
    1. vijaya, have you checked the more updated version of this guide: http://krams915.blogspot.com/2012/01/spring-security-31-implement_5023.html ?

      Also, I suggest enabling debug logging to see the error.

      Delete
  47. @Mark,I did not check that yet.I will check that now.

    I have also enabled debug mode but no help :-( .
    Was trying different ways but couldn't succeed.
    But thank you so much for your reply.

    ReplyDelete
  48. Awesome! This is your first post i have read. I appreciate the minute details you have covered..In one go everything worked perfectly fine in my pet project.I just followed steps shown above.Such a time saver.Thank You.

    ReplyDelete
  49. I want to use my own one-way encryption Algorithm instead of springs Md5PasswordEncoder. Can you please make me understand what should be method signature so that spring can pass user password to it for encoding before comparing with value got from DB and how can i configure it in Spring Security XML

    ReplyDelete
    Replies
    1. Rahul, you can pass a different password encoder under the authentication-manager element. See Part III of this tutorial http://krams915.blogspot.com/2012/01/spring-security-31-implement_5023.html I suggest you take a look at that guide. It's newer and I think cleaner.

      Delete
  50. ya but line 54 clearly is doing dbUser.getPassword().toLowerCase(),
    I think it should be to username. not password

    ReplyDelete
  51. Hi, nice tutorial, I need to add spring security to a small project of mine, after I added:

    springSecurityFilterChain
    org.springframework.web.filter.DelegatingFilterProxy




    springSecurityFilterChain
    /*
    to web.xml, I get this error:

    feb 05, 2014 3:34:48 PM org.apache.catalina.core.StandardContext filterStart
    Grave: Exception starting filter springSecurityFilterChain
    org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'springSecurityFilterChain' is defined
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:529)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1095)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:277)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1097)
    at org.springframework.web.filter.DelegatingFilterProxy.initDelegate(DelegatingFilterProxy.java:326)
    at org.springframework.web.filter.DelegatingFilterProxy.initFilterBean(DelegatingFilterProxy.java:236)
    at org.springframework.web.filter.GenericFilterBean.init(GenericFilterBean.java:194)
    at org.apache.catalina.core.ApplicationFilterConfig.initFilter(ApplicationFilterConfig.java:281)
    at org.apache.catalina.core.ApplicationFilterConfig.getFilter(ApplicationFilterConfig.java:262)
    at org.apache.catalina.core.ApplicationFilterConfig.(ApplicationFilterConfig.java:107)
    at org.apache.catalina.core.StandardContext.filterStart(StandardContext.java:4775)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5452)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:901)
    at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:877)
    at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:633)
    at org.apache.catalina.startup.HostConfig.deployDescriptor(HostConfig.java:656)
    at org.apache.catalina.startup.HostConfig$DeployDescriptor.run(HostConfig.java:1635)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
    at java.util.concurrent.FutureTask.run(FutureTask.java:262)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:744)

    can you help me?
    thanks

    ReplyDelete
  52. mvn jetty:run does not work for me unless I include a bunch of configuration for the plugin in your pom.xml. Am I the only person experiencing this?

    ReplyDelete
  53. I’m impressed with the surpassing and instructive blogs that you just provide in such very short timing.
    adt security reviews

    ReplyDelete