Spring Security Single-SignOn Filter
July 6, 2022 ยท View on GitHub
The Waffle Spring-Security Filter implements the Negotiate and Basic protocols with Kerberos and NTLM single sign-on support for web applications that utilize Spring-Security. This allows users to browse to a Windows intranet site without having to re-enter credentials for browsers that support Kerberos or NTLM and to fall back to Basic authentication for those that do not. For more information about Spring-Security see here. NOTE: Also available with delegation to support authentication for the service provider [here] (https://github.com/dblock/waffle/blob/master/Docs/spring/DelegatingSpringSecuritySingleSignOnFilter.md) Configuring Spring Security
The following steps are required to configure a web server with the Waffle Spring-Security Filter.
We'll assume that Spring-Security is configured via web.xml with a filter chain and a Spring context loader listener. The Waffle beans configuration will be added to waffle-filter.xml.
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/waffle-filter.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
Copy Waffle JARs, including waffle-jna.jar, caffeine.jar, jna.jar, jna-platform.jar, slf4j-api.jar and waffle-spring-security5.jar in the application's lib directory along with Spring and Spring-Security JARs. Or, if you use Maven, add the following to your pom.xml:
- Use specific versions as bundled in waffle-distro
<dependency>
<groupId>com.github.dblock.waffle</groupId>
<artifactId>waffle-spring-security4</artifactId>
<version>${waffle.version}</version>
</dependency>
Declare a Windows Authentication provider. This is the link between Waffle and the operating system.
<bean id="waffleWindowsAuthProvider" class="waffle.windows.auth.impl.WindowsAuthProviderImpl" />
Declare a collection of Waffle security filter providers that implement various authentication protocols.
<bean id="negotiateSecurityFilterProvider" class="waffle.servlet.spi.NegotiateSecurityFilterProvider">
<constructor-arg ref="waffleWindowsAuthProvider" />
</bean>
<bean id="basicSecurityFilterProvider" class="waffle.servlet.spi.BasicSecurityFilterProvider">
<constructor-arg ref="waffleWindowsAuthProvider" />
</bean>
<bean id="waffleSecurityFilterProviderCollection" class="waffle.servlet.spi.SecurityFilterProviderCollection">
<constructor-arg>
<list>
<ref bean="negotiateSecurityFilterProvider" />
<ref bean="basicSecurityFilterProvider" />
</list>
</constructor-arg>
</bean>
Add the Waffle security filter and entry point to the sec:http configuration section. The filter will be placed before the BASIC authentication filter that ships with Spring-Security. The filter uses the collection of authentication filter providers defined above to perform authentication.
<sec:http entry-point-ref="negotiateSecurityFilterEntryPoint">
<sec:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY" />
<sec:custom-filter ref="waffleNegotiateSecurityFilter" position="BASIC_AUTH_FILTER" />
</sec:http>
<bean id="negotiateSecurityFilterEntryPoint" class="waffle.spring.NegotiateSecurityFilterEntryPoint">
<property name="Provider" ref="waffleSecurityFilterProviderCollection" />
</bean>
Define a required default Spring-Security authentication manager.
<sec:authentication-manager alias="authenticationProvider" />
Define a Spring-Security filter that uses the collection of security filter providers to perform authentication.
<bean id="waffleNegotiateSecurityFilter" class="waffle.spring.NegotiateSecurityFilter">
<property name="Provider" ref="waffleSecurityFilterProviderCollection" />
</bean>
Granted Authorities
Upon successful login, Waffle will populate Spring Security's Authentication instance with GrantedAuthority objects.
By default, Waffle will populate the Authentication instance with the following GrantedAuthority objects:
- A
GrantedAuthoritywith the stringROLE_USER. - One
GrantedAuthorityper group to which the user belongs. TheGrantedAuthoritystrings will be the uppercase group name prefixed withROLE_. For example, if a user is a member of theEveryonegroup, he obtains theROLE_EVERYONEgranted authority.
The default behavior can be changed by configuring a different defaultGrantedAuthority and grantedAuthorityFactory on the waffleNegotiateSecurityFilter object.
Security Filter Options
The waffleNegotiateSecurityFilter bean can be configured with the following options.
- principalFormat: Specifies the name format for the principal.
- roleFormat: Specifies the name format for the role.
- allowGuestLogin: Allow guest login. When true and the system's Guest account is enabled, any invalid login succeeds as Guest. Note that while the default value of allowGuestLogin is true, it is recommended that you disable the system's Guest account to disallow Guest login. This option is provided for systems where you don't have administrative privileges.
- impersonate: Allow impersonation. When true the remote user will be impersonated. Note that there is no mapping between the Windows native threads, under which the impersonation takes place, and the Java threads. Thus you'll need to use Windows native APIs to perform impersonated actions. Any action done in Java will still be performed with the user account running the servlet container.
- defaultGrantedAuthority: Specifies the
GrantedAuthorityto be added to every successfully authenticated user. By default, thedefaultGrantedAuthoritywill add aGrantedAuthorityforROLE_USER. If you do not want this behavior, you can set thedefaultGrantedAuthoritytonull(if you do not want aGrantedAuthorityto be added by default), or some otherGrantedAuthority. - grantedAuthorityFactory: Used to create
GrantedAuthorityobjects for each of the groups to which the authenticated user belongs. The defaultgrantedAuthorityFactorywill constructGrantedAuthorityobjects whose string is the uppercase group name prefixed withROLE_.
<bean id="waffleNegotiateSecurityFilter" class="waffle.spring.NegotiateSecurityFilter">
<property name="Provider" ref="waffleSecurityFilterProviderCollection" />
<property name="AllowGuestLogin" value="false" />
<property name="Impersonate" value="true" />
<property name="PrincipalFormat" value="fqn" />
<property name="RoleFormat" value="both" />
</bean>
Security Filter Provider Collection Options
The waffleSecurityFilterProviderCollection bean can be constructed with a list of available security filter providers. Waffle supplies a Negotiate and a BASIC authentication security filter provider. In addition, each provider may implement more options.
<bean id="waffleSecurityFilterProviderCollection" class="waffle.servlet.spi.SecurityFilterProviderCollection">
<constructor-arg>
<list>
<ref bean="negotiateSecurityFilterProvider" />
<ref bean="basicSecurityFilterProvider" />
</list>
</constructor-arg>
</bean>
Negotiate Security Filter Provider Options
The negotiateSecurityFilterProvider bean supports a list of protocols. Choose one or the combination of Negotiate and NTLM.
<bean id="negotiateSecurityFilterProvider" class="waffle.servlet.spi.NegotiateSecurityFilterProvider">
<constructor-arg ref="waffleWindowsAuthProvider" />
<property name="protocols">
<list>
<value>Negotiate</value>
<value>NTLM</value>
</list>
</property>
</bean>
Basic Security Filter Provider Options
The basicSecurityFilterProvider bean supports a custom Basic authentication Realm name.
<bean id="basicSecurityFilterProvider" class="waffle.servlet.spi.BasicSecurityFilterProvider">
<constructor-arg ref="waffleWindowsAuthProvider" />
<property name="realm" value="DemoRealm" />
</bean>
Mixed Single-SignOn and Form
To support both single sign-on and form-based authentication with spring security similarly to TomcatMixedSingleSignOnAndFormAuthenticatorValve.
Split single sign-on and form based authentication in dedicated entry point configurations:
<sec:http pattern="/waffle" entry-point-ref="negotiateSecurityFilterEntryPoint">
<sec:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY" />
<sec:custom-filter ref="waffleNegotiateSecurityFilter" position="BASIC_AUTH_FILTER" />
</sec:http>
<sec:http>
<sec:intercept-url pattern="/login.jsp" access="permitAll" />
<sec:form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?error=true"/>
<sec:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY" />
</sec:http>
<sec:authentication-manager alias="authenticationProvider">
<sec:authentication-provider ref="waffleSpringAuthenticationProvider" />
</sec:authentication-manager>
<bean id="waffleSpringAuthenticationProvider" class="waffle.spring.WindowsAuthenticationProvider">
<property name="authProvider" ref="waffleWindowsAuthProvider" />
</bean>
Create a login page based on the following code. There're two requirements for the login form. The form-based authentication must post to the login processing url location. The single sign-on link must redirect to the single sign-on entry point path.
<form method="POST" name="loginform" action="<%=request.getContextPath()%>/login">
<table style="vertical-align: middle;">
<tr>
<td>Username:</td>
<td><input type="text" name="username" /></td>
</tr>
<tr>
<td>Password:</td>
<td><input type="password" name="password" /></td>
</tr>
<tr>
<td><input type="submit" value="Login" /></td>
</tr>
</table>
</form>
<hr>
<a href="<%=request.getContextPath()%>/waffle">
Login (Negotiate)
</a>
Defining the redirection after a successful single sign-on authentication can be achieved by registering a redirect servlet with "/waffle" url-mapping:
@WebServlet("/waffle")
public class RedirectServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.sendRedirect(resp.encodeRedirectURL(req.getContextPath() + "/index.jsp"));
}
}
Waffle Spring-Security Demo
A demo application can be found in the Waffle distribution in the Samples\waffle-spring-filter directory. Copy the entire directory into Tomcat's or Jetty's webapps directory and navigate to http://localhost:8080/waffle-spring-filter/.