Translate

domingo, 23 de marzo de 2014

MongoDB Spring Security Code Example

Titulo: Ejemplo práctico de Spring Security con MongoDB con código fuente.

I have decided to change the main language of my blog in order to reach and share everything with more people.

In this post you can see the next topics

- SpringData With MongoDb Integration
- Spring MVC to manage navigation
- Spring Security with Custom user detais service to assign roles and web application authentication.




Everything is easy and separated in different files to be more understandable and you have all the code in github.

This example is based on the book Spring Security 3.x Cookbook  that cover a lot of spring security integrations with different technologies:
  1. ORM (Hibernate) and NoSQL Databases (MongoDB) 
  2. JSF
  3. JSP
  4. Struts 2
  5. GWT
  6. Grails
  7. Vaadin
  8. Wicked
  9. Facebook, Twitter
  10. WebServices: RESTFull, SOAP
  11. Captcha Integration
  12. and more all with code samples
After this recommendation, lets go to show the example, all the code you can find it in my GitHub repository.

Please download the code as ZIP or fork the repository to follow the example.

To run the example you have the following requirements:
1- Eclipse Kepler to import the project from github or zip file.
2 - Document data base MongoDB installed in the same machine as your application server.
3 - Application Server Apache Tomcat 7
4 - Configure your application path context to "/" in ordet to Spring Security secure your URL.



Import the project into eclipse like a Maven project and deploy it in a Tomcat server sever with the above path configuration.

Populate you MongoDB database with users data, you can do it easily executing this test file in the application.UserPopulateCollection.java

Remember to uncomment the //@Test line.



or insert in the Mongo Database the next data into User Collection




The application  is a 4 screens application, we have a Login page where you can login like:

1 - Admin User (Role 1 - ROLE_ADMIN): You can access to User and Campaign pages but not create new campaigns.
2 - Campaign User (Role 2 - ROLE_CAMPAIGN): You only can access to Campaign page and create new one, but you can not access to users pages.


You can access the application from the following url using any of these users

http://localhost:8080/login

User Admin
user: user1@antuansoft.es
pwd: 123

User Campaign
user: user2@antuansoft.es
pwd 123


Menu page with the different application´s options Users and Campaings.



Users page where you have all the info about your application users.




Campaing page where you can access to all information of your campaings and can add and delete it



I think it's time we have a look at the project configuration files,

A - pom.xml

In this file you can see al the dependencies that we need, all of this related with Spring and MongoDB.

B - web.xml

Here we have the main project configuration, where the application server starts reading de config files.

Open the file in github or in your eclipse project and you can see:
1- The Spring Security filter configuration to secure all the URLs
2 - Spring MVC servlet dispatcher to manager all the URL requests.
3 - Log4J configuration
4 - Spring modules (Spring Data, Spring Security, Spring MVC) configuration files

 <context-param>
     <param-name>contextConfigLocation</param-name>
     <param-value>
      /WEB-INF/mvc-config.xml
      /WEB-INF/security-config.xml       
      /WEB-INF/mongo-config.xml
     </param-value>
 </context-param>
        

C - mvc-config.xml

In this file there are the basic MVC configuration and packages to scan.

1 - Set the Spring context to  manage requests with the Spring MVC Controllers with the tag        <mvc:annotation-driven />
2 - Set the packages to scan Spring beans annotated with @RequestMapping @Component, etc...

  <context:component-scan base-package="com.antuansoft.spring.mvc">  
  <context:component-scan base-package="com.antuansoft.spring.security">  

3 - Define the views configuration location ant type, in this case with jsp, using JSTL.

D - mongo-config.xml

Here we have the MongDB configuration

1 - We define the database parameters, host and port and the database name in the mongoDbFactory. This is the main configuration to connect with database.

2- Define the MongoRepositories, this is where Spring Data is going to scan to look for Mongo DAOS (Mongo Data Acces Objects). In this DAOS is where all the database operations (save, delete, etc.) and the domain objects (User, and Campaign) are defined.

<mongo:repositories base-package="com.antuansoft.mongodb.repositories" />

E - security-config.xml

The security configuration of the application is defined here.

1 - Enable the @PreAuthorize annotation that will be user in MainController class

2 - Secure the application´s  urls. Due to this config is important to change the application path context to "/" like we said above.
   - /login, /logout and /accessdenied urls are permited to authenticated and not  authenticated users.
   - to access to /menu you have to be authenticated with ROLE_ADMIN or ROLE_CAMPAIGN
   - to access to /listUsers you have to be authenticated with ROLE_ADMIN
   - to access to /listCampaigns you have to be authenticated with ROLE_ADMIN or ROLE_CAMPAIGN
   - Finally the spring security login form is too easy to understand.


3 - The most important part is the definition of a custom class (mongoUserDetailsService) to authenticate users application access. An the password encoding, here we use plain text but  i recommend  to use MD5.

<authentication-manager alias="authenticationManager">
 <authentication-provider user-service-ref="mongoUserDetailsService">
   <password-encoder hash="plaintext" />
  </authentication-provider>
 </authentication-manager>

After configuration files we are going to review the specific Java files to understand the Spring Data, Spring Mvc and Spring Security features.

Spring Security:

Open the file MongoUserDetailsService.java

This is the file we have defined in security-config.xml as the authentication provider, this class have to implement the SpringFramework interface UserDetailsService and create the method UserDetailsService.

This method is used to check if the user data send in login form is valid against your database info, this method have to return a org.springframework.security.core.userdetails.User class with the data of the user found in your database.

You have to query your user collection and get the user info, we use the method getUserDetail that call to userRepositoryDao.findByUsername(username);

After getting  user info and based on this, the user role is defined in getAuthorities method.and pass it to UserDetails object.

getUserDetail  and getAuthorities are two methods that you can customize to set the authentication and the user Rols according yo your requirements.

 private org.springframework.security.core.userdetails.User userdetails;
 public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
        boolean enabled = true;
        boolean accountNonExpired = true;
        boolean credentialsNonExpired = true;
        boolean accountNonLocked = true;
        com.antuansoft.mongodb.domain.User user = getUserDetail(username);
        
        userdetails = new User(user.getUsername(), 
                    user.getPassword(),
                    enabled,
                    accountNonExpired,
                    credentialsNonExpired,
                    accountNonLocked,
                    getAuthorities(user.getRole()));
            return userdetails;
    }

    public List getAuthorities(Integer role) {
        List authList = new ArrayList();
        if (role.intValue() == 1) {
            authList.add(new SimpleGrantedAuthority("ROLE_ADMIN"));

        } else if (role.intValue() == 2) {
            authList.add(new SimpleGrantedAuthority("ROLE_CAMPAIGN"));
        }
        System.out.println(authList);
        return authList;
    }

    public com.antuansoft.mongodb.domain.User getUserDetail(String username) {
     com.antuansoft.mongodb.domain.User user = userRepositoryDao.findByUsername(username);
        //System.out.println(user.toString());
        return user;
    }


MongoDB:

Here we have two things to consider, Domain classes and Dao interfaces.

1 - Domain classes

This classes are plain old java objects (pojos) to define the data of your database collections, in other words the objects that your are going to persists in your database.

Look at the com.antuansoft.mongodb.domain packge and you will find the two documents that we are going to use in this application. User and Campaign.

The only thing that is special in the classes is that they are annotated with @Document and specify a collection where this pojo will be stored.

@Document(collection = "campaign" )
public class Campaign {


You can open this file to see that.

Campaign.java
User.java

2 - DAO interfaces:

Dao interfacesare in com.antuansoft.mongodb.repositories package and here is where all the database operations (save, delete, etc.) and the domain objects (User, and Campaign) are defined.

In mongo-config.xml file we have defined this package to be scanned to look for mongo repositories, its means that Spring Context transform this interfaces in a DAO object with all the basic operations like create, read, update,delete where we can use in the application.

This classes only have to extend from org.springframework.data.repository.CrudRepository to work.

--
public interface UserRepositoryDao extends CrudRepository<User, String> {
 
 
 public User findByUsername(String username);

}

--

This Crud repository has two params <User, String>, here is  where bind the User domain pojo to this Repository Dao. Here we indicate that this Dao process User Object and the ID´s type is String.

In UserRepositoyDao there is something special, it is a special method user for authentication findByUsername. Another feature of SpringData is that you can define query methods without write any line of code.

Here we have defined a query to find a user by a username, only with key words. find+By+field to query in user pojo object (findByUsername). And it´s work!!!! is magic!!

Spring MVC:

Finally the only thing that we have to review is the controller that allows us to navigate through the application.

The MainController class is a basic class that use the SpringMVC annotations, @RequestMapping,  to navigate:

@RequestMapping(value = "/listCampaigns", method = RequestMethod.GET)
 public String listCampaigns(ModelMap map) {
  
  map.addAttribute("new_campaign", new Campaign());
  Iterable campaings= campaignRepositoryDao.findAll();
  map.addAttribute("campaigns",campaings);
  
  return "listCampaigns";
 }


and basically it means that when SpringMVC detects this urls the method will be executed, in this case the method pass to listCampaigns.jsp all the campaings and new campaing object.

In this class we have a little special thing that add a spring security functionality, it´s the @PreAuthorize annotation that only allow to ROLE_CAMPAIGN users to add a new Campaign.


@PreAuthorize("hasRole('ROLE_CAMPAIGN')")
 @RequestMapping(value = "/addCampaing", method = RequestMethod.POST)
 public String addCampaign(@ModelAttribute(value = "new_campaign") 
                                  Campaign new_campaign,
             BindingResult result){
  
  new_campaign.setId(UUID.randomUUID().toString());
  campaignRepositoryDao.save(new_campaign);
  
  return "redirect:/listCampaigns";
 }

And this its all, i thinks that it is a complete example a not too difficult to understand.

Remember that you have all the code in my GitHub repository.


References:

Mainly Spring Security 3.x Cookbook

Spring Data MongoDB
Spring Security
Spring MVC


3 comentarios:

  1. I think the post is verry good but I have proplem with your sources: "https://github.com/antuansoft/SpringSecurity-MongoDB-MVC".
    It can't delete "Document" on with "deleteCampaign" on web aplication.
    I hope you can help me as soon as. Thanks, you.
    email : ngochuy.mmt@gmail.com

    ResponderEliminar
  2. Buena serie, Antuan.
    Gracias por compartir.
    Un abrazo!

    ResponderEliminar