Windows
Description
Windows security involves two processes, authenticating the user with Weave using their Windows username and using information contained in an Active Directory server to determine what 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
<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 is add an additional filter to perform the NTLM authentication steps when required. But before we add the new filter to the list 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
<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 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
.
<bean id="ntlmEntryPoint" class="org.acegisecurity.ui.ntlm.NtlmProcessingFilterEntryPoint"/> <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>
The ntlmEntryPoint
is pretty straight forward, but we can see that the ntlmAuthenticationManager
references yet another section that we need to add, the smbAuthenticationManager
.
<bean id="smbAuthenticationProvider" class="org.acegisecurity.providers.smb.SmbNtlmAuthenticationProvider"> <property name="authorizationProvider"> <ref local="nullDaoAuthenticationProvider"/> </property> </bean>
Again a fairly simple section, but again it references yet another section that we need to add, the nullDaoAuthenticationProvider
.
<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>
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
<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>
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.
<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>
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 specification.
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
.
So we fist need to change the ntlmAuthenticationManager
to
<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
<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>
This provider uses two other beans to provide information, the authenticationLdap
bean and the populatorLdap
bean.
The authentication would be configured as follows
<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>
Which as we can see requires two other beans, the initialDirContextFactory
and the userSearchLdap
.
The initialDirContextFactory
is also used by the userSearchLdap
and the populaterLdap
beans so we'll look at that first
<bean id="initialDirContextFactory" class="org.acegisecurity.ldap.DefaultInitialDirContextFactory"> <constructor-arg value="ldap://172.16.0.30:389/"/> <property name="managerDn"> <value>CN=username,OU=Users,DC=example,DC=com,DC=au</value> </property> <property name="managerPassword"> <value>password</value> </property> </bean>
Here the ip address, manager distinguished name and manager passwords must be set to appropriate values for a user that can read for the active directory server.
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 as follows
<bean id="userSearchLdap" class="org.acegisecurity.ldap.search.FilterBasedLdapUserSearch"> <constructor-arg> <value>OU=Users,DC=example,DC=com,DC=au</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>
This configuration assumes that there is a branch in the tree matching the first constructor arg and that the sAMAccountName value of any user found there will match the username they logged into Windows with.
Finally the populatorLdap
is responsible for mapping the username to the roles and would be configured as follows
<bean id="populatorLdap" class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator"> <constructor-arg> <ref local="initialDirContextFactory"/> </constructor-arg> <constructor-arg> <value>OU=Users,DC=example,DC=com,DC=au</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>
This "should" take the active directory groups that the user belongs to and convert them to a format that's usable in Weave, and also assigns a default ROLE_USERS role to all users (which you can remove if it's not appropriate).
You will then need to create Weave Access Control Lists utilising the roles that users will be assigned.
You may need to ensure that logging of the security related information is output by weave to be able to determine what roles a user is assigned to when they login. 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)
log4j.logger.org.acegisecurity=DEBUG log4j.logger.com.cohga.server.security=DEBUG
Then when someone is logged in via active directory the log file will show what roles they were granted, which should help you to understand what role names must be used in the access control lists