Showing posts with label Spring Security. Show all posts
Showing posts with label Spring Security. Show all posts

Wednesday, December 31, 2014

Homebrew LDAP authorization with Spring Security

Continuing my journey from previous post, I realize that although LDAP authentication has been done successfully, but the user were still have no roles being assign. For this issue, I'm require to implement a custom authorization mechanism in order to assign LDAP user a role.

I found there are plenty of resources on how could an LDAP user is map to a role:
  1. configure LdapAuthenticationProvider in Spring. (read here)
  2. configure user-context-mapper-ref and extends one class with DefaultLdapAuthoritiesPopulator (read here)
  3. confgiure user-context-mapper-ref and extends one class with LdapUserDetailsMapper (read here)
  4. configure BindAuthenticator constructor and implements one class with LdapAuthoritiesPopulator (read here)
Among those resources, I found option 3 would be the most suit for my use case since I don't have the role group define in LDAP. And my requirement is as long as I got the name huahsin detected, then grant him an ROLE_ADMIN. To put nonsense short, I created a class extending LdapUserDetailsMapper:
public class MyAuthorityMapper extends LdapUserDetailsMapper {
 
 @Override
 public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection authority) {
  
  UserDetails userDetails = super.mapUserFromContext(ctx, username, authority);
  
  Collection<grantedauthority> authorities = new ArrayList<grantedauthority>();
  
  if( "huahsin".equalsIgnoreCase(username) ) {
   authorities.add(new GrantedAuthorityImpl("ROLE_ADMIN"));
  }
  else {
   authorities.add(new GrantedAuthorityImpl("ROLE_USER"));
  }
  
  return new User(userDetails.getUsername(), 
    userDetails.getPassword(),
    true,
    true,
    true,
    true,
    authorities);
 }
 
}
ROLE_ADMIN and ROLE_USER shown in the code snippet above is hard coded sample on how the authority is granted. In real life, the role should obtain from other source such as database. On Spring configuration site, I will have this:
    <authentication-manager alias="authenticationManager">

        <ldap-authentication-provider user-search-filter="cn={0}" group-search-base="ou=Counter Strike,ou=java,dc=homebrew,dc=org" user-context-mapper-ref="myAuthorityMapper"/>

    </authentication-manager>

    <beans:bean class="org.huahsin.WebEngineering.MyAuthorityMapper" id="myAuthorityMapper"/>

    <ldap-server url="ldap://127.0.0.1:10389"/>

Thursday, December 25, 2014

Homebrew LDAP authentication with Spring Security

The Spring Security was so interesting that I can't wait to make my own experiment at home. To do this, I'm using Apache Directory Studio to achieve this mission. For starter, I create the following structure in my LDAP:

dc=org
    |---dc=homebrew
             |---ou=java
                      |---ou=Counter Strike
                               |---cn=huahsin

Take note that when creating a CN object, SN attribute has to be define as well. Otherwise an error will be prompt. On Spring configuration site, I'm required to configure <ldap-server> element to establish connection against which authentication should take place. Following piece is more than enough for this purpose:
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/security" xsi:schemalocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-3.1.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.1.xsd">

   ...

   <ldap-server url="ldap://127.0.0.1:10389"/>

</beans:beans>
Also take note that there is additional attributes called manager-dn and manager-password in <ldap-server>, interestingly without these attributes put in <ldap-server> element, it wouldn't fail the authentication. The process were still go on. I think there must be a purpose for these attributes, just put this aside for later. The usage of that 2 attributes are shown below:
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/security" xsi:schemalocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-3.1.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.1.xsd">

   ...

   <ldap-server manager-dn="cn=huahsin,ou=Counter Strike,ou=java,dc=homebrew,dc=org" manager-password="abcde" url="ldap://127.0.0.1:10389"/>

</beans:beans>
Once LDAP connection has been established, authentication will start taking place immediately. Now I'll required <ldap-authentication-provider> for this purpose:
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/security" xsi:schemalocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-3.1.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.1.xsd">

   ...

   <authentication-manager alias="authenticationManager">
      <ldap-authentication-provider user-search-filter="cn={0}"/>
   </authentication-manager>
</beans:beans>
{0} is a placeholder for CN, it will be replace by real value during runtime. For my case, it would be huahsin. Note the usage of user-search-filter, never put cn={0},ou=Counter Strike,ou=java,dc=homebrew,dc=org for this attribute, the authentication will not get pass. That should be put in user-search-group, as shown below.
   ...
   <authentication-manager alias="authenticationManager">
      < ldap-authentication-provider group-search-base="ou=Counter Strike,ou=java,dc=homebrew,dc=org" user-search-filter="cn={0}"/>
   </authentication-manager>

   ...
Another note on the usage of group-search-base attribute is when I have the LDAP define in such a way:
< beans:beans xmlns:beans="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/security" xsi:schemalocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-3.1.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.1.xsd" >

   ...

   <ldap-server url="ldap://127.0.0.1:10389/dc=homebrew,dc=org"/>

</beans:beans>
I should have the ou=Counter Strike,ou=java define in group-search-base as shown below:
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/security" xsi:schemalocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-3.1.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.1.xsd">

   ...

   <authentication-manager alias="authenticationManager">
      <ldap-authentication-provider group-search-base="ou=Counter Strike,ou=java" user-search-filter="cn={0}" />
   </authentication-manager>
</beans:beans>
Otherwise following error would be seen.
 Exception thrown by application class 'org.springframework.ldap.support.LdapUtils.convertLdapException:174'

org.springframework.ldap.NameNotFoundException: [LDAP: error code 32 - NO_SUCH_OBJECT: failed for MessageType : SEARCH_REQUEST Message ID : 3 SearchRequest baseDn : 'ou=Counter Strike,ou=java,dc=homebrew,dc=org,dc=homebrew,dc=org' filter : '(uniqueMember=2.5.4.3=kok hoe+2.5.4.4=loh,2.5.4.11=Counter Strike,2.5.4.11=java,0.9.2342.19200300.100.1.25=homebrew,0.9.2342.19200300.100.1.25=org)' scope : whole subtree typesOnly : false Size Limit : no limit Time Limit : no limit Deref Aliases : deref Always attributes : 'cn', 'objectClass', 'javaSerializedData', 'javaClassName', 'javaFactory', 'javaCodeBase', 'javaReferenceAddress', 'javaClassNames', 'javaRemoteLocation' org.apache.directory.api.ldap.model.message.SearchRequestImpl@303434e3 ManageDsaITImpl Control Type OID : '2.16.840.1.113730.3.4.2' Criticality : 'false' ' : ERR_648 Invalid search base ou=Counter Strike,ou=java,dc=homebrew,dc=org,dc=homebrew,dc=org]; nested exception is javax.naming.NameNotFoundException: [LDAP: error code 32 - NO_SUCH_OBJECT: failed for MessageType : SEARCH_REQUEST Message ID : 3 SearchRequest baseDn : 'ou=Counter Strike,ou=java,dc=homebrew,dc=org,dc=homebrew,dc=org' filter : '(uniqueMember=2.5.4.3=kok hoe+2.5.4.4=loh,2.5.4.11=Counter Strike,2.5.4.11=java,0.9.2342.19200300.100.1.25=homebrew,0.9.2342.19200300.100.1.25=org)' scope : whole subtree typesOnly : false Size Limit : no limit Time Limit : no limit Deref Aliases : deref Always attributes : 'cn', 'objectClass', 'javaSerializedData', 'javaClassName', 'javaFactory', 'javaCodeBase', 'javaReferenceAddress', 'javaClassNames', 'javaRemoteLocation' org.apache.directory.api.ldap.model.message.SearchRequestImpl@303434e3 ManageDsaITImpl Control Type OID : '2.16.840.1.113730.3.4.2' Criticality : 'false' ' : ERR_648 Invalid search base ou=Counter Strike,ou=java,dc=homebrew,dc=org,dc=homebrew,dc=org]; remaining name 'ou=Counter Strike,ou=java,dc=homebrew,dc=org'
at org.springframework.ldap.support.LdapUtils.convertLdapException(LdapUtils.java:174)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:306)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:259)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:606)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:524)
at org.springframework.security.ldap.SpringSecurityLdapTemplate.searchForSingleAttributeValues(SpringSecurityLdapTemplate.java:173)
at org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator.getGroupMembershipRoles(DefaultLdapAuthoritiesPopulator.java:215)
at org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator.getGrantedAuthorities(DefaultLdapAuthoritiesPopulator.java:185)
at org.springframework.security.ldap.authentication.LdapAuthenticationProvider.loadUserAuthorities(LdapAuthenticationProvider.java:197)
at org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider.authenticate(AbstractLdapAuthenticationProvider.java:63)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:156)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:174)
at org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.attemptAuthentication(UsernamePasswordAuthenticationFilter.java:94)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:195)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:105)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.session.ConcurrentSessionFilter.doFilter(ConcurrentSessionFilter.java:125)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.access.channel.ChannelProcessingFilter.doFilter(ChannelProcessingFilter.java:144)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:259)
at com.ibm.ws.webcontainer.filter.FilterInstanceWrapper.doFilter(FilterInstanceWrapper.java:194)
at [internal classes]
Caused by: javax.naming.NameNotFoundException: [LDAP: error code 32 - NO_SUCH_OBJECT: failed for MessageType : SEARCH_REQUEST Message ID : 3 SearchRequest baseDn : 'ou=Counter Strike,ou=java,dc=homebrew,dc=org,dc=homebrew,dc=org' filter : '(uniqueMember=2.5.4.3=kok hoe+2.5.4.4=loh,2.5.4.11=Counter Strike,2.5.4.11=java,0.9.2342.19200300.100.1.25=homebrew,0.9.2342.19200300.100.1.25=org)' scope : whole subtree typesOnly : false Size Limit : no limit Time Limit : no limit Deref Aliases : deref Always attributes : 'cn', 'objectClass', 'javaSerializedData', 'javaClassName', 'javaFactory', 'javaCodeBase', 'javaReferenceAddress', 'javaClassNames', 'javaRemoteLocation' org.apache.directory.api.ldap.model.message.SearchRequestImpl@303434e3 ManageDsaITImpl Control Type OID : '2.16.840.1.113730.3.4.2' Criticality : 'false' ' : ERR_648 Invalid search base ou=Counter Strike,ou=java,dc=homebrew,dc=org,dc=homebrew,dc=org]
at com.sun.jndi.ldap.LdapCtx.mapErrorCode(LdapCtx.java:3112)
at com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:3033)
at com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:2840)
at com.sun.jndi.ldap.LdapCtx.searchAux(LdapCtx.java:1849)
at com.sun.jndi.ldap.LdapCtx.c_search(LdapCtx.java:1772)
at com.sun.jndi.toolkit.ctx.ComponentDirContext.p_search(ComponentDirContext.java:386)
at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.search(PartialCompositeDirContext.java:356)
at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.search(PartialCompositeDirContext.java:339)
at javax.naming.directory.InitialDirContext.search(InitialDirContext.java:267)
at org.springframework.ldap.core.LdapTemplate$4.executeSearch(LdapTemplate.java:253)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:293)
... 27 more
This stack trace gave me a very good hints on what is going on behind the scene. The error has already mention Invalid search base ou=Counter Strike,ou=java,dc=homebrew,dc=org,dc=homebrew,dc=org. My common logic sense told me that the value define in user-search-group has been concatenate with the one define in url. To fix this, dc=homebrew,dc=org should be remove either from <ldap-server> or <ldap-authentication-provider>.

Another complex scenario, what if there is another DC object being define in LDAP? For this experiment, I create another DC object named cybertron as shown below:

Root DSE
   |---dc=org
       |---dc=cybertron
   |---dc=org
       |---dc=homebrew
           |---ou=java
               |---ou=Counter Strike
                   |---cn=huahsin

And then I have the LDAP define in this way:
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/security" xsi:schemalocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-3.1.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.1.xsd">

   ...

   <ldap-server url="ldap://127.0.0.1:10389/dc=cybertron,dc=org"/>

< /beans:beans >
And I'm using this piece for authentication:
   ...
   <authentication-manager alias="authenticationManager">
      <ldap-authentication-provider group-search-base="ou=Counter Strike,ou=java,dc=homebrew,dc=org" user-search-filter="cn={0}"/>
   </authentication-manager>

   ...
Don't be foo that the above code would get pass, it wouldn't because the searching criteria would become like this ou=Counter Strike,ou=java,dc=homebrew,dc=org,dc=homebrew,dc=org,dc=cybertron,dc=org. That is the reason why it fail.

Monday, December 22, 2014

Authenticate through Active Directory in Spring

There is an web application that use a very basic authentication, or I should said it was pre-code in Spring configuration just as show below:

   


 
    
       
          
            
          
      
    

Assuming the front end code is using JSF:
 
When user is trigger the login, following code will get executed.
 public String doLogin() throws IOException, ServletException {
  
  ExternalContext context = FacesContext.getCurrentInstance().getExternalContext();
  RequestDispatcher dispatcher = ((ServletRequest) context.getRequest()).getRequestDispatcher("/j_spring_security_check");
  dispatcher.forward((ServletRequest) context.getRequest(), (ServletResponse) context.getResponse());
  FacesContext.getCurrentInstance().responseComplete();
  
  return null;
 }
The code above is a great help, it will do the authentication as what have been define in Spring configuration, and also validate on which URL does allow to access. Thanks Spring save a great effort for me as I don't need to write crazy logic on authentication. Now I want to convert this mechanism to a more elegant way, which is to integrate this application with Active Directory. This sound easy to me as there are plenty of tutorials on Internet talking about Spring security with LDAP. Wait!! It is LDAP, not Active Directory and it was clueless to me. It took me sometime reading the Spring security documentation for few times only I got to realize this -  ActiveDirectoryLdapAuthenticationProvider.
   
      
      
   
Somehow the code above is not yet perfect as it'll search through all the entry which might be a trouble to me if the entries are huge. I haven't got a clue on how could I filter base on certain group. But for now, let's get things work. Since I'm dedicating my work to let Spring handle it for me, it would be easier when comes to exception handling. The code are shown below:
   
      
         
            /faces/badCredential.xhtml
            /faces/credentialsExpired
            /faces/accountLocked
            /faces/accountDisabled
            /faces/unauthorized
         
      
   
Note that I have declare this bean as authenticationFailureHandler. In order to get that piece to work, I need to tied up this bean in authentication-failure-handler-ref as shown below:

 
 

Great! My code able to handle failure result. Let's move on. Upon successfully authenticate, authorization need to be grant. The challenge part is I'm not using the group provided in LDAP to categories access role, instead I'm making a custom one. In other words, I have my data store in database that govern the rules who have access and who don't. The best place to put this code is via authentication-success-handler-ref of form-login, and then tied this up with a Spring bean as shown below:
   
      
      
   

   
   
AuthenticateSuccessHandler is a custom made class that extends from SavedRequestAwareAuthenticationSuccessHandler. It provide me the facility to set the default page to go after a successful login. For my case, I have the user status stored in database. authorizationBo is the guy who responsible to retrieve the user status. If the status return true, I'll grant this user a ROLE_USER, otherwise no role will be granted. And I'm using the Authentication object in onAuthenticationSuccess() to determine which landing page should a user go after the authentication process. The code below reveal the logic behind the scene:
public class AuthenticateSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

 @Override
 public void onAuthenticationSuccess(final HttpServletRequest request, final HttpServletResponse response, final Authentication authentication) throws ServletException, IOException {
  
  if( authentication.getPrincipal() instanceof LdapUserDetailsImpl ) {
   
   boolean status = authorizationBo.checkAuthorization(authentication.getName());
   
   if( status ) {
    List<grantedauthority> grantedAuth = new ArrayList<>();
    grantedAuth.add(new SimpleGrantedAuthority("ROLE_USER"));
    UserDetails userDetails = new User(authentication.getName(), "", grantedAuth);
    
    SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(userDetails, "", grantedAuth));
   }
  }
  
  if( authentication.getName().equals("admin") )
   setDefaultTargetUrl("/faces/administrative.xhtml");
  else
   setDefaultTargetUrl("/faces/dashboard.xhtml");
  
  super.onAuthenticationSuccess(request, response, authentication);
 }
}
Finally I have cover both the success and failure parts. My initial though is to do some crazy logic in doLogin() such as authenticating a user and then granting a user role, but I don't at the end. Spring is a great assistant in this area, just let Spring handle it all.

Sunday, May 25, 2014

Work around to authenticate during unit testing on Spring MVC

Following method is the "usual practice" used in unit testing the authentication on Spring MVC.
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(value = {"classpath:/WEB-INF/Project-servlet.xml", "classpath:/WEB-INF/security.xml", "classpath:/WEB-INF/datasource.xml", "classpath:/WEB-INF/user.xml"})

public class HelloControllerTest extends AbstractJUnit4SpringContextTests {

 private MockMvc mockMvc;
 
 @Autowired
 private WebApplicationContext wac;
 
 @Autowired
 @Qualifier("authServiceProvider4")
 private UserDetailsService userDetailsService;

 @Autowired
 private FilterChainProxy proxy;
 
 @Before
 public void setUp() {
  mockMvc = MockMvcBuilders.webAppContextSetup(wac).addFilter(proxy).build();
 }

 @Test
 public void testUserWithAdminRoleLandOnWelcomeUrl() throws Exception {
  
  UserDetails ud = userDetailsService.loadUserByUsername("user1");
  Authentication auth = new UsernamePasswordAuthenticationToken(ud.getUsername(), ud.getPassword(), ud.getAuthorities());
  SecurityContextHolder.getContext().setAuthentication(auth);

  ...

  mockMvc.perform(get("/welcome"))
   .andExpect(status().isOk());

 }
}
Code sample above showing a test method testing on valid user, user1, landing on welcome site which require admin role in order to render the page. Somehow the test were failed, and following failure trace were seen:
java.lang.AssertionError: Status expected:<200> but was:<302>
 at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:60)
 at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:89)
 at org.springframework.test.web.servlet.result.StatusResultMatchers$5.match(StatusResultMatchers.java:546)
 at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:141)
 at org.huahsin.sit.HelloControllerTest.testUserWithAdminRoleLandOnWelcomeUrl(HelloControllerTest.java:101)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:622)
 at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
 at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
 at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
 at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
 at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
 at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
 at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83)
 at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
 at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231)
 at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:88)
 at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
 at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
 at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
 at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
 at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
 at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
 at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
 at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
 at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174)
 at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
 at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)


From what I understand from this answer, the session of user1 were gone missing, I need to hold the session of user1 when MockMvc doing its work. To do this, a mocking session class is require:
    public static class MockSecurityContext implements SecurityContext {

        private static final long serialVersionUID = -1386535243513362694L;

        private Authentication authentication;

        public MockSecurityContext(Authentication authentication) {
            this.authentication = authentication;
        }

        @Override
        public Authentication getAuthentication() {
            return this.authentication;
        }

        @Override
        public void setAuthentication(Authentication authentication) {
            this.authentication = authentication;
        }
    }
After that create a mock session and pass it to MockMvc to perform its test work.
 @Test
 public void testUserWithAdminRoleLandOnWelcomeUrl() throws Exception {
  
  UserDetails ud = userDetailsService.loadUserByUsername("user1");
  Authentication auth = new UsernamePasswordAuthenticationToken(ud.getUsername(), ud.getPassword(), ud.getAuthorities());
  SecurityContextHolder.getContext().setAuthentication(auth);

  MockHttpSession session = new MockHttpSession();
  session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, new MockSecurityContext(auth));

  mockMvc.perform(get("/welcome").session(session))
   .andExpect(status().isOk());

 }

Friday, May 23, 2014

Why @Secured and @PreAuthorize isn't working?

As mention in the title, I'm so curious to know why the both annotation are not working? Since there were so many examples and tutorials available on the Internet, but why none of them showing me that configuring Spring Security in such a way isn't a wise move? Which way? The finding is as follow.

As according to the Spring MVC development standard, the Spring Security configuration and servlet configuration were separated. Thus I was thinking that those security stuff together with <global-method-security> must be separated out from regular Spring configuration module and put them in Spring security configuration module.

To make the picture clearer. I have the Spring security configure in such a way:

 
 

  
   
   
  
   ...
  
   

   ...
   
  

  
  
   
   

   ...

  
 

And those non security stuff would place in regular Spring configuration module shown as below:


   

   
      
         /WEB-INF/pages/
      

      
         .jsp
      
   

And last, I have both Spring configuration declare in web.xml


   ...

   
      contextConfigLocation
      
         /WEB-INF/security.xml
         /WEB-INF/datasource.xml
         /WEB-INF/WebEngineering-servlet.xml
      
   

...


Everything were run perfect and no error. Just that @Secured will never work. Initially I though there is a bug in @Secured for Spring Security 3.1, but this is not true because the annotation were still not working even though I have upgraded to Spring Security 3.2. Someone in the forum suggest me to use @PreAuthorize simply because @PreAuthorize is newer than @Secured. I tried that, unfortunately @PreAuthorise is still working. I have tried many ways, keep searching the solutions, reading the same article again and again to make sure I didn't miss out the important point. Until my last try before I nearly give up (and before I start blaming Spring framework), I take out <global-method-security> from Spring security module, and put it in the regular Spring configuration module.

And it works!!

So this is the final amendment I made on the regular Spring configuration:


   
   

   
      
         /WEB-INF/pages/
      

      
         .jsp
      
   

And now both @Secured and @PreAuthorize are living in my code happily ever after. :)

Sunday, November 10, 2013

Variation on authentication-manager in Spring Security

During the feasibility study on authentication module, I been thinking to implement BO/DAO pattern. While working on the POC, I keep asking myself: "Is this the right way of doing this?" Without re-implementing the wheel, I search on the WEB to see whether is there any existing framework out there which specialize on authentication? Yes, I found Apache Acegi and Spring Security. Interestingly, there is a rumours about Spring Security is actually origin from Apache Acegi. Anyway what I'm concern is since I'm already using Spring framework at the earlier stage of the development, thus I will continue with the Spring family.

The configuration is pretty straight forward to setup. One thing that caught my attention is the usage of authentication-manager. The code snippet shown below is the typical usage to everyone which is to make a quick POC demo with a static account. In this case the login ID is huahsin and the password is 1234.
 ...
 <authentication-manager alias="authenticationManager">
  
  <authentication-provider>
   <password-encoder hash="plaintext"/>
   <user-service>
    <user authorities="ROLE_USER" name="huahsin" password="1234"/>
   </user-service>
  </authentication-provider>
 </authentication-manager>
In real world, the user account are usually store in a database for later verification. Now code snippet below shows that user-service has been replace by jdbc-user-service. Notice that users-by-username-query is responsible for retrieving the user name whereas authorities-by-username-query is the sub-query of previous query that retrieve the user role.
 ...
 <authentication-manager alias="authenticationManager">
  ...
  <authentication-provider>
   <jdbc-user-service authorities-by-username-query="select users.username, authority.authority as authority from users, authority where users.username = ? and users.username = authority.username" data-source-ref="dataSource" users-by-username-query="select username, password, 'true' as enabled from users where username=?"/>
  </authentication-provider>
 </authentication-manager>
This is the initial idea from me but somehow there are still objection on it as I have expose the risk on the SQL code is being published to the other developers. This is very subjective to some company. But in mine company there is IT governance look after us. They are very concern on this and they don't like any sensitive data being retrieved easily. Thus I have to move this into Java code like this:
package org.huahsin.security;

public class AuthServiceProvider implements AuthenticationProvider {

 private User user;
 
 private Role role;

 @Override
 public Authentication authenticate(Authentication authentication) throws AuthenticationException {
  
  String name = authentication.getName();
  String password = authentication.getCredentials().toString();
  
  /*
   * assuming there is an DAO object retrieving the particular user information
   * from table and the information is store inside User entity.
   */
  
  if( user.getUsername().equals(name) && user.getPassword().equals(password) ) {
   List<grantedauthority> grantedAuths = new ArrayList<grantedauthority>();
   grantedAuths.add(new SwitchUserGrantedAuthority(role.getRole(), authentication));
   Authentication auth = new UsernamePasswordAuthenticationToken(name, password, grantedAuths);
   
   return auth;
  }
  else {
   return null;
  }
 }

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

}

And trigger this bean from Spring like this way:
 ...
   <authentication-manager alias="authenticationManager">
  
      <authentication-provider ref="authServiceProvider"/>
   </authentication-manager>

   <beans:bean class="org.huahsin.security.AuthServiceProvider" id="authServiceProvider"/>
   ...
Notice that the code above is extending the AuthenticationProvider class, this shows the general usage during authentication process as it provide wider flexibility on identifying a user. There is always an alternate solution that could allow me to authenticate a user, which is by extending the UserDetailsService. This class has an override method, loadUserByUsername() which will retrieve the specific user during the authentication process. As shown in the code snippet below:
@Service("authServiceProvider")
public class AuthServiceProvider implements UserDetailsService {

 private User user;
 
 private Role role;

 @Override
 public UserDetails loadUserByUsername(String username)
   throws UsernameNotFoundException, DataAccessException {
  
  /*
   * assuming there is an DAO object retrieving the particular user information
   * from table and the information is store inside User entity.
   */
  
  return new org.springframework.security.core.userdetails.User(user.getUsername(), 
      user.getPassword(),
      true,
      true,
      true,
      true,
      role.getRole());
 }
}
Since this class is extending UserDetailsService, there is a slightly difference when trigger this bean from Spring, now the bean is register with user-service-ref.
 ...
 <authentication-manager alias="authenticationManager">
  <authentication-provider user-service-ref="authServiceProvider"/>
 </authentication-manager>
Usually the user's credential store in database are encrypted, says SHA512, then I just need to mention the encryption algorithm used in Spring like this:
 ...
 <authentication-manager alias="authenticationManager">
  <authentication-provider ref="daoAuthenticationProvider"/>
 </authentication-manager>

 <beans:bean class="org.springframework.security.authentication.dao.DaoAuthenticationProvider" id="daoAuthenticationProvider">
  <beans:property name="userDetailsService" ref="authServiceProvider"/>
  <beans:property name="passwordEncoder" ref="passwordEncoder"/>
 </beans:bean>

 <beans:bean class="org.springframework.security.authentication.encoding.ShaPasswordEncoder" id="passwordEncoder">
  <beans:constructor-arg index="0" value="512"/>
 </beans:bean>
Spring very kind for me, there is no single piece of Java code on user's password encryption, it is all handle automatically.

Sunday, July 14, 2013

Configuring HTTPS causes redirect loop in Spring Security

My application now have authentication module guarding its usage from anonymous user. But sometime I do felt that authentication module doesn’t really showing a great help in securing my web application especially the technology in nowadays is greatly improve, hacking is just as easy as 1, 2, 3. Thus I was thinking to integrate HTTPS protocol into my application to make it more secure. Sound great!! According to the documentation, by adding this code > requires-channel=”true” into the configuration as shown below, will force the application to use HTTPS protocol.
    <http auto-config="true">
        <intercept-url access="ROLE_ADMIN" pattern="/landingpage*"/>
        <intercept-url access="IS_AUTHENTICATED_ANONYMOUSLY" pattern="/login*" requires-channel="https"/>
  
        <form-login authentication-failure-url="/loginfailed" default-target-url="/landingpage" login-page="/login"/>
        
        <session-management invalid-session-url="/sessionTimeout">
            <concurrency-control error-if-maximum-exceeded="true" max-sessions="1"/>
        </session-management>

        <logout invalidate-session="false" logout-success-url="/login"/>
    
Unfortunately the connection were refuse. The connection refuse I mention here is referring to page not found or HTTP 404 being shown on the page. This configuration will not going to work until I discover it few days later of the research. In order to enable HTTPS, the server first must have SSL port enable. Unfortunately Tomcat was disabled by default. To enable SSL port, there are tons of tutorials article on the Internet.

Almost there! The last step is to configure port mapping in the above Spring Security to complete the whole thing. Add following code inside <http>:
    <http auto-config="true">
        ....
        ....
        <port-mappings>
            <port-mapping http="8084" https="8443"/>
        </port-mappings>
    </http>

Missing port mapping in the configuration may cause following error.

The webpage at http://localhost:8084/WebApp3/landingpage has resulted in too many redirects. Clearing your cookies for this site or allowing third-party cookies may fix the problem. If not, it is possibly a server configuration issue and not a problem with your computer. Error code:
ERR_TOO_MANY_REDIRECTS
In conclusion, if the application require to enforce HTTPS rule: 1) Ensure server has SSL port enable. 2) Port mapping has been configure in Spring configuration. 3) Intercept URL to HTTPS channel in Spring configuration.

Saturday, June 29, 2013

Why using Spring logout will redirect to session-timeout URL?

At first I have the most basic Spring security being configured in this way.
   <http auto-config="true">
        <intercept-url access="ROLE_ADMIN" pattern="/welcome*"/>
        <form-login authentication-failure-url="/loginfailed" default-target-url="/welcome" login-page="/login"/>
        <logout logout-success-url="/login"/>
   </http>
With this configuration, I can handle user login and logout request, where should the user land after successful login, and what will happen if the user failed to login. This is not enough, I want more. I would like to have a session control over the user. I want to stop the same user being login at the same time from different locations or different web browser. <session-management> could help me achieve this objective.
    <http auto-config="true">
        <intercept-url access="ROLE_ADMIN" pattern="/welcome*"/>
        <form-login authentication-failure-url="/loginfailed" default-target-url="/welcome" login-page="/login"/>
        <logout logout-success-url="/login"/>
        <session-management invalid-session-url="/sessionTimeout">
            <concurrency-control error-if-maximum-exceeded="true" max-sessions="1"/>
        </session-management>
    </http>
Anyhow, there is some problem with the code above, when I logout the application, it will redirect me to sessionTimeout page. Why this could happen? If I pay a close attention to the code, I could see that the problem is coming from <session-management invalid-session-url="/sessionTimeout">. Meanwhile, I've found something interesting in the code, by modifying the code as following will have my problem solved.
   <session-management>
      <concurrency-control error-if-maximum-exceeded="true" expired-url="/sessionTimeout" max-sessions="1"/>
   </session-management>
Now I know what is the real problem is happening. When a user logout, the session is first invalidate then only session management will get trigger. When session management come in, and found out the session has already gone, then sessionTimeout page will be redirect.

So if I want to stick to the invalid-session-url, I have to keep the session valid during logout. The code below showing details implementation.
   ...

   <logout invalidate-session="false" logout-success-url="/login"/>

   ...

Monday, December 10, 2012

Spring-security 3.x doesn't work well with spring 2.x

Now only I spot that in my pom.xml file contain spring.jar is version 2 whereas the rest of the Spring component are version 3 and above. No wonder I have this error shown in the Eclipse console:

Caused by: java.lang.NoSuchMethodError: org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.getLocalName(Lorg/w3c/dom/Node;)Ljava/lang/String;
 at org.springframework.security.config.SecurityNamespaceHandler.parse(SecurityNamespaceHandler.java:71)
 at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1297)
 at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1287)
 at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:135)
 at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:92)
 at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions(XmlBeanDefinitionReader.java:507)
 at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:398)
 ... 43 more

Found a related problem from StackOverflow. It is either upgrade spring.jar version or remove it.