Sunday, December 9, 2012

Spring MVC (Security): custom authentication manager and login page

In the previous post, we've implemented basic authentication and authorization features, mainly relying on the login page that Spring security generates. However, most of the time, we'll want to have our own login page as well as a custom authentication manager (having all the usernames, passwords, and roles hardcoded in the  web.xml file is definitely not a good solution!). Let's see how we could achieve that. As we use the project from this previous post as starting point, feel free to check out it source code here.


Tools and libraries
  • Eclipse Indigo
  • Spring-core 3.1.1
  • Spring-security 3.1.0
  • Tomcat 7
  • Maven 3
STEP 1 - Creating the custom login page

In our example, we'll create a login page, named "myLoginPage.jsp"  that is actually quite similar to the one that Spring Security generates   except that we'll add a logout button:

<%@ taglib prefix="s" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="security"
 uri="http://www.springframework.org/security/tags"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form"%>
<div id="loginPannel">
 <security:authorize access="!isAuthenticated()">
  <h1>This is my custom login page</h1>
  
  <c:if test="${loginFailed}">
   <div style="color: red">Could not sign in, please check your login/password...</div>
  </c:if>  

  <form method="post" class="signin" action="j_spring_security_check">
   <table>
    <tr>
     <th><label for="username_or_email">Username/email</label></th>
     <td><input id="username_or_email" name="j_username" type="text" /></td>
    </tr>
    <tr>
     <th><label for="password">Password</label></th>
     <td><input id="password" name="j_password" type="password" /></td>
    </tr>
    <tr>
     <th></th>
     <td><input name="commit" type="submit" value="Sign In" /></td>
    </tr>
   </table>
  </form>
 </security:authorize>
</div>

Note the following line in our JSP page: <security:authorize access="!isAuthenticated()">
It specifies that this page is restricted to user that aren't authenticated. Access rules can be defined using expressions which correspond to the methods (such as, hasRole( ), hasAnyRole( ), ...) from the "SecurityExpressionRoot" provided by Spring.

STEP 2 - Customizing login page

In this step, we edit the Spring configuration file "MyDispatcherServlet-servlet.xml", to override some behavior of Spring default security configuration:

 <security:http auto-config="true" use-expressions="true"> 
  <security:form-login login-page="/login.go" default-target-url="/home.go" authentication-failure-url="/login.go?errorLogin"/> 
  <security:intercept-url pattern="/home.go" access="hasRole('ADMIN')" /> 
  <security:logout logout-success-url="/home.go" />
 </security:http>


  • use-expression="true": allows us to use expression such as hasRole(...), hasAnyRole(...), isAuthenticated( ), and so on, to define access rules
  • login-page="/login.go": defines the URL at which the login page is located
  • default-target-url="/home.go": defines the URL toward which the user will be redirected once he successfully logged in
  • authentication-failure-url="/login.go?errorLogin": defines the URL toward which the user will be redirected when the login fails. In our example, we redirect the user to the login page with an additional "errorLogin" parameter in the URL. This existence of this parameter will be checked in a Spring Controller class. If it exists, an attribute will be added to the model before returning the view to be displayed. Within the JSP page, we check this model attribute to eventually display an error message. 
  • <security:intercept-url pattern="/home.go" access="hasRole('ADMIN')" />: indicates that the "/home.go" URL is subject to access rules.
  • <security:logout logout-success-url="/home.go" />: defines the URL at which the user is redirected after he logged out.
STEP 3 - Defining a custom authentication provider

In most cases, we'll store user credentials and roles in a DB, LDAP, you name it, and consequently, we'll want authentication and authorization to be performed against that user repository. In this example, we'll configure an authentication provider as if we were storing the user credentials in a DB.

To do so, we edit the Spring configuration file "MyDispatcherServlet-servlet.xml" and define a custom Spring service (here, it's named "myUserDetailService") as authentication provider:

 <security:authentication-manager>
  <security:authentication-provider
   user-service-ref="myUserDetailService">
  </security:authentication-provider>
 </security:authentication-manager>

STEP4 - Implementing the custom authentication provider


  • First, we have to create a class that implements the GrantedAuthority interface from Spring. This interface will have us implement a getAuthority( ) method that returns a role name.
Ex:

package service.security;

import org.springframework.security.core.GrantedAuthority;

public class GrantedAuthorityImpl implements GrantedAuthority{
 private static final long serialVersionUID = 1029928088340565343L;

 private String rolename;
 
 public GrantedAuthorityImpl(String rolename){
  this.rolename = rolename;
 }
 
 public String getAuthority() {
  return this.rolename;
 }

}


  • Then, we create a class that implements the UserDetails interface from Spring. This class will hold all the account-related info such as the user credentials and the associated roles.
package service.security;

import java.util.Collection;
import java.util.HashSet;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

public class UserDetailsImpl implements UserDetails{
 private static final long serialVersionUID = -6509897037222767090L;
 
 private Collection authorities = new HashSet();
 private String password;
 private String username;
 
 public UserDetailsImpl(String username, String password, Collection authorities){
  this.username = username; 
  this.password = password;
  this.authorities = authorities;
 }

 public Collection getAuthorities() {
  return this.authorities;
 }

 public String getPassword() {
  return this.password;
 }

 public String getUsername() {
  return this.username;
 }

 public boolean isAccountNonExpired() {
  return true;
 }

 public boolean isAccountNonLocked() {
  return true;
 }

 public boolean isCredentialsNonExpired() {
  return true;
 }

 public boolean isEnabled() {
  return true;
 }

}

  • Finally, we create a class that implements the UserDetailsService interface from Spring. This interface contains only one method: UserDetails loadUserByUsername(String username). In our example, we emulate a DB user repository by defining a Map whose keys are usernames, and values are UserDetails objects. Pay attention to the static initialization block in which we populate this map with 3 users.
Remark:

If we actually had user credentials stored in DB, we would just have to implement a repository class, and inject it into this class.
package service.security;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service("myUserDetailService")
public class UserDetailsServiceImpl implements UserDetailsService{
 
 // just to emulate user data and credentials retrieval from a DB, or whatsoever authentication service
 private static Map<String, UserDetails> userRepository = new HashMap<String, UserDetails>();
 
 static{
  GrantedAuthority authorityAdmin = new GrantedAuthorityImpl("ADMIN");
  GrantedAuthority authorityGuest = new GrantedAuthorityImpl("GUEST");
  
  /* user1/password1 --> ADMIN */
  Set<GrantedAuthority> authorities1 = new HashSet<GrantedAuthority>();
  authorities1.add(authorityAdmin);
  UserDetails user1 = new UserDetailsImpl("user1", "password1", authorities1);
  userRepository.put("user1", user1);
  
  /* user2/password2 --> GUEST */
  Set<GrantedAuthority> authorities2 = new HashSet<GrantedAuthority>();
  authorities2.add(authorityGuest);
  UserDetails user2 = new UserDetailsImpl("user2", "password2", authorities2);
  userRepository.put("user2", user2);
  
  /* user3/password3 --> ADMIN + GUEST */
  Set<GrantedAuthority> authorities3 = new HashSet<GrantedAuthority>();
  authorities3.add(authorityAdmin);
  authorities3.add(authorityGuest);
  UserDetails user3 = new UserDetailsImpl("user3", "password3", authorities3);
  userRepository.put("user3", user3);
 }
 
 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  UserDetails matchingUser = userRepository.get(username);
  
  if(matchingUser == null){
   throw new UsernameNotFoundException("Wrong username or password");
  }
  
  return matchingUser;
 }

}


  • In the Controller class, we add the two following URL mapping. As as result, GET requests for /login URL will display the custom login page, while requests for the same URL with the "errorLogin" parameter will display the same custom login page with an additional "loginFailed" model attribute. This attribute is checked in the JSP page, to display an error message, if neccessary.
 @RequestMapping(method=RequestMethod.GET, value="/login")
 public String displayLoginPage(){
  return "myLoginPage";
 }
 
 @RequestMapping(value="/login", params="errorLogin")
 public String directToLoginPageWithError(Model model){
  // Adding an attribute to flag that an error happened at login
  model.addAttribute("loginFailed", true);

  return "myLoginPage";
 }

Testing the application

  • Custom login page:
  • Login with bad credentials


  • Login with user1/password1
  • Login with user2/password2

In this last case, user2 is authenticated but not authorized to access the home.go page. In the Spring configuration "MyDispatcherServlet-servlet.xml" file, we specified that the home.go page is restricted to user with role ADMIN with the following configuration element:

<security:intercept-url pattern="/home.go" access="hasRole('ADMIN')" /> 

Source code

The source code of this example application can be checked out here.

Sunday, December 2, 2012

Spring MVC: implementing authentication and authorization using Spring security

In this post, we'll go through the few steps that will allow you to implement both authentication and authorization security features in a Spring MVC application.

We'll use the Spring MVC demo application we've developed in a previous post as starting point. Feel free to take a look at it   first (here's the post), if you want to follow the steps below one at a time.

You'd rather get down to business right away? Here are the sources of this application (pick the master branch).

Tools and libraries
  • Eclipse Indigo
  • Spring-core 3.1.1
  • Spring-security 3.1.0
  • Tomcat 7
  • Maven 3

STEP 1 - Adding necessary Maven dependencies

In order to use Spring Security, we'll need to add the following dependencies in our pom.xml configuration:
  • spring-security-taglibs
  • spring-security-config
  • standard
  • jstl
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>Spring-MVC-Tutorial</groupId>
 <artifactId>Spring-MVC-Tutorial</artifactId>
 <packaging>war</packaging>
 <version>0.0.1-SNAPSHOT</version>

 <properties>
  <spring-version>3.1.1.RELEASE</spring-version>
  <spring-security-version>3.1.0.RELEASE</spring-security-version>
 </properties>

 <build>
  <plugins>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-war-plugin</artifactId>
    <version>2.2</version>
    <configuration>
     <!-- specifiy which directory within the project hierarchy will be considered 
      as the root directory from the generated war file -->
     <warSourceDirectory>WebContent</warSourceDirectory>
    </configuration>
   </plugin>
  </plugins>
 </build>

 <dependencies>
  <!-- Spring Core -->
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-core</artifactId>
   <version>${spring-version}</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context</artifactId>
   <version>${spring-version}</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>${spring-version}</version>
  </dependency>
  <!-- Spring security -->
  <dependency>
   <groupId>org.springframework.security</groupId>
   <artifactId>spring-security-taglibs</artifactId>
   <version>${spring-security-version}</version>
  </dependency>
  <dependency>
   <groupId>org.springframework.security</groupId>
   <artifactId>spring-security-config</artifactId>
   <version>${spring-security-version}</version>
  </dependency>
  <!-- standard.jar -->
  <dependency>
   <groupId>taglibs</groupId>
   <artifactId>standard</artifactId>
   <version>1.1.2</version>
  </dependency>
  <!-- JSTL -->
  <dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>jstl</artifactId>
   <version>1.2</version>
  </dependency>
 </dependencies>
</project>

STEP 2 - Enabling Spring Security in the web application configuration

  • Once we add Spring Security features, unlike basic Spring MVC applications (in which, by default, the DispatcherServlet initialization will look for a file named [servlet-name]-servlet.xml in the WEB-INF directory), we'll need to explicitly define a ContextLoaderListener listener as well as a context parameter named contextConfigLocation whose value is the relative path to your Spring configuration file, in the web.xml file.
Here's the added part:

 <listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>

 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>
      /WEB-INF/MyDispatcherServlet-servlet.xml
  </param-value>
 </context-param>


  • Then, yet in the web.xml file, we'll add Spring security filter definition so that requests that are sent to our dispatcher servlet will first be intercepted by this security filter so that authentication/authorization can be checked against the request content, origin, and so on. At the same time, we'll define an URL mapping to this security filter to specify which requests we want to have the security filter intercept and process.
Here's the added part:
 
  springSecurityFilterChain
  org.springframework.web.filter.DelegatingFilterProxy
 

 
  springSecurityFilterChain
  /*
 
We are now done with the web.xml file and it looks like this:


 Spring MVC tutorial

 
  org.springframework.web.context.ContextLoaderListener
 

 
  contextConfigLocation
  
      /WEB-INF/MyDispatcherServlet-servlet.xml
  
 

 
  MyDispatcherServlet
  org.springframework.web.servlet.DispatcherServlet
 

 
  MyDispatcherServlet
  *.go
 

 
  springSecurityFilterChain
  org.springframework.web.filter.DelegatingFilterProxy
 

 
  springSecurityFilterChain
  /*
 

STEP 3 - Configuring Spring Security


In this step, we'll edit our Spring configuration file (that we've defined as being the MyDispatcherServlet-servlet.xml file back at the step 2), and configure the security features we want to use, that is, we'll configure authentication through a login page, and authentication through access right that the user must have in order to access a certain page.
Starting from the MyDispatcherServlet-servlet.xml file we created in the basic Spring MVC application tutorial, here are the added parts:

 <security:http auto-config="true">
  <security:intercept-url pattern="/home.go" access="ROLE_REGISTERED_USER"/> 
  <security:logout logout-success-url="/home.go"/>
 </security:http>
 
 <security:authentication-manager>
  <security:authentication-provider>  
   <security:user-service>
    <security:user name="user1" password="demo" authorities="ROLE_REGISTERED_USER" />
    <security:user name="user2" password="demo" authorities="ROLE_FREE_USER" />
   </security:user-service>
  </security:authentication-provider>
 </security:authentication-manager>

  • By defining the auto-config attribute value to true in the <security:http> element, we are using the default security features that are provided by Spring Security libraries, that is, it provides us, among others, a default login page and access rights checking
  • In the <security:intercept-url> element, we specify the URLs whose access should be restricted to users with the role(s) that we specify in the access attribute. In this example, we are basically saying that a user should be authenticated (he must log in through the default login page) and hold the ROLE_REGISTERED_USER role in order to access the "home.go" page.
  • With the <security:authentication-manager> element, we can specify how (directly querying a DB, providing a custom authentication/authorization service, through LDAP, ...)
    • username and password are retrieved to perform authentication checking
    • roles are retrieved to perform authorization checking
    In this example, we simply hard coded the usernames, their associated password and roles directly in the Spring configuration file.
Here's the whole Spring configuration file:


 
 
 

 
 

 
  
   
   
 
 
 
   
  
 
 
 
    
   
    
    
   
  
 


STEP 4 - Testing
Remark: in order to make the resulting application easier to test, we'll add a logout button to the page to be displayed, as follows:


<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
 pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>




Spring-MVC-Tutorial


 Welcome my Spring-MVC-Tutorial homepage
 "> Logout


Case 1 - Unauthenticated user

Once the application is deployed, try to access the home.go page and you'll be redirected to the default login page.



Case 2 - Wrong credentials

Try to login with wrong credentials: you'll be redirected to the login page but take a closer look to the address bar. See the  "spring_security_login?login_error"? This is the default behavior of Spring security when relying on its generated login page, to redirect the user to the login page and the login_error parameter is used to detect that the previous login attempt failed, so an error message must be displayed.



Case 3 - Authenticated and authorized user


On the login page, log in using the following credentials user1/demo. As the "user1" user has the ROLE_REGISTERED_USER role that is required to access the home.go page (remember the roles we've defined at step 3?), you'll be successfully directed to the required home.go page.


Case 4 - Authenticated but unauthorized user

From the previous page, click on the logout link to get back to the login page, then log in with the credentials user2/demo. As user2 only has the ROLE_FREE_USER role, we'll get an HTTP 403 ERROR page indicating that the user is not authorized to access the required page.


Sources

The source code of this example project is accessible here.