4

I'm trying to use Waffle authentication with Spring Security, in a Spring Boot fashion. Expected result is 'block everything if Negotiate fails'.

Waffle project provides a configuration example for this kind of use case (there is in this example a fallback to simple HTTP auth if Negotiate fails, which I don't need), assuming configuration is done through web.xml. But despite many attempts, I don't understand how to plug Waffle with Spring Security using Boot and Java-only configuration. I'm using Spring Boot 1.2.1.RELEASE with starters web and security, Waffle version is 1.7.3.

I realize that this is not a specific question but Spring forum now redirects here and Waffle guys don't know about Spring Boot. Could someone help me translate an XML Spring Security configuration to Spring Boot?

First step is declaring a filter chain and context loader listener.

<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>

I'm assuming (am I wrong?) that this is already handled by @EnableWebMvcSecurity, so nothing to do here.

Next is declaring a couple of provider beans, so I translate this

<bean id="waffleWindowsAuthProvider" class="waffle.windows.auth.impl.WindowsAuthProviderImpl" />

<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>

<bean id="waffleNegotiateSecurityFilter" class="waffle.spring.NegotiateSecurityFilter">
    <property name="Provider" ref="waffleSecurityFilterProviderCollection" />
</bean>

to this

@Bean
public WindowsAuthProviderImpl waffleWindowsAuthProvider() {
    return new WindowsAuthProviderImpl();
}

@Bean
@Autowired
public NegotiateSecurityFilterProvider negotiateSecurityFilterProvider(final WindowsAuthProviderImpl windowsAuthProvider) {
    return new NegotiateSecurityFilterProvider(windowsAuthProvider);
}

@Bean
@Autowired
public BasicSecurityFilterProvider basicSecurityFilterProvider(final WindowsAuthProviderImpl windowsAuthProvider) {
    return new BasicSecurityFilterProvider(windowsAuthProvider);
}

@Bean
@Autowired
public SecurityFilterProviderCollection waffleSecurityFilterProviderCollection(final NegotiateSecurityFilterProvider negotiateSecurityFilterProvider, final BasicSecurityFilterProvider basicSecurityFilterProvider) {
    final SecurityFilterProvider[] securityFilterProviders = {
            negotiateSecurityFilterProvider,
            basicSecurityFilterProvider
    };
    return new SecurityFilterProviderCollection(securityFilterProviders);
}

@Bean
@Autowired
public NegotiateSecurityFilter waffleNegotiateSecurityFilter(final SecurityFilterProviderCollection securityFilterProviderCollection) {
    final NegotiateSecurityFilter negotiateSecurityFilter = new NegotiateSecurityFilter();
    negotiateSecurityFilter.setProvider(securityFilterProviderCollection);
    return negotiateSecurityFilter;
}

Final step is sec:http section configuration. An entry point is declared and filter is placed before BASIC auth filter.

Example:

<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>

My Boot translation:

@Autowired
private NegotiateSecurityFilterEntryPoint authenticationEntryPoint;

@Autowired
private NegotiateSecurityFilter negotiateSecurityFilter;    

@Override
protected void configure(final HttpSecurity http) throws Exception {
    http
            .authorizeRequests().anyRequest().authenticated()
            .and()
            .addFilterBefore(this.negotiateSecurityFilter, BasicAuthenticationFilter.class)
            .httpBasic().authenticationEntryPoint(this.authenticationEntryPoint);
}

@Bean
@Autowired
public NegotiateSecurityFilterEntryPoint negotiateSecurityFilterEntryPoint(final SecurityFilterProviderCollection securityFilterProviderCollection) {
    final NegotiateSecurityFilterEntryPoint negotiateSecurityFilterEntryPoint = new NegotiateSecurityFilterEntryPoint();
    negotiateSecurityFilterEntryPoint.setProvider(securityFilterProviderCollection);
    return negotiateSecurityFilterEntryPoint;
}

Running this configuration leads to strange behavior: sometimes NTLM is triggered and succeed, sometimes Negotiate filter crashes with an 'invalid token supplied' error (same credentials, user, browser, configuration).

Provided example works like a charm, which makes me think that my Boot configuration is in question.

Any help appreciated!

LeRiton
  • 313
  • 1
  • 4
  • 10
  • If the filter fires I suppose it must be working. I really don't know anything else about waffle (but it looks interesting). One difference I spotted with your code and the XML sample is that you only installed the custom entry point into the HTTP basic filter (and not the exception handling for the chain overall). Probably you need to do that, but I don't know if it will fix everything. – Dave Syer Mar 12 '15 at 08:08
  • Thanks for your feedback. It doesn't fix 'invalid token' issue (I'll ping again the Waffle team for that), but it's a start. – LeRiton Mar 13 '15 at 12:19
  • Any updates on this? I am having the same problem (I think). Waffle on Spring Boot. It seems to work OK in Chrome and possibly other browsers, but it fails when using IE11. Server logs a Win32Exception "The token supplied to the function is invalid" – Mike May 02 '16 at 16:41
  • I'm not working on this issue anymore, but I'll be happy to learn more about that. Please let me know if you find something useful, I think I can make some test if needed. – LeRiton May 23 '16 at 13:56

1 Answers1

2

Spring Boot auto-registers all Filter beans so in this case the NegotiateSecurityFilter ends up being twice in the filter chain.

You have to disable the auto-registration for this specific Filter by creating a FilterRegistrationBean overriding this behavior:

@Bean
public FilterRegistrationBean registration(NegotiateSecurityFilter filter) {
    FilterRegistrationBean registration = new FilterRegistrationBean(filter);
    registration.setEnabled(false);
    return registration;
}

Also, as Dave Syer mentioned, you should be setting the authentication entry point bean using the ExceptionHandlingConfigurer.

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.exceptionHandling()
        .authenticationEntryPoint(authenticationEntryPoint);
    // ...
}
krisse7
  • 21
  • 4
  • Thanks for the clarification! Unfortunately, I am no longer working on any project using Waffle, so I am unable to test your proposal. If someone could give it a try and tell us, I would be glad to approve your answer. – LeRiton Feb 28 '17 at 15:24
  • Yes! This worked for me (Spring Boot 2.2 + Waffle 2.2.1) – Brice Roncace Mar 24 '20 at 16:20