...
To do this ensure that the following two lines appear in logging.properties
and any others referencing security are removed (in case they reduce the logging)
Code Block |
---|
| none |
---|
| none |
---|
title | Enable security process logging |
---|
linenumbers | truenone |
---|
|
log4j.logger.org.acegisecurity=DEBUG
log4j.logger.com.cohga.server.security=DEBUG
|
...
Looking at the default security.xml
file it contains the following near the top:
Code Block |
---|
| xml |
---|
| xml |
---|
title | Default request filter chain |
---|
linenumbers | true | xml |
---|
|
<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/server/**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,jsonExceptionTranslationFilter,filterInvocationInterceptor
/**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
</value>
</property>
</bean>
|
...
That should be the final new section we need to add, since the sections that it references should already exist. So all that remains is to add the first section we added to the list of filters:
Code Block |
---|
| xml |
---|
| xml |
---|
title | Adding NTLM filter to request filter chain |
---|
linenumbers | truexml |
---|
|
<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/server/**=httpSessionContextIntegrationFilter,ntlmProcessingFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,jsonExceptionTranslationFilter,filterInvocationInterceptor
/**=httpSessionContextIntegrationFilter,ntlmProcessingFilter,logoutFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
</value>
</property>
</bean>
|
...
To do this we add a new section to the security.xml
file to define a new NtlmProcessingFilter
Code Block |
---|
| xml |
---|
| xml |
---|
title | The root NTLM filter definition |
---|
linenumbers | truexml |
---|
|
<bean id="ntlmProcessingFilter" class="org.acegisecurity.ui.ntlm.NtlmProcessingFilter">
<property name="defaultDomain"><value>DOMAINNAME</value></property>
<property name="domainController"><value>172.16.0.30</value></property>
<property name="authenticationEntryPoint" ref="ntlmEntryPoint"/>
<property name="authenticationManager" ref="ntlmAuthenticationManager"/>
</bean>
|
...
The ntlmEntryPoint
takes care of the communication for the server to obtain the Windows userid from the browser.
The ntlmAuthenticationManager
then sends that information through a list of AuthenticationProviders
to validate that it's correct.
Code Block |
---|
| xml |
---|
| xml |
---|
title | NTLM entry point for obtaining the user information from browser |
---|
linenumbers | true | xml |
---|
|
<bean id="ntlmEntryPoint" class="org.acegisecurity.ui.ntlm.NtlmProcessingFilterEntryPoint"/>
|
Code Block |
---|
| xml |
---|
| xml |
---|
title | NTLM authentication manager for verifying the user information from browser |
---|
linenumbers | truexml |
---|
|
<bean id="ntlmAuthenticationManager" class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref local="smbAuthenticationProvider"/>
<bean class="org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider">
<property name="key" value="changeThis"/>
</bean>
<bean class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">
<property name="key" value="changeThis"/>
</bean>
</list>
</property>
</bean>
|
...
Setting up the smbAuthenticationProvider
is just a matter of configuring the SmbNtlmAuthenticationProvider
with the authorizationProvider
provider to be used.
Code Block |
---|
| xml |
---|
| xml |
---|
title | A SMB NTLM aware authentication provider |
---|
linenumbers | true | xml |
---|
|
<bean id="smbAuthenticationProvider" class="org.acegisecurity.providers.smb.SmbNtlmAuthenticationProvider">
<property name="authorizationProvider">
<ref local="nullDaoAuthenticationProvider"/>
</property>
</bean>
|
In this case we're referencing yet another item, the nullDaoAuthenticationProvider
authentication provider.
The nullDaoAuthenticationProvider
is a simple authentication provider that uses a separate UserDetailsService
to retrieve the information about what roles a user has, and if you're using the default security.xml
file for this that will be the users.properties
file.
Alternatively the UserDetailsService
could be accessing a database to retrieve the users roles, and later we'll be looking at changing this to use Active Directory (via LDAP) to determine the users roles.
Code Block |
---|
| xml |
---|
| xml |
---|
title | A SMB simple password authenticator |
---|
linenumbers | true | xml |
---|
|
<bean id="nullDaoAuthenticationProvider" class="org.acegisecurity.providers.smb.NullPasswordDaoAuthenticationProvider">
<property name="userDetailsService" ref="userDetailsService"/>
<property name="userCache">
<bean class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache">
<property name="cache">
<bean class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheManager">
<bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>
</property>
<property name="cacheName" value="userCache"/>
</bean>
</property>
</bean>
</property>
</bean>
|
...
Depending upon the version of active directory you're running you may need to specify a username/password for the ntlmProcessingFilter
, so if you find authentication errors in the weave.log file after enabling integrated authentication then change the ntlmProcessingFilter
to the following and set the appropriate username/password.
Code Block |
---|
| xml |
---|
| xml |
---|
title | Setting username/password for domain access |
---|
linenumbers | truexml |
---|
|
<bean id="ntlmProcessingFilter" class="org.acegisecurity.ui.ntlm.NtlmProcessingFilter">
<property name="defaultDomain"><value>DOMAINNAME</value></property>
<property name="domainController"><value>172.16.0.30</value></property>
<property name="authenticationEntryPoint" ref="ntlmEntryPoint"/>
<property name="authenticationManager" ref="ntlmAuthenticationManager"/>
<property name = "JCifsProperties">
<map>
<entry key="jcifs.smb.client.username">
<value>username</value>
</entry>
<entry key="jcifs.smb.client.password">
<value>password</value>
</entry>
</map>
</property>
</bean>
|
...
The new configuration items that the IPFilteredNtlmProcessingFilter
provides are excludedIpAddresses
and includedIpAddresses
, and are set as a list of IP addresses or address ranges.
Code Block |
---|
| xml |
---|
| xml |
---|
title | Selectively applying NTLM authentication |
---|
linenumbers | true | xml |
---|
|
<bean id="ntlmProcessingFilter" class="org.acegisecurity.ui.ntlm.IPFilteredNtlmProcessingFilter">
<property name="defaultDomain"><value>DOMAINNAME</value></property>
<property name="domainController"><value>172.16.0.30</value></property>
<property name="authenticationEntryPoint" ref="ntlmEntryPoint"/>
<property name="authenticationManager" ref="ntlmAuthenticationManager"/>
<property name="excludedIpAddresses">
<list>
<value>192.168.2.0/24</value>
<value>138.19.19.50</value>
</list>
</property>
<property name="includedIpAddresses">
<list>
<value>172.16.0.0/16</value>
</list>
</property>
</bean>
|
...
As of version 1.3.4 of the org.acegisecurity.ntlm bundle there's an additional property that can be set for the IPFilteredNtlmProcessingFilter
, and that's defaultRole
, which when set will add the role (exactly as it appears in the security.xml file) to the list of roles the user has. This allows you to utilise multiple Active Directory domain to authenticate user and provide access control based on what domain the user was authenticated against.
Note: If you're using LDAP to provide the users roles then it's also possible to set a defaultRole
in the LDAP populator.
Code Block |
---|
| xml |
---|
| xml |
---|
title | Using multiple domain for authentication |
---|
linenumbers | truexml |
---|
|
<bean id="ntlmProcessingFilterInternal" class="org.acegisecurity.ui.ntlm.IPFilteredNtlmProcessingFilter">
<property name="defaultDomain"><value>INTERNAL</value></property>
<property name="domainController"><value>172.16.0.30</value></property>
<property name="authenticationEntryPoint" ref="ntlmEntryPoint"/>
<property name="authenticationManager" ref="ntlmAuthenticationManager"/>
<property name="includedIpAddresses">
<list>
<value>172.16.0.0/16</value>
</list>
</property>
<property name="defaultRole"><value>ROLE_INTERNAL</value></property>
</bean>
<bean id="ntlmProcessingFilterExternal" class="org.acegisecurity.ui.ntlm.IPFilteredNtlmProcessingFilter">
<property name="defaultDomain"><value>EXTERNAL</value></property>
<property name="domainController"><value>201.20.109.76</value></property>
<property name="authenticationEntryPoint" ref="ntlmEntryPoint"/>
<property name="authenticationManager" ref="ntlmAuthenticationManager"/>
<property name="includedIpAddresses">
<list>
<value>201.20.0.0/16</value>
</list>
</property>
<property name="defaultRole"><value>ROLE_EXTERNAL</value></property>
</bean>
|
Not that to enable this both filters need to be added to the filter chain:
Code Block |
---|
| xml |
---|
| xml |
---|
title | Adding NTLM filter to request filter chain |
---|
linenumbers | truexml |
---|
|
<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/server/**=httpSessionContextIntegrationFilter,ntlmProcessingFilter1,ntlmProcessingFilter2,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,jsonExceptionTranslationFilter,filterInvocationInterceptor
/**=httpSessionContextIntegrationFilter,ntlmProcessingFilter1,ntlmProcessingFilter2,logoutFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
</value>
</property>
</bean>
|
...
So we fist need to change the ntlmAuthenticationManager
to
Code Block |
---|
| xml |
---|
| xml |
---|
title | Enabling LDAP support for authenticating users |
---|
linenumbers | true | xml |
---|
|
<bean id="ntlmAuthenticationManager" class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref local="ldapAuthenticationProvider"/>
<bean class="org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider">
<property name="key" value="changeThis"/>
</bean>
<bean class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">
<property name="key" value="changeThis"/>
</bean>
</list>
</property>
</bean>
|
And then setup the new ldapAuthenticationProvider
as follows:
Code Block |
---|
| xml |
---|
| xml |
---|
title | LDAP authentication provider that can handle NTLM authenticated users |
---|
linenumbers | truexml |
---|
|
<bean id="ldapAuthenticationProvider" class="org.acegisecurity.ui.ntlm.ldap.authenticator.NtlmAwareLdapAuthenticationProvider">
<constructor-arg>
<ref local="authenticatorLdap"/>
</constructor-arg>
<constructor-arg>
<ref local="populatorLdap"/>
</constructor-arg>
</bean>
|
...
The authentication would be configured as follows:
Code Block |
---|
| xml |
---|
| xml |
---|
title | The authenticator that will search an LDAP directory for the user |
---|
linenumbers | truexml |
---|
|
<bean id="authenticatorLdap" class="org.acegisecurity.ui.ntlm.ldap.authenticator.NtlmAwareLdapAuthenticatorImpl">
<constructor-arg>
<ref local="initialDirContextFactory"/>
</constructor-arg>
<property name="userSearch">
<ref local="userSearchLdap"/>
</property>
</bean>
|
...
The initialDirContextFactory
is also used by the userSearchLdap
and the populaterLdap
beans so we'll look at that first
Code Block |
---|
| xml |
---|
| xml |
---|
title | Setting up a connection to the LDAP server |
---|
linenumbers | true | xml |
---|
|
<bean id="initialDirContextFactory" class="org.acegisecurity.ldap.DefaultInitialDirContextFactory">
<constructor-arg value="ldap://192.168.0.16:389/"/>
<property name="managerDn">
<value>CN=Administrator,OU=Users,DC=cohga,DC=local</value>
</property>
<property name="managerPassword">
<value>password</value>
</property>
</bean>
|
...
The two final beans, the userSearchLdap
and populatorLdap
also require information that is specific to the environment you're running within, the userSearchLdap
beans would be something like the following:
Code Block |
---|
| xml |
---|
| xml |
---|
title | Setting up an LDAP search for a user |
---|
linenumbers | truexml |
---|
|
<bean id="userSearchLdap" class="org.acegisecurity.ldap.search.FilterBasedLdapUserSearch">
<constructor-arg>
<value>OU=Users,DC=cohga,DC=local</value>
</constructor-arg>
<constructor-arg>
<value>(sAMAccountName={0})</value>
</constructor-arg>
<constructor-arg>
<ref local="initialDirContextFactory" />
</constructor-arg>
<property name="searchSubtree">
<value>true</value>
</property>
</bean>
|
...
Finally the populatorLdap
is responsible for mapping the username to the roles and would be configured as follows
Code Block |
---|
| xml |
---|
| xml |
---|
title | Setting up an LDAP serach for groups |
---|
linenumbers | true | xml |
---|
|
<bean id="populatorLdap" class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
<constructor-arg>
<ref local="initialDirContextFactory"/>
</constructor-arg>
<constructor-arg>
<value>OU=Weave,DC=cohga,DC=local</value>
</constructor-arg>
<property name="groupRoleAttribute">
<value>cn</value>
</property>
<property name="searchSubtree">
<value>true</value>
</property>
<property name="rolePrefix">
<value>ROLE_</value>
</property>
<property name="convertToUpperCase">
<value>true</value>
</property>
<property name="groupSearchFilter">
<value>(member={0})</value>
</property>
<property name="defaultRole">
<value>ROLE_USERS</value>
</property>
</bean>
|
...
To implement the new authorities populator you should change:
Code Block |
---|
| xml |
---|
| xml |
---|
title | Old DefaultLdapAuthoritiesPopulator |
---|
linenumbers | true | xml |
---|
|
<bean id="populator" class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
...
</bean>
|
to
Code Block |
---|
| xml |
---|
| xml |
---|
title | New SupplementedLdapAuthoritiesPopulator with reference to user details service |
---|
linenumbers | truexml |
---|
|
<bean id="populator" class="org.acegisecurity.providers.ldap.populator.SupplementedLdapAuthoritiesPopulator">
<property name="userDetailsService" ref="ldapDetailsService"/>
...
</bean>
|
This will change it from DefaultLdapAuthoritiesPopulator
to SupplementedLdapAuthoritiesPopulator
and add a reference to the service that will provide the additional role information, which can be included by adding:
Code Block |
---|
| xml |
---|
| xml |
---|
title | New user details service reading roles from a file |
---|
linenumbers | true | xml |
---|
|
<bean id="ldapDetailsService" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
<property name="userProperties">
<bean class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="location" value="ldap.properties"/>
</bean>
</property>
</bean>
|
...
Alternatively you could also use the following if you just had a couple of users:
Code Block |
---|
| xml |
---|
| xml |
---|
title | New user details service reading roles from its configuration |
---|
linenumbers | truexml |
---|
|
<bean id="ldapDetailsService" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
<property name="userMap">
<value>
shaun=password,ROLE_TEST
</value>
</property>
</bean>
|
...