Windows Authentication
Description
Implementing Windows security generally involves two processes, authenticating determining who the user with Weave is using their Windows username and userid then using information contained in an Active Directory server to determine what roles a user has access to.
The first process can be implemented independently of the second, and while the second could be implemented independently of the first it doesn't really make sense.
Integrated Authentication
To implement Windows integrated authentication and allow internal users to login to Weave automatically using their Windows username involves editing the security.xml
file to replace the login form with handling with the NTLM processor.
So looking at the default security.xml
file it contains the following near the top
...
that is a user can be identified by their Windows userid but their roles can be read from a database or the user.properties file rather than from Active Directory.
Info |
---|
The authentication of the Windows user information if performed using the SMB protocol. |
The configuration for Windows and Active Directory authentication is done via the security.xml
file as all the authentication is.
First up we'll look at how to provide the user with access to the system without having to enter a username/password via Windows integrated authentication. Then we'll look at extending this to also obtain the access levels for the users from the domain.
Integrated Authentication
To implement Windows integrated authentication and allow internal users to login to Weave automatically using their Windows userid involves editing the security.xml
file to replace the default login form with handling from the NTLM processor.
Looking at the default security.xml
file it contains the following near the top:
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
<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>
|
What this section does is to determine which filters are applied to any incoming requests, passing everything that matched /server/**
through the first list, and everything else through the second.
What we want to do here to enable Windows authentication is add an additional filter to perform the NTLM authentication steps when required. But before we add the new filter to the list
That should be the final new section we need to add the new definition for the filter object itself.To do this we add a new section to the security.xml
file to define a new NtlmProcessingFilter
, 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 | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
<bean id="ntlmProcessingFilterfilterChainProxy" class="org.acegisecurity.uiutil.ntlm.NtlmProcessingFilterFilterChainProxy"> <property name="defaultDomain"><value>DOMAINNAME</value></property> filterInvocationDefinitionSource"> <property name="domainController"><value>172.16.0.30</value></property> <property name="authenticationEntryPoint" ref="ntlmEntryPoint"/> <property name="authenticationManager" ref="ntlmAuthenticationManager"/> </bean> |
The two values in there DOMAINNAME
and 172.16.0.30
need to be replaced with values that are appropriate for your environment.
This is the filter that we will eventually add to the filter list, but we first need to create two more sections that this references, the ntlmEntryPoint
and the ntlmAuthenticationManager
.
Code Block | |
---|---|
xml | xml<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>
|
Here we've added the ntlmProcessingFilter
to the list of filters that will be applied to the incoming requests.
Now we need to create the ntlmProcessingFilter
filter and configure it to use the local domain.
To do this we add a new section to the security.xml
file to define a new NtlmProcessingFilter
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
<bean id="ntlmEntryPointntlmProcessingFilter" class="org.acegisecurity.ui.ntlm.NtlmProcessingFilterEntryPointNtlmProcessingFilter"/> <bean<property idname="ntlmAuthenticationManager" class="org.acegisecurity.providers.ProviderManager">defaultDomain"><value>DOMAINNAME</value></property> <property name="domainController"><value>172.16.0.30</value></property> <property name="providersauthenticationEntryPoint"> <list> <ref local ref="smbAuthenticationProviderntlmEntryPoint"/> <bean class="org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider"> <property name="keyauthenticationManager" valueref="changeThisntlmAuthenticationManager"/> </bean> <bean class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider"> <property name="key" value="changeThis"/> </bean> </list> </property> </bean> |
...
The two values in there DOMAINNAME
and 172.16.0.30
need to be replaced with values that are appropriate for your environment.
This is the filter that we added to the filter list, now we need to create two more sections that this filter references, the ntlmEntryPoint
and the ntlmAuthenticationManager
.
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 | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
<bean id="smbAuthenticationProviderntlmEntryPoint" class="org.acegisecurity.providersui.smbntlm.SmbNtlmAuthenticationProviderNtlmProcessingFilterEntryPoint"/> |
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
<bean id="ntlmAuthenticationManager" class="org.acegisecurity.providers.ProviderManager"> <property name="authorizationProviderproviders"> <list> <ref local="nullDaoAuthenticationProvidersmbAuthenticationProvider"/> </property> </bean> |
Again a fairly simple section, but again it references yet another section that we need to add, the nullDaoAuthenticationProvider
.
Code Block | |
---|---|
xml | xml |
linenumbers | true | <bean id="nullDaoAuthenticationProvider" <bean class="org.acegisecurity.providers.smbanonymous.NullPasswordDaoAuthenticationProviderAnonymousAuthenticationProvider"> <property name="userDetailsServicekey" refvalue="userDetailsServicechangeThis"/> <property name="userCache"> </bean> <bean class="org.acegisecurity.providers.daorememberme.cache.EhCacheBasedUserCacheRememberMeAuthenticationProvider"> <property name="cachekey"> <bean class="org.springframework.cache.ehcache.EhCacheFactoryBean" value="changeThis"/> <property name="cacheManager"></bean> </list> <bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/> </property> </property> </bean> |
The ntlmEntryPoint
does not require any configuration, but we can see that the ntlmAuthenticationManager
references yet another item that we need to add, the smbAuthenticationManager
.
The smbAuthenticationManager
provides authentication via the SMB protocol of the authentication information extracted by the NtlmProcessingFilter
.
Note |
---|
The |
Setting up the smbAuthenticationProvider
is just a matter of configuring the SmbNtlmAuthenticationProvider
with the authorizationProvider
provider to be used.
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
<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 | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
<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>
|
If you're starting with the default security.xml
file that should be the final new section we need to add, since the userDetailsService
that it references should already exist. And you could restart the server and assuming that the users.properties
file has an entry for each user they should be able to log in without having to enter a username/password.
Customising the the SMB authentication process
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 | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
<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>
|
Additional properties that can effect the SMB authentication process can be found here.
Selectively applying NTLM authentication
You can specify what IP addresses you want NTLM authentication to apply to, or not apply to, allowing you to support NTLM authentication for internal users and bypass it for external ones, for example (this prevents external users from being presented with a username/password dialogue box that they will probably not have valid values for).
To do this you need to replace the ntlmProcessingFilter, rather than using the org.acegisecurity.ui.ntlm.NtlmProcessingFilter
class you should use the org.acegisecurity.ui.ntlm.IPFilteredNtlmProcessingFilter
, this implementation of the NtlmProcessingFilter
can then be provided with additional configuration items specifying which IP addresses should/shouldn't be provided with the option to authenticate using NTLM.
Note |
---|
All the previous configuration items still apply, and should still be set, for the |
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 | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
<bean id="ntlmProcessingFilter" class="org.acegisecurity.ui.ntlm.NtlmProcessingFilter"> <property name="cacheName" value="userCache"/> </bean> </property> </bean> </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 |
linenumbers | true | <bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy"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="filterInvocationDefinitionSourceexcludedIpAddresses"> <list> <value>192.168.2.0/24</value> <value> <value>138.19.19.50</value> CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_APACHE_ANT /server/**=httpSessionContextIntegrationFilter,ntlmProcessingFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,jsonExceptionTranslationFilter,filterInvocationInterceptor</list> </property> <property name="includedIpAddresses"> <list> /**=httpSessionContextIntegrationFilter,ntlmProcessingFilter,logoutFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor<value>172.16.0.0/16</value> </value>list> </property> </bean> |
Here you can see that we've added the ntlmProcessingFilter
to the two filter lists, this should allow used that are logged into Windows to have that username automatically recognized by Weave.
But that doesn't take care of determining what the user has access to once they're logged in. That will still need to be done via the users.properties
file (to map usernames to roles), unless you setup security.xml
to utilise information stored in Active Directory.
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.
...
You don't need to provide both excludedIpAddresses
and includedIpAddresses
, in fact it's more than likely that you'll only want to provide one, either listing those addresses that should be NTLM authenticated, and everyone else isn't, or listing those addresses that should not be NTLM authenticated and everyone else should. But, if you do provide both then the exclude list is checked first. Also, if the include list is set then the IP address must appear in the list for NTLM authentication to be attempted.
Info |
---|
The IPFilteredNtlmProcessingFilter class is provided in version 1.0.7 or later of the |
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 domainRole
, 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.
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
<bean id="ntlmProcessingFilterntlmProcessingFilterInternal" class="org.acegisecurity.ui.ntlm.NtlmProcessingFilter"> <property name="defaultDomain"><value>DOMAINNAME<><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 = "JCifsProperties"> <map> <entry<property keyname="jcifs.smb.client.username"> <value>username</value> </entry> <entry key="jcifs.smb.client.password"> <value>password</value> </entry> </map> </property> </bean> |
Selectively applying NTLM authentication
You can specify what IP addresses you want NTLM authentication to apply to, or not apply to, allowing you to support NTLM authentication for internal users and bypass it for external ones, for example (this prevents external users from being presented with a username/password dialogue box that they will probably not have valid values for).
...
authenticationEntryPoint" ref="ntlmEntryPoint"/> <property name="authenticationManager" ref="ntlmAuthenticationManager"/> <property name="doaminRole"><value>ROLE_INTERNAL</value></property> <property name="includedIpAddresses"> <list> <value>172.16.0.0/16</value> </list> </property> </bean> <bean id="ntlmProcessingFilterExternal" class="org.acegisecurity.ui.ntlm.NtlmProcessingFilter |
...
Note |
---|
All the previous configuration items still apply, and should still be set, for the |
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 |
linenumbers | true | <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="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="doaminRole"><value>ROLE_EXTERNAL</value></property> <property name="includedIpAddresses"> <list> <value>201.20.0.0/16</value> </list> </property> </bean> |
Not that to enable this both filters need to be added to the filter chain:
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy"> <property name="excludedIpAddressesfilterInvocationDefinitionSource"> <list><value> <value>192.168.2.0/24</value> <value>138.19.19.50</value> </list> </property> <property name="includedIpAddresses"> <list>CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_APACHE_ANT <value>172.16.0.0/16</value> </list> </property> </bean> |
You don't need to provide both excludedIpAddresses
and includedIpAddresses
, in fact it's more than likely that you'll only want to provide one, either listing those addresses that should be NTLM authenticated, and everyone else isn't, or listing those addresses that should not be NTLM authenticated and everyone else should. But, if you do provide both then the exclude list is checked first. Also, if the include list is set then the IP address must appear in the list for NTLM authentication to be attempted.
Info |
---|
The IPFilteredNtlmProcessingFilter class is provided in version 1.0.7 or later of the org.acegisecurity.ntlm bundle/server/**=httpSessionContextIntegrationFilter,ntlmProcessingFilter1,ntlmProcessingFilter2,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,jsonExceptionTranslationFilter,filterInvocationInterceptor
/**=httpSessionContextIntegrationFilter,ntlmProcessingFilter1,ntlmProcessingFilter2,logoutFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
</value>
</property>
</bean>
|
Active Directory Groups
Information about what active directory groups a user belongs to can be used to provide role information to Weave for the users that are authenticated using Windows integrated authentication, removing the need to utilise the users.properties
file.
This information is obtained from an AD domain controller using the LDAP specificationcontroller using the LDAP protocol.
When setting up the LDAP integration it's recommended that the JXplorer tool be used to test the settings, because the JXplorer uses the LDAP server in the same way as Weave but provides an interactive method for verifying the settings.
To enable LDAP as a source of authentication information the ntlmAuthenticationManager
we created earlier needs to be altered to use an LdapAuthenticationProvider
rather than the SmbAuthenticationProvider
.
...
This provider uses two other beans item to provide information, the authenticationLdap
bean and the populatorLdap
bean.
...
Which as we can see requires two other beansitems, the initialDirContextFactory
and the userSearchLdap
.
The initialDirContextFactory
is also used by the userSearchLdap
and the populaterLdap
beans so we'll look at that first
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
<bean id="initialDirContextFactory" class="org.acegisecurity.ldap.DefaultInitialDirContextFactory"> <constructor-arg value="ldap://172192.16168.0.3016:389/"/> <property name="managerDn"> <value>CN=usernameAdministrator,OU=Users,DC=examplecohga,DC=com,DC=au<local</value> </property> <property name="managerPassword"> <value>password</value> </property> </bean> |
...
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<bean id="userSearchLdap" class="org.acegisecurity.ldap.search.FilterBasedLdapUserSearch"> <constructor-arg> <value>OU=Users,DC=examplecohga,DC=com,DC=au<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 | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
<bean id="populatorLdap" class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator"> <constructor-arg> <ref local="initialDirContextFactory"/> </constructor-arg> <constructor-arg> <value>OU=Users,DC=examplecohga,DC=com,DC=au<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> |
...