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.

Tuesday, December 16, 2014

Why cmake isn't compiling?

Something has caught my attention recently, just come across the CMake and I find it interesting when having some refreshment on C++. When I'm coding, I'm very rely on IDE stuff especially on Visual Studio C++ 6.0, I used to master every single short-cut key on this tool. However I didn't aware of there is a process working behind the scene, until I knew it in one day, and I'm so curious about it.

When I know Makefile, I find that this is the “engine” working behind the scene before compiler can even start to compile. After a long learning curve on Makefile, I realize that this tool definitely not for an idiot! It is a tool that only genius will know how to use it. Until I found CMake, something seem reasonable to understand.

But be cautious, I made some stupid mistake when I first using this tool:
  1. I accidentally miss spelled the file to CMakeList. The correct file name is CMakeLists.txt. The file name end with an s and have txt extension on it.
  2. Missing path-to-source argument when issuing the command cmake. The correct format is cmake .. This is to tell cmake that the source file can be find at current level.

One nice thing about this tool is it will generate a Makefile for me which save me a lot of time. Besides Makefile it generated, it also generate other files which I don't know its usage.

Monday, December 1, 2014

NoSuchMethodError on org.hamcrest.Matcher.describeMismatch

I found an interesting error when I have the following code being used in my unit test:

Assert.assertThat(((LoggingEvent) loggingEvent.getValue()).getLevel(), CoreMatchers.is(Level.DEBUG));

Following stacktrace could be seen:
java.lang.NoSuchMethodError: org.hamcrest.Matcher.describeMismatch(Ljava/lang/Object;Lorg/hamcrest/Description;)V 
 at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:18) 
 at org.junit.Assert.assertThat(Assert.java:865) 
 at org.junit.Assert.assertThat(Assert.java:832) 
 at org.huahsin68.EmployeeImplTest.test(EmployeeImplTest.java:62) 
 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.internal.runners.TestMethod.invoke(TestMethod.java:68) 
 at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:310) 
 at org.junit.internal.runners.MethodRoadie$2.run(MethodRoadie.java:88) 
 at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:96) 
 at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.executeTest(PowerMockJUnit44RunnerDelegateImpl.java:294) 
 at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTestInSuper(PowerMockJUnit47RunnerDelegateImpl.java:127) 
 at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTest(PowerMockJUnit47RunnerDelegateImpl.java:82) 
 at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runBeforesThenTestThenAfters(PowerMockJUnit44RunnerDelegateImpl.java:282) 
 at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie.java:86) 
 at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:49) 
 at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.invokeTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:207) 
 at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.runMethods(PowerMockJUnit44RunnerDelegateImpl.java:146) 
 at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$1.run(PowerMockJUnit44RunnerDelegateImpl.java:120) 
 at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:33) 
 at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:45) 
 at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.run(PowerMockJUnit44RunnerDelegateImpl.java:118) 
 at org.powermock.modules.junit4.common.internal.impl.JUnit4TestSuiteChunkerImpl.run(JUnit4TestSuiteChunkerImpl.java:101) 
 at org.powermock.modules.junit4.common.internal.impl.AbstractCommonPowerMockRunner.run(AbstractCommonPowerMockRunner.java:53) 
 at org.powermock.modules.junit4.PowerMockRunner.run(PowerMockRunner.java:53) 
 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) 

An expert told me that never use the hamcrest came from JUnit 4, I'm require to download a separate hamcrest library (it is version 1.3 as of this writing), and it must come before JUnit 4 in the build path. What he told is true.

How could I unit test on the message came from log4j?

I was ask to unit test every single function that I wrote, and I was so unlucky that every function I wrote do invoke log4j. I was thinking whether I should cover those in my unit test as well? There is not much resources found on Internet, mat be due to the reason this really waste of effort or they are more concern on the logic design than this. But I'm so curious on how the code is if I'm insist to go with it. For the purpose of this, I have create an POC as shown below. Let's assume I'm going to unit test on funcA():
public class EmployeeImpl { 

 private static Logger log; 

 public static void initLog() { 
   
  log = Logger.getLogger(EmployeeImpl.class); 
  log.setLevel(Level.INFO); 
  log.setAdditivity(false); 
   
  ConsoleAppender ca = new ConsoleAppender(); 
  ca.setWriter(new OutputStreamWriter(System.out)); 
  ca.setLayout(new PatternLayout("%-5p [%t]: %m%n")); 
  log.addAppender(ca); 
 } 
  
 public static void funA() { 
  log.info("Entering into funcA"); 
 } 
}

Next I have my unit test as shown below targeting on funcA():
@RunWith(PowerMockRunner.class)
public class EmployeeImplTest { 
  
 @Mock 
 private Appender appenderMock; 
  
 @Captor 
 private ArgumentCaptor<loggingevent> loggingEvent; 

 @Test 
 public void test() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { 
   
  Logger.getRootLogger().addAppender(appenderMock); 
   
  Field log = EmployeeImpl.class.getDeclaredField("log"); 
  log.setAccessible(true); 
  log.set(null, LogManager.getLogger(EmployeeImpl.class)); 
   
  EmployeeImpl.funA(); 
   
  Mockito.verify(appenderMock, Mockito.times(1)).doAppend((LoggingEvent) loggingEvent.capture()); 
  Assert.assertThat(((LoggingEvent) loggingEvent.getValue()).getLevel(), CoreMatchers.is(Level.INFO)); 
  Assert.assertThat(((LoggingEvent) loggingEvent.getAllValues().get(0)).getRenderedMessage(), CoreMatchers.equalTo("Entering into funcA")); 
 } 
}
Tada!! The test case above test 2 things; 1) Ensure the log level is INFO, 2) Ensure the message Entering into funcA. were shown. Either one is incorrect will failed the test case. In this case, the result will be pass.