Pages

Monday, April 8, 2013

Customise Basic Authentication in Spring Security - a Simpler Example

Basic Authentication is a an easy and seemingly popular solution to securing web sites or RESTful web services if combined with secure HTTP (https).
Authentication Required!
Spring Security is an comprehensive and flexible java library to be used to implement security constraints for web sites and/or web services. Spring Security with its web module has its built-in support for basic authentication.

This post has discussed a way to implement basic authentication to secure RESTful web service calls with some kind of customization. However, after researching Spring Security in depth, I found  the customization solution can be even simpler.

So let's say we want to implement a customized basic authentication for a RESTful web service. the authentication only allow a request with a web referrer from certain URL or domain (or from a certain IP address). That means we need to check the HTTP Request header "Referer".

Here's the example solution. First we define spring security filter in the web.xml as shown below. It will load the spring security context definition from /WEB-INF/spring-security.xml.
[web.xml]
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns="http://java.sun.com/xml/ns/javaee" 
  xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
  id="WebApp_ID" version="2.5">
 
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
            /WEB-INF/spring-security.xml
        </param-value>
  </context-param>
 
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
 
  <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>
 
  <servlet>
    <servlet-name>spring-restful</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
 
  <servlet-mapping>
    <servlet-name>spring-restful</servlet-name>
    <url-pattern>/example/webservice/*</url-pattern>
  </servlet-mapping>
</web-app>

The spring security context definition is shown below. It defines a <security:http> element. This enables us to secure web app URL "/example/webservice/**" with  basic authentication.
[spring-security.xml]
<?xml version="1.0" encoding="UTF-8"?>
<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"
  xmlns:context="http://www.springframework.org/schema/context"
  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
          http://www.springframework.org/schema/context
          http://www.springframework.org/schema/context/spring-context-3.2.xsd">

   
  <security:global-method-security pre-post-annotations="enabled"/>
      
  <security:http create-session="stateless" entry-point-ref="authenticationEntryPoint" 
      realm="Example Webservices"  pattern="/example/webservice/**">
    <security:http-basic authentication-details-source-ref="myAuthDetailsSource" />      
    <security:intercept-url method="POST" pattern="/example/webservice/**" 
     access="IS_AUTHENTICATED_FULLY"/>
  </security:http>

  <bean id="myAuthDetailsSource" 
      class="com.mytechtip.spring.security.example.MyAuthenticationDetailsSource" />
   
  <bean id="authenticationEntryPoint" 
       class="org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint">
    <property name="realmName" value="Example Webservices" />
  </bean>
   
  <security:authentication-manager alias="authenticationManager">
    <security:authentication-provider ref="myAuthenticationProvider" />
  </security:authentication-manager>  

  <bean id="myAuthenticationProvider" 
      class="com.mytechtip.spring.security.example.MyAuthenticationProvider" />
</beans>

As you can see above, we customize the basic authentication with our own source of authentication details. This custom source allows us to capture the information (such as Referer) we can authenticate against. Also we wire up a custom authentication provider that can check the custom fields. We list the the sample implementation of the two classes.
[MyAuthenticationDetailsSource.java]
package com.mytechtip.spring.security.example;

import javax.servlet.http.HttpServletRequest;

import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;

public class MyAuthenticationDetailsSource extends
    WebAuthenticationDetailsSource {

  @Override
  public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
    return new MyAuthenticationDetails(context);
  }

  @SuppressWarnings("serial")
  class MyAuthenticationDetails extends WebAuthenticationDetails {

    private final String referer;

    public MyAuthenticationDetails(HttpServletRequest request) {
      super(request);
      this.referer = request.getHeader("Referer");
    }

    public String getReferer() {
      return referer;
    }
  }

}

[MyAuthenticationProvider.java]
package com.mytechtip.spring.security.example;

import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;

import com.mytechtip.spring.security.example.MyAuthenticationDetailsSource.MyAuthenticationDetails;

public class MyAuthenticationProvider implements AuthenticationProvider {

  @Override
  public Authentication authenticate(Authentication authentication)
      throws AuthenticationException {

    // cast as it pass the support method
    UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) authentication;
    String principal = (String) auth.getPrincipal();
    String credential = (String) auth.getCredentials();

    Object obj = auth.getDetails();
    if (obj instanceof MyAuthenticationDetails) {
      MyAuthenticationDetails details = (MyAuthenticationDetails) obj;
      // Doing the extra check such as referer
      if ("http://example.com/referer".equalsIgnoreCase(details.getReferer())) {
        // do the further check ...
        // return the result if passed validation
        UsernamePasswordAuthenticationToken result = 
            new UsernamePasswordAuthenticationToken(principal, credential);
        return result;
      }
    }

    throw new BadCredentialsException("Bad Authentication");

  }

  @Override
  public boolean supports(Class<?> authentication) {
    return authentication.equals(UsernamePasswordAuthenticationToken.class);

  }
}

1 comment: