Saturday, November 15, 2014

Implementing Token based Authentication and Form Authentication using Spring Security

In today's world, where technological ecosystem has developed so much that no system can work independently. Systems are integrated together to work in tandem and serve value to their users. Rather than users going to several systems to gather information, the concept has evolved to integrate all the required system together and present the information in simple consumable form for the user. Due to this paradigm shift, the systems are developed using Service Oriented Architecture (SOA). They expose services for their consumers to get information from the system without involving any complexity of integrating two heterogeneous systems.

Background

The systems will have their regular login form for allowing users to login directly into it and get the required information. Along with this, the information retrieval can also be automated using APIs (REST, SOAP etc) and these APIs must be secured to allow authorized access.This post would demonstrate how Spring Security can be used to setup Form Authentication as well as Token based Authentication for REST Services. Here, I am making REST APIs as stateless endpoints and hence every access to APIs would be sending an authentication token along with it.


Configuration

Below is the configuration setup to enable Form and Token authentication. 
  1. First add the following spring-security dependencies in pom.xml :
    <dependency> <!-- security-core for core authentication and access-control classes-->
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-core</artifactId>
        <version>${org.springframework.security.version}</version>
    </dependency>
    <dependency> <!-- security-web for web-security infrastructure code -->
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-web</artifactId>
        <version>${org.springframework.security.version}</version>
    </dependency>
    <dependency> <!-- security-config contains the security namespace parsing code -->
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-config</artifactId>
        <version>${org.springframework.security.version}</version>
    </dependency>
  2. Configure security namespace in your application context:
    <beans xmlns="http://www.springframework.org/schema/beans"
           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.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">                                   
    
  3. Add the following filter declaration to your web.xml file. This provides a hook into Spring security infrastructure and delegates to filter implementation defined as a Spring Bean in application context. We will see use of this in our example of Token based authentication.
    <filter>
      <filter-name>springSecurityFilterChain</filter-name>
      <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    
    <filter-mapping>
      <filter-name>springSecurityFilterChain</filter-name>
      <url-pattern>/*</url-pattern>
    </filter-mapping>
    
  4. Configure form login and authentication provider in application context
    <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' default-target-url='/home.htm'/>
    </security:http>
    
    <!-- myUserDetailsService is a custom implementation of Spring Security's UserDetailsService -->
    <security:authentication-manager> 
        <security:authentication-provider user-service-ref="myUserDetailsService">
          <security:password-encoder base64="true" hash="md5"> <!--encodes the password as per Hash algo and salt value -->
            <security:salt-source user-property="username"/>
          </security:password-encoder>
        </security:authentication-provider>
    </security:authentication-manager>
  5. Configure token authentication for Rest APIs. You have to add another security:http tag. Yes, you can have multiple security:http tags configured in your application context xml.
    <security:http  pattern="/api/**" entry-point-ref="restAuthenticationEntryPoint" use-expressions="true" auto-config="false" create-session="stateless" >
        <security:custom-filter ref="authenticationTokenProcessingFilter" position="FORM_LOGIN_FILTER"   />
        <security:intercept-url pattern="/spr/api/**" access="isAuthenticated()" />
        <security:logout />
    </security:http>
     
    <bean class="com.javabydefault.web.filter.TokenAuthenticationFilter" id="authenticationTokenProcessingFilter">
        <constructor-arg type="java.lang.String"><value>/api/**</value></constructor-arg>
    </bean>
    
    Let's understand this configuration bit more in detail:pattern : This configuration is invoked for all the url patterns starting with /api
    entry-point-ref : This is where spring sends requests which are not authenticated. The default is LoginUrlAuthenticationEntryPoint which send to login forms. For REST Api, we need to return HTTP 401 - Unauthorized if the request wasn't authenticated in the filter chain by our AuthenticationTokenProcessingFilter
    use-expressions = "true": Indicates spring that access attributes of the intercept-url elements to contain Spring EL expressions. The expression must evaluate to a boolean defining whether access should be allowed or not.
    auto-config = "false" : Turning off auto-config, helps Spring to not render Form for authentication if request is unauthorized. Since the authentication will happen inside the custom filter, there is no need to redirect to default form for authentication.
    create-session = "stateless" : Creating a stateless session for Rest Api access indicates Spring to not create HTTP session and will re-authenticate on every request. The SecurityContextHolder is cleared after each request. 
    security:custom-filter : A Custom implementation of AbstractAuthenticationProcessingFilter which will be invoked at FORM_LOGIN_FILTER position. The default implementation is UsernamePasswordAuthenticationFilter which authenticates based on Username and Password. Overriding AbstractAuthenticationProcessingFilter will authenticate based on Token passed in the request header.
    security:intercept-url : Intercepts the url pattern /api/** and only grants access to the authenticates requests. isAuthenticated() is a spring expression which returns true if request has been authenticated by any of the Spring filter.
    security:logout : Invalidating the session after each request.  
Implementation

Below is the java code implemented for this example:

1. MyUserDetailService.java which loads user details from database in Form based authentication.
import com.javabydefault.db.entity.Login;
import com.javabydefault.db.repository.LoginRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.stereotype.Component;

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

@Component
public class MyUserDetailsServiceImpl implements UserDetailsService {
    private static final Logger log = LoggerFactory.getLogger(MyUserDetailsServiceImpl.class);
    
    @Autowired
    LoginRepository loginRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Login login = loginRepository.findOne(username);
        log.debug(username+" Login:"+login);
        if(login == null) throw new UsernameNotFoundException(username+" not found");
        List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(login.getRoles()==null? 0 : login.getRoles().size());
        if(login.getRoles() != null) for(String role : login.getRoles()) authorities.add(new SimpleGrantedAuthority("ROLE_"+role));
        return new User(username, login.getPassword(), authorities);
    }
}
2. RestAuthenticationEntryPoint.java, implements AuthenticationEntryPoint, this is where spring sends requests which are not authenticated by any of the filter in the spring filter chain. This always returns HTTP 401 if none of the filter is able to authenticate the request.
import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

@Component("restAuthenticationEntryPoint")
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
 
   @Override
   public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException ) throws IOException {
      response.sendError( HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized" );
   }
}
3. TokenAuthenticationFilter.java is a custom filter invoked at FORM_LOGIN_FILTER position in Spring Filter Chain. This class overrides AbstractAuthenticationProcessingFilter.attemptAuthentication() which validates the token and returns the Authentication object, if token is valid else throws an exception. It's up to you to define the structure of the token and establish its validity. 
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

public class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    protected TokenAuthenticationFilter(String defaultFilterProcessesUrl) {
        super(defaultFilterProcessesUrl); //defaultFilterProcessesUrl - specified in applicationContext.xml.  
        super.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(defaultFilterProcessesUrl)); //Authentication will only be initiated for the request url matching this pattern
        setAuthenticationManager(new NoOpAuthenticationManager());
        setAuthenticationSuccessHandler(new TokenSimpleUrlAuthenticationSuccessHandler());
    }

    /**
     * Attempt to authenticate request 
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException,
            IOException, ServletException {
        String token = request.getHeader("Authorization");
        logger.info("token found:"+token);
        
        AbstractAuthenticationToken userAuthenticationToken = authUserByToken(token);
        if(userAuthenticationToken == null) throw new AuthenticationServiceException("Invalid Token"));
        return userAuthenticationToken;
    }
    
    /**
     * authenticate the user based on token
     * @return
     */
    private AbstractAuthenticationToken authUserByToken(String token) {
        if(token==null) return null;

        String username = getUserNameFromToken(); //logic to extract username from token
        String role = getRolesFromToken(); //extract role information from token
        
        List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
        authorities.add(new SimpleGrantedAuthority(role));
        
        User principal = new User(username, "", authorities); 
        AbstractAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(principal, "", principal.getAuthorities());
        
        return authToken;
    }
 
 
    @Override
    public void doFilter(ServletRequest req, ServletResponse res,
            FilterChain chain) throws IOException, ServletException {
        super.doFilter(req, res, chain);
    }
}
4. NoOpAuthenticationManager.java is a No Operation Authentication Manager set in the constructor of TokenAuthenticationFilter. It implements Spring's Authentication Manager class. It just returns the same authentication object passed in as a parameter to authenticate(). AbstractAuthenticationProcessingFilter requires that you set the authenticationManager property. Since all authentication happens in attemptAuthentication() of AbstractAuthenticationProcessingFilter, this AuthenticationManager can be set for the filter.


package com.javabydefault.web.filter;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;

public class NoOpAuthenticationManager implements AuthenticationManager {
     
    @Override
    public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
        return authentication;
    }
}
5. TokenSimpleUrlAuthenticationSuccessHandler.java  is a custom Success Handler where spring sends requests if authentication is successful. It overrides the default SimpleUrlAuthenticationSuccessHandler which redirects request to home page url. It's a redirect roundtrip from browser with Http code 301. In case of REST Api, we need to do a server side forward to the requested url after successful authentication.
package com.javabydefault.web.filter;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;

public class TokenSimpleUrlAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    @Override
    protected String determineTargetUrl(HttpServletRequest request,
            HttpServletResponse response) {
        String context = request.getContextPath();
        String fullURL = request.getRequestURI();
        String url = fullURL.substring(fullURL.indexOf(context)+context.length());
        return url;
    }
 
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,
            HttpServletResponse response, Authentication authentication)
            throws IOException, ServletException {
        String url = determineTargetUrl(request,response);
        request.getRequestDispatcher(url).forward(request, response);
    }

}
Summary
This posts attempts to show how spring security can be used for Token based authentication along with Form based authentication which is now becoming a very common use case. Hopefully, this will be a good starting point for the developers attempting to achieve this functionality. Spring Security provides a great deal of freedom and probably this is one of the way to achieve this.

7 comments:

  1. This comment has been removed by the author.

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

    ReplyDelete
  3. can you update this to new Spring annotations design?

    ReplyDelete
  4. This works fine thanks. Any idea of how to achieve Role based authorization? i want /api/user/** to be accessed by ROLE_USER only. Currently with your code, FilterSecurityInterceptor never gets called. I added chain.doFilter in doFilter method in TokenAuthenticationFilter which calls FilterSecurityInterceptor. The issue is in AccessDeniedHandlerImpl where request.isCommitted hence we can not throw the AccessDenied exception. Solution would be to not write to request (Which commits it), but i am not sure how to achieve this?

    ReplyDelete
  5. Hi, if you provide a full project with a github link then it would be more helpful.

    ReplyDelete
  6. can you send me the whole project

    ReplyDelete
  7. can you send me the whole project

    ReplyDelete