Search code examples
spring-securityspring-bootspring-wsws-security

Spring boot using Spring Security authentication failure when using SpringPlainTextPasswordValidationCallbackHandler in an XwsSecurityInterceptor


I have set a up a spring boot (1.2.3) application with spring security and spring-ws. I have configured spring security to use .ldapAuthentication() for authentication in my WebSecurityConfigurerAdapter. I am trying to get the same spring security authenticationManager to authenticate my spring ws SOAP web services using ws-security usernametokens (plain text) in my WsConfigurerAdapter.

I have configured my WebSecurityConfigurerAdapter like this:

package za.co.switchx.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    @ConfigurationProperties(prefix="ldap.contextSource")
    public LdapContextSource contextSource() {
        LdapContextSource contextSource = new LdapContextSource();
        return contextSource;
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .ldapAuthentication()
                .userSearchBase("cn=Users,dc=SwitchX,dc=co,dc=za")
                .userSearchFilter("(uid={0})")
                .groupSearchBase("cn=Groups,dc=SwitchX,dc=co,dc=za")
                .groupSearchFilter("(&(cn=*)(|    (objectclass=groupofUniqueNames)(objectclass=orcldynamicgroup)))")
                .contextSource(contextSource());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/ws/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .csrf().disable()
            .httpBasic();
    }   
}

So then I went to configure my WsConfigurerAdapter like this:

package za.co.switchx.config;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.ws.config.annotation.EnableWs;
import org.springframework.ws.config.annotation.WsConfigurerAdapter;
import org.springframework.ws.transport.http.MessageDispatcherServlet;
import org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition;
import org.springframework.xml.xsd.SimpleXsdSchema;
import org.springframework.xml.xsd.XsdSchema;
import org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor;
import org.springframework.ws.soap.security.xwss.callback.SpringPlainTextPasswordValidationCallbackHandler;

import org.springframework.ws.server.EndpointInterceptor;

@EnableWs
@Configuration
public class WebServiceConfig extends WsConfigurerAdapter {

    @Bean
    public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
        MessageDispatcherServlet servlet = new MessageDispatcherServlet();
        servlet.setApplicationContext(applicationContext);
        servlet.setTransformWsdlLocations(true);
        return new ServletRegistrationBean(servlet, "/ws/*");
    }

    @Bean(name = "ApplicantTypeService")
    public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema applicantTypeServiceSchema) {
        DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
        wsdl11Definition.setPortTypeName("ApplicantTypePort");
        wsdl11Definition.setLocationUri("/ws/ApplicantTypeService");
        wsdl11Definition.setTargetNamespace("http://switchx.co.za/services/applicant/types/applicant-type-web-service");
        wsdl11Definition.setSchema(applicantTypeServiceSchema);
        return wsdl11Definition;
    }

    @Bean
    public XsdSchema applicantTypeSchema() {
        return new SimpleXsdSchema(new ClassPathResource("xsd/ApplicantTypeService.xsd"));
    }

    @Bean
    public XwsSecurityInterceptor securityInterceptor() {

        XwsSecurityInterceptor securityInterceptor = new XwsSecurityInterceptor();
        securityInterceptor.setCallbackHandler(new SpringPlainTextPasswordValidationCallbackHandler());
        securityInterceptor.setPolicyConfiguration(new ClassPathResource("securityPolicy.xml"));
        return securityInterceptor;
    }

    @Override
    public void addInterceptors(List<EndpointInterceptor> interceptors) {
        interceptors.add(securityInterceptor());
    }
}

If I use a SimplePasswordValidationCallbackHandler in the XwsSecurityInterceptor it does authenticate the ws usernametoken correctly, so I know there is nothing wrong with the ws-security section. And if I logon via http basic it authenticates my ldap user correctly so I know that works.

The problem is that when I try use my ldap user logon in the ws security usernametoken I get ERROR c.s.xml.wss.logging.impl.filter - WSS1408: UsernameToken Authentication Failed in the logs, so looks like its not using my global ldap authentication defined in the WebSecurityConfigAdapter

I cant seem to figure out how to get the SpringPlainTextPasswordValidationCallbackHandler (which is supposed to use spring security) in the XwsSecurityInterceptor to use the global authenticationManager, please help?? I have really been bashing my head against this for the last day but cant seem to win.


Solution

  • Ok I figured this out so though I would post for anyone trying this in the future.

    I resolved this problem by changing my spring boot class to:

    @SpringBootApplication
    @EnableGlobalMethodSecurity(securedEnabled = true)
    public class SwitchxApplication extends WebMvcConfigurerAdapter {
    
        @SuppressWarnings("unused")
        private static final Logger log = LoggerFactory.getLogger(SwitchxApplication.class);
    
        @Bean
        public ApplicationSecurity applicationSecurity() {
            return new ApplicationSecurity();
        }
    
        @Configuration
        @Order(Ordered.HIGHEST_PRECEDENCE)
        protected static class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter {              
    
            @Bean
            @ConfigurationProperties(prefix="ldap.contextSource")
            public LdapContextSource contextSource() {
                LdapContextSource contextSource = new LdapContextSource();
                return contextSource;
            }
    
            @Override
            public void init(AuthenticationManagerBuilder auth) throws Exception {
                auth
                    .ldapAuthentication()
                        .userSearchBase("cn=Users,dc=Blah,dc=co,dc=za")
                        .userSearchFilter("(uid={0})")
                        .groupSearchBase("cn=Groups,dc=Blah,dc=co,dc=za")
                        .groupSearchFilter("(&(cn=*)(|(objectclass=groupofUniqueNames)(objectclass=orcldynamicgroup)))")
                        .contextSource(contextSource());
            }
        }
    
    @Order(Ordered.LOWEST_PRECEDENCE - 8)
    protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter {       
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
            .authorizeRequests()
                .antMatchers("/ws/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .csrf().disable()
            .httpBasic();
        }       
    }
    
        public static void main(String[] args) {
            SpringApplication.run(SwitchxApplication.class, args);
        }
    }
    

    And then made the following relevant changes in my WsConfigurerAdapter to:

    @EnableWs
    @Configuration  
    public class WebServiceConfig extends WsConfigurerAdapter {
    
        private static final Logger log = LoggerFactory.getLogger(WebServiceConfig.class);
    
        @Autowired
        private AuthenticationManager authenticationManager;
    
        @Bean
        public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
            MessageDispatcherServlet servlet = new MessageDispatcherServlet();
            servlet.setApplicationContext(applicationContext);
            servlet.setTransformWsdlLocations(true);
            return new ServletRegistrationBean(servlet, "/ws/*");
        }
    
        .....
        .....
    
        @Bean
        public SpringPlainTextPasswordValidationCallbackHandler callbackHandler() {
            SpringPlainTextPasswordValidationCallbackHandler callbackHandler = new SpringPlainTextPasswordValidationCallbackHandler();
            try { 
                callbackHandler.setAuthenticationManager(authenticationManager);
            } catch(Exception e) {
                log.error(e.getMessage());
            }
            return callbackHandler;
        }
    
        @Bean
        public XwsSecurityInterceptor securityInterceptor() {
    
            XwsSecurityInterceptor securityInterceptor = new XwsSecurityInterceptor();
            securityInterceptor.setCallbackHandler(callbackHandler());
            securityInterceptor.setPolicyConfiguration(new ClassPathResource("securityPolicy.xml"));
            return securityInterceptor;
        }
    
        @Override
        public void addInterceptors(List<EndpointInterceptor> interceptors) {
            interceptors.add(securityInterceptor());
        }
    }
    

    So basically the end result is that for all /ws paths the basic http security is ignored but because of the security intercepter in the WS Config it will use a basic ws-security user name token to authenticate web service calls, allowing you to have both authentication mechanisms using spring security set up with ldap.

    I hope this helps someone, was a bit tricky not finding of lot of documentation on the boot and java config documentation on this particular setup and stuff as it still relatively new. But after not getting this working, its pretty awesome and Im very impressed.