Apr 1, 2012

Spring Security - Single Sign-on Emulator


Spring Security comes with Preauthentication framework that allows authentication using an external authenticated system ( Single Sign-On  solutions- IBM’s WebSEAL, CA Siteminder) while still using internal authentication provider for authorization.

Spring’s preauthentication framework reads SSO security tokens (populated by external SSO provider) and populates user identity in its context, authorities and UserDetails are still loaded by authentication providers (JDBC, LDAP..) configured in security configuration.

Usually, enterprise SSO solutions are expensive and their licenses are not always extended to developer’s sandbox. As a result, workarounds are devised for developers that can compromise security, like hard-coding/commenting authentication piece( imagine if that makes it to production).  Also, there might be times when production support requires direct access to the application without going through SSO channel (SSO systems can be buggy too :)).

We implemented a configurable SSO emulator using spring security's filter chain that can mimic external authentication system on developer’s sandboxes. In SSO enabled environment the request is authenticated by external authentication system while on non-SSO environments it is authenticated by internal providers. In both cases the authorization (userdetailsservice) was common and agnostic of the authentication mechanism.


Spring Security makes every incoming request pass through a filter chain, where each filter inspects and validates the request based on it configuration before handing it over to next filter.  Spring allows you to write you own custom filters and place them in the filter chain. You have to be really careful with the sequencing of your filters because there are inter dependencies between them.  I had to spent a lot of time to get the filter sequencing right :)


We configured two custom filters and place them infront of preautheticated filter.

First filter (CustomFormAuthenticationFIlter) inspects the request to check where it is coming from:
  •         If it was coming from SSO system, then assume request authenticated and continue with the filter chain for pre-authentication scenario.
  •         If request was not coming from SSO then invoke spring’s form-login and authenticate using configure authentication provide.

Second filter (SSOAuthenticationTokenPopulatorFilter) inspects the request for the authentication mechanism
  •        If request is authenticated using internal customer authentication provider, then populate the SSO security tokens.


CustomFormAuthenticationFIlter:
public class CustomFormAuthenticationFilter extends
              UsernamePasswordAuthenticationFilter {

       @Override
       public void doFilter(ServletRequest req, ServletResponse res,
                     FilterChain chain) throws IOException, ServletException {             
             
              if (ssoAuthenticationTokenExists) {                    
                     chain.doFilter(req, res);

              } else {                    
                     super.doFilter(req, res, chain); 
              } 
       }      

}

SSOAuthenticationTokenPopulatorFilter: This filter will populate SSO Token if form-based authentication was successful. To populate the custom headers, we created our own custom RequestWrapper (extends HttpServletRequestWrapper), and after setting the custom header, replaced the request in the filter chain.

public class SSOAuthenticationTokenPopulatorFilter extends GenericFilterBean {
protected boolean isHeaderPopulationRequired() {
              Boolean customAuthentication = SecurityContextHolder.getContext()
                           .getAuthentication().getClass().isAssignableFrom(
                                  UsernamePasswordAuthenticationToken.class); 
             
              return customAuthentication;
 }

public void doFilter(ServletRequest request, ServletResponse response,
                     FilterChain chain) throws IOException, ServletException {
              if (isHeaderPopulationRequired ()) {

                     request = addCustomHeaders (
                                  (HttpServletRequest) request,
                                  (HttpServletResponse) response);               
              }
              chain.doFilter(request, response);

}


public HttpServletRequest addCustomHeaders(HttpServletRequest request, HttpServletResponse response)
       {             
              AbstractAuthenticationToken principal = (AbstractAuthenticationToken) request.getUserPrincipal();
              request = new CustomRequestWrapper(request);
              String sUserID = principal.getName(); 
             
              ((CustomRequestWrapper) request).addCustomHeader("SSOAuthenticationToken", sUserID);
             
              return request;
       }

}

public class CustomRequestWrapper extends HttpServletRequestWrapper {
       private Map<String, String> customHeaders = new HashMap<String, String>();

       public CustomRequestWrapper (HttpServletRequest request) {
              super(request);
       }

       public void addCustomHeader(String name, String value){
              customHeaders.put(name, value);
       }
       public String getHeader(String name){
              String header = null;
              if (customHeaders.containsKey(name)){
                     header = (String) customHeaders.get(name);
              } else {
                     header = super.getHeader(name);
              }
             
              return header;
       }

}

Now the most important piece, sequencing of filters:
·         customFormAutenticationhFilter
·         ssoAuthenticationTokenPopulatorFilter
·         Pre-authenticated (RequestHeaderAuthenticationFilter)

I sequenced by explicitly specifying before/after position for the filters, but I guess we can directly specify it in filters attribute of intercepting url.

Sample code is available @ https://github.com/romiawasthy/ssoemulator. 


       <sec:http use-expressions="true">
              <sec:intercept-url pattern="/**" access="isAuthenticated()" />
<sec:custom-filter ref="customFormAutenticationhFilter" before="FORM_LOGIN_FILTER" />
              <sec:custom-filter ref="ssoAuthenticationTokenPopulatorFilter"
                     after="ANONYMOUS_FILTER" />

<sec:custom-filter ref="preAuthenticatedFilter"
                     before="EXCEPTION_TRANSLATION_FILTER" />       
              <sec:form-login login-processing-url="/login_security_check"
                     always-use-default-target="false" authentication-failure-url="/spring_security_login?login_error" />
              <sec:logout />      

       </sec:http>

Or simply

<sec:http use-expressions="true">
              <sec:intercept-url pattern="/**" access="customFormAuthenticationFilter, ssoAuthenticationTokenPopulatorFilter, preAuthenticatedFilter " />
             
              <sec:form-login login-processing-url="/login_security_check"
                     always-use-default-target="false" authentication-failure-url="/spring_security_login?login_error" />
              <sec:logout />
       </sec:http>

Now you have a SSO emulator configured.
  

Mar 12, 2012

Maven Profile -conditionally exclude junits


Our dataaccess layer is dependent on special software components that are not available in every developer’s sandbox. By default we exclude these tests, but we still need our continuous integration environment (Bamboo) to run all the tests.  
We were able to leverage Maven’s Profile to conditionally exclude tests.

We created a maven property in our pom: exclude.tim.tests

<exclude.tim.tests>%regex[com.somecompany.dataaccess.usermanager.dao.*Test.*]</exclude.tim.tests>



We used this property in exclude configuration.
<plugin>
       <groupId>org.apache.maven.plugins</groupId>
       <artifactId>maven-surefire-plugin</artifactId>
       <version>2.12</version>
       <configuration>
              <excludes>
                     <exclude>${exclude.tim.tests}</exclude>
              </excludes>
       </configuration>
</plugin>

Running, mvn clean install excludes these tests.

To include the tests, we created a profile IncludeSpecialTests and configured the test exclusion property with a dummy value (NothingToExclude)
<profiles>
  <profile>
    <id>IncludeSpecialTests </id>
    <properties>
      <exclude.tim.tests>NothingToExclude</exclude.tim.tests>
    </properties>
  </profile>
</profiles>

Running mvn install  runs all the tests.
mvn clean install –PIncludeSpecialTests

Mar 6, 2012

Injecting Mockitto mocks using Spring @Autowired


We are using @Autowired (by Type) for dependency management of our multi-layer/multi-tiered Spring 3 project. Here is how our interface/implementation look,

public interface UserProfileService {

       public abstract UserProfileDO getUser(String userId) throws UserNotFoundException;

}

@Service
public class UserProfileServiceImpl extends BaseService implements UserProfileService, InitializingBean
{

       @Autowired
       private UserProfileDAO userprofileDao;

       @Override
       public void afterPropertiesSet() throws Exception
       {
              Assert.notNull(userprofileDao, "UserProfileDAO not injected.");
       } 
      
       @Override
       public UserProfileDO getUser(String userId) throws UserNotFoundException
       {
              IMPersonEntity person;
              try
              {
                     person = userprofileDao.getUser(userId);
              }
              catch (DataAccessException e)
              {
                     throw new UserNotFoundException(userId);
              }
              if (person == null)
                     throw new UserNotFoundException(userId);

              UserProfileDO userProfile = (UserProfileDO) objectConversion(person, UserProfileDO.class);
              return userProfile;
       }

       public void setUserprofileDao(UserProfileDAO userprofileDao)
       {
              this.userprofileDao = userprofileDao;
       }

}

UserProfileService has two dependent beans
·         UserProfileDAO: Needs to be  mocked. This is a data-access layer which is implementated using  IBMs Tivoli APIs and LDAP.
·         dozerObjectMapper: This dependency is from the BaseService, which converts the Entities into DomainObject. We don’t intend to mock this.

To configure Mockitto, I  started by using @Mock annotation but in my test I needed a mix of @Autowired and @Mock. I  don’t think MockitoJUnit44Runner loads the beans from ContextConfiguration. Also, I didn’t find any special value on using @Mock annotation (This is my first mockitto test, maybe I will realize its value later).

@RunWith(MockitoJUnit44Runner.class)
@ContextConfiguration("classpath:mock-userprofile-service-context.xml")
public class UserProfileServiceTest implements InitializingBean
{
       @Autowired    private UserProfileService userDetailsService;
       @Mock         private UserProfileDAO mockUserDetailsDao;

Other issue with my test was that my ServiceInterface (UserProfileService) did not have setter for DAO ( as per desing), that means I cannot set my mockObject on the service. I overcame this problem by using (abusing) Reflection.
So, I changed my test to switched back to SpringJUnit4ClassRunner (removed @Mock annotation) and I used reflection to set  mockObject to the testService.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:mock-userprofile-service-context.xml")
public class UserProfileServiceTest implements InitializingBean
{
       @Autowired    
private UserProfileService userDetailsService;
       private UserProfileDAO mockUserDetailsDao = mock(UserProfileDAO);

@Test
       public void getValidUser() throws UserNotFoundException, EntityNotFoundException, MultipleEntitiesFoundException
       {
              IMPerson mockPerson = new IMPerson();
              mockPerson.setDisplayName("test cca");
              when(mockUserDetailsDao.getUser("cca_test_user")).thenReturn(mockPerson);
ReflectionTestUtils.setField(userDetailsService, "userProfileDAO", mockUserDetailsDao);
              UserProfileDO user = userDetailsService.getUser("cca_test_user");
              assertNotNull(user);
              Assert.assertEquals("test cca", user.getDisplayName());

       }

This got me going, I could run tests with mock objects. I still wanted to get rid of reflection and find a way to Autowire mockObject (maintain consistency across application).
So,  I moved my mockbean creation to  test context and had @Autowired in service detect the required mock Dao (no more Reflection). I still needed a reference to mock bean in my test, to configure mock conditions.
This is how my final test context looks, with mock bean creation.

mock-userprofile-service-context.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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">


<bean  id="mockUserProfileDao" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="com.company.dataaccess.userprofile.dao.UserProfileDAO" />
</bean>

<bean class="com.company.service.userprofile.UserProfileServiceImpl"/>
<bean class="org.dozer.DozerBeanMapper"/>

</beans> 
This is how my  final test looks

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:mock-userprofile-service-context.xml")
public class UserProfileServiceTest
{ 
       @Autowired
       private UserProfileService userDetailsService;
       @Autowired
       private UserProfileDAO mockUserDetailsDao;
      

       @Test
       public void getValidUser() throws UserNotFoundException, EntityNotFoundException, MultipleEntitiesFoundException
       {
              IMPerson mockPerson = new IMPerson();
              mockPerson.setDisplayName("test cca");
              when(mockUserDetailsDao.getUser("cca_test_user")).thenReturn(mockPerson);
              UserProfileDO user = userDetailsService.getUser("cca_test_user");
              assertNotNull(user);
              Assert.assertEquals("test cca", user.getDisplayName());

       }
       @Test
       @ExpectedException(UserNotFoundException.class)
       public void getMultipleInValidUser() throws UserNotFoundException, EntityNotFoundException, MultipleEntitiesFoundException
       {
              when(mockUserDetailsDao.getUser("multipleUserids")).thenThrow(new MultipleEntitiesFoundException());
              userDetailsService.getUser("multipleUserids");
              fail();

       }
       @Test
       @ExpectedException(UserNotFoundException.class)
       public void getInValidUser() throws UserNotFoundException, EntityNotFoundException, MultipleEntitiesFoundException
       {
              when(mockUserDetailsDao.getUser("nulluser")).thenReturn(null);
              userDetailsService.getUser("nulluser");
              fail();

       }
      
}

Happy testing J