Tuesday, September 02, 2008

NTLM with Spring Security 2.0

from http://blog.mediasoft.be/?p=1

A lot of users are having trouble when dealing with NTLM based authentication with Spring Security. The underlying NTLM support is built on top of JCIFS (jcifs.samba.org), an open source client library that implements the CIFS/SMB networking protocol in Java.

NTLM authentication allows the login credentials of a Windows user, who is logged on into a domain, to be automatically passed to your browser.
NTLM is a Microsoft-developed protocol providing single sign-on capabilities to web applications. It allows a web server to automatically discover the username of a browser client when that client is logged into a Windows domain and is using an NTLM-aware browser. A web application can then reuse the user's Windows credentials without having to ask for them again.

This only works for Internet Explorer. When using Firefox, you will be prompted with an authentication prompt where you can enter your username and password. You can enable NTLM authentication also in Firefox, by doing the following steps:

  • Type "about:config" in the address bar of Firefox
  • You will see all settings of Firefox, but you need to find the key "network.automatic-ntlm-auth.trusted-uris".
  • Enter the hostnames like: "host1.domain.com, host2.domain.com"
    or just ".domain.com" to list them all at once

Once you have setup your project with the correct dependencies and libraries, we are ready to start configuring our application context. You need spring-security-core and spring-security-ntlm as project dependencies in order to get it working.

NOTE: It is better to separate your application context files into multiple files, in order to focus the configuration on the core parts of your system.

First we will add the NTLM filter itself:

  1. <bean id="ntlmFilter" class="org.springframework.security.ui.ntlm.NtlmProcessingFilter">  
  2. <property name="stripDomain" value="true"/>  
  3. <property name="defaultDomain" value="domain.mediasoft.be"/>  
  4. <property name="netbiosWINS" value="domain"/>  
  5. <property name="authenticationManager" ref="providerManager"/>  
  6. </bean>  

The value of the default domain should be the hostname of the SMB server that will be used to authenticate HTTP clients. It is preferred though, to use a WINS server over a specific domain controller.

Next, we add an Exception Translation Filter and pass it the entry point

  1. <bean id="exceptionTranslationFilter"  
  2. class="org.springframework.security.ui.ExceptionTranslationFilter">  
  3. <property name="authenticationEntryPoint" ref="ntlmEntryPoint"/>  
  4. </bean>  

We also need an entry point for our NTLM filter, define it like this:

  1. <bean id="ntlmEntryPoint"  
  2. class="org.springframework.security.ui.ntlm.NtlmProcessingFilterEntryPoint">  
  3. <property name="authenticationFailureUrl" value="/access_denied.jsp"/>  
  4. </bean>  

Our filter security intercepter can be defined as follows:

A filter security interceptor needs to be in place to perform security handling of HTTP resources:

  1. <bean id="filterSecurityInterceptor"  
  2. class="org.springframework.security.intercept.web.FilterSecurityInterceptor">  
  3. <property name="authenticationManager" ref="providerManager"/>  
  4. <property name="accessDecisionManager" ref="accessDecisionManager"</property>  
  5. <property name="objectDefinitionSource">  
  6. <value>  
  9. /access_denied.jsp=ROLE_ANONYMOUS  
  10. /**=ROLE_USER  
  11. </value>  
  12. </property>  
  13. </bean>  

As referenced in the above code block, we need to specify a provider manager to specify our authorization managers:

  1. <bean id="providerManager"  
  2. class="org.springframework.security.providers.ProviderManager">  
  3. <property name="providers">  
  4. <list> <ref bean="daoAuthenticationProvider"/> </list>  
  5. </property>  
  6. </bean>  

In this example, we will simply use an in-memory userservice to perform the lookup of the actual users allowed to this application. I just provided two test users in this usecase.

  1. <bean id="daoAuthenticationProvider"  
  2. class="be.mediasoft.spring.security.UserDetailsAuthenticationProvider">  
  3. <property name="userDetailsService">  
  4. <ref local="memoryUserDetailsService"/>  
  5. </property>  
  6. </bean>  
  8. <bean id="memoryUserDetailsService"  
  9. class="org.springframework.security.userdetails.memory.InMemoryDaoImpl">  
  10. <property name="userMap">  
  11. <value>  
  13. administrator=PASSWORD,ROLE_ADMIN  
  14. </value>  
  15. </property>  
  16. </bean>  

We also need to specify an access decision mechanism. In this case we will just use the plain unanimous voting mechanism:

We'll just use an UnanimousBased decision manager that requires all voters to abstain or grant access

  1. <bean id="accessDecisionManager"  
  2. class="org.springframework.security.vote.UnanimousBased">  
  3. <property name="allowIfAllAbstainDecisions" value="false"/>  
  4. <property name="decisionVoters">  
  5. <list> <bean id="roleVoter" class"org.springframework.security.vote.RoleVoter"/> </list>  
  6. </property>  
  7. </bean>  

Configuring web.xml
When your context configuration is complete, we can now add the following filter declaration to our web.xml file:

  1. <filter>  
  2. <filter-name>_filterChainProxy</filter-name>  
  3. <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  
  4. </filter>  
  6. <filter-mapping>  
  7. <filter-name>_filterChainProxy</filter-name>  
  8. <url-pattern>/*</url-pattern>  
  9. </filter-mapping>  

This provides a hook into the Spring Security web infrastructure.

Virtual filter chain
We need to apply all defined filters in a virtual filter chain, so that they can be applied automatically to the requests in the right order.

  1. <bean id="filterSecurityInterceptor"  
  2. class="org.springframework.security.intercept.web.FilterSecurityInterceptor">  
  3. <property name="authenticationManager" ref="providerManager"/>  
  4. <property name="accessDecisionManager"><ref local="accessDecisionManager"/></property>  
  5. <property name="objectDefinitionSource">  
  6. <value>  
  9. /access_denied.jsp=ROLE_ANONYMOUS  
  10. /**=ROLE_USER  
  11. </value>  
  12. </property>  
  13. </bean>  

Mind the order of these entries as the NTLM filter needs to be defined after the exception translation filter as this filter is based on exceptions to do NTLM handshaking etc.

Our http session context integration filter is defined as follows:

  1. <bean id="httpSessionContextIntegrationFilter"  
  2. class="org.springframework.security.context.HttpSessionContextIntegrationFilter">  
  3. <property name="contextClass" value="org.springframework.security.context.SecurityContextImpl"/>  
  4. </bean>  

The custom user details provider is implemented as follows:

  1. public class UserDetailsAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {  
  2. private UserDetailsService userDetailsService;  
  3. protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {  
  4. UserDetails loadedUser;  
  5. try {  
  6. loadedUser = this.getUserDetailsService().loadUserByUsername(username);  
  7. catch (DataAccessException repositoryProblem) {  
  8. throw new AuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem);  
  9. }  
  10. if (loadedUser == nullthrow new AuthenticationServiceException("User cannot be null");  
  11. return loadedUser;  
  12. }  
  13. public UserDetailsService getUserDetailsService() {  
  14. return userDetailsService;  
  15. }  
  16. public void setUserDetailsService(UserDetailsService userDetailsService) {  
  17. this.userDetailsService = userDetailsService;  
  18. }  
  19. }  

TO BE COMPLETED: Spring 2.0 configuration

  1. <security:authentication-manager alias="_authenticationManager"/>  
  3. <security:authentication-provider>  
  4. <security:user-service>  
  5. <security:user name="fox" password="PASSWORD" authorities="ROLE_USER, ROLE_ADMIN"/>  
  6. <security:user name="administrator" password="PASSWORD" authorities="ROLE_USER,ROLE_ADMIN"/>  
  7. </security:user-service>  
  8. </security:authentication-provider>  
  10. <bean id="userDetailsAuthenticationProvider"  
  11. class="be.mediasoft.spring.security.UserDetailsAuthenticationProvider">  
  12. <security:custom-authentication-provider/>  
  13. </bean>  
  15. <bean id="ntlmEntryPoint" class="org.springframework.security.ui.ntlm.NtlmProcessingFilterEntryPoint">  
  16. <property name="authenticationFailureUrl" value="/access_denied.jsp"/>  
  17. </bean>  
  19. <bean id="ntlmFilter" class="org.springframework.security.ui.ntlm.NtlmProcessingFilter">  
  20. <security:custom-filter position="NTLM_FILTER"/>  
  21. <property name="stripDomain" value="true"/>  
  22. <property name="defaultDomain" value="domain.mediasoft.be"/>  
  23. <property name="netbiosWINS" value="domain"/>  
  24. <property name="authenticationManager" ref="_authenticationManager"/>  
  25. </bean>  
  27. <bean id="exceptionTranslationFilter" class="org.springframework.security.ui.ExceptionTranslationFilter">  
  28. <property name="authenticationEntryPoint" ref="ntlmEntryPoint"/>  
  29. </bean>  
  31. <security:http access-decision-manager-ref="accessDecisionManager" entry-point-ref="ntlmEntryPoint">  
  32. <security:intercept-url pattern="/access_denied.jsp" filters="none"/>  
  33. <security:intercept-url pattern="/**" access="ROLE_USER"/>  
  34. </security:http>  
  36. <bean id="accessDecisionManager" class="org.springframework.security.vote.UnanimousBased">  
  37. <property name="allowIfAllAbstainDecisions" value="false"/>  
  38. <property name="decisionVoters">  
  39. <list>  
  40. <bean id="roleVoter" class="org.springframework.security.vote.RoleVoter"/>  
  41. </list>  
  42. </property>  
  43. </bean> 

No comments: