Spring WS: How to apply Interceptor to a specific endpoint

Christophe Douy picture Christophe Douy · Sep 24, 2015 · Viewed 16.1k times · Source

I have multiple working SOAP Web Services on a Spring application, using httpBasic authentication, and I need to use WS-Security instead on one of them to allow authentication with the following Soap Header.

<soap:Header><wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soap:mustUnderstand="1">
  <wsse:UsernameToken xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="UsernameToken-1">
    <wsse:Username>username</wsse:Username>
    <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">password</wsse:Password>
  </wsse:UsernameToken>
</wsse:Security></soap:Header>

Current WSConfiguration was done according to https://github.com/spring-projects/spring-boot/blob/master/spring-boot-samples/spring-boot-sample-ws/ giving something like

@EnableWs
@Configuration
public class WebServiceConfig extends WsConfigurerAdapter {

    @Bean
    public ServletRegistrationBean dispatcherServlet(ApplicationContext applicationContext) {
        MessageDispatcherServlet servlet = new MessageDispatcherServlet();
        servlet.setApplicationContext(applicationContext);
        return new ServletRegistrationBean(servlet, "/services/*");
    }

    @Bean(name = "SOAP1")
    public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema soap1) {
        DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
        wsdl11Definition.setPortTypeName("Soap1");
        wsdl11Definition.setLocationUri("/soap1/");
        wsdl11Definition.setTargetNamespace("http://mycompany.com/hr/definitions");
        wsdl11Definition.setSchema(soap1);
        return wsdl11Definition;
    }

    @Bean
    public XsdSchema soap1() {
        return new SimpleXsdSchema(new ClassPathResource("META-INF/schemas/hr.xsd"));
    }

}

and Web Security according to http://spring.io/blog/2013/07/03/spring-security-java-config-preview-web-security/ looks like this

@EnableWebSecurity
@Configuration
public class CustomWebSecurityConfigurerAdapter extends
   WebSecurityConfigurerAdapter {
  @Autowired
  public void configureGlobal(AuthenticationManagerBuilder auth) {
    auth
      .inMemoryAuthentication()
        .withUser("user1")  
          .password("password")
          .roles("SOAP1")
          .and()
        .withUser("user2") 
          .password("password")
          .roles("SOAP2");
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
      .authorizeUrls()
        .antMatchers("/soap/soap1").hasRole("SOAP1") 
        .antMatchers("/soap/soap2").hasRole("SOAP2") 
        .anyRequest().authenticated() 
        .and().httpBasic();
  }
}

After some searches, I found that Wss4J provides a UsernameToken authentication, but can't figure out how to use it. What I'm trying to do is the following https://sites.google.com/site/ddmwsst/ws-security-impl/ws-security-with-usernametoken but without XML files with bean definitions.

What I plan to do:

  • Create the Callback Handler.
  • Create a Wss4jSecurityInterceptor, setting "setValidationActions" to "UsernameToken", "setValidationCallbackHandler" to my callback handler, and then add it by overriding addInterceptors on my WebServiceConfig.

(I tried something like that, but I just realised my callback was using a deprecated method)

Problem : Even if it works, it would then apply to all my webservices on "WebServiceConfig".

Update :

The implementation does work, but as expected it is applied to all my Web Services. How could I add my interceptor only to 1 Web Service ?

Following, the code I added in WebServiceConfig

 @Bean
    public Wss4jSecurityInterceptor wss4jSecurityInterceptor() throws IOException, Exception{
        Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor();
        interceptor.setValidationActions("UsernameToken");
        interceptor.setValidationCallbackHandler(new Wss4jSecurityCallbackImpl());

    return interceptor;
}

@Override
public void addInterceptors(List<EndpointInterceptor> interceptors)  {
    try {
        interceptors.add(wss4jSecurityInterceptor());
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Answer

Christophe Douy picture Christophe Douy · Mar 3, 2016

Sorry, I totally forgot to answer this, but in case it helps someone :

We got it working by creating a new SmartEndpointInterceptor, and applying it only to our endpoint:

public class CustomSmartEndpointInterceptor extends Wss4jSecurityInterceptor implements SmartEndpointInterceptor {

    //CustomEndpoint is your @Endpoint class
    @Override
    public boolean shouldIntercept(MessageContext messageContext, Object endpoint) {
        if (endpoint instanceof MethodEndpoint) {
            MethodEndpoint methodEndpoint = (MethodEndpoint)endpoint;
            return methodEndpoint.getMethod().getDeclaringClass() == CustomEndpoint.class; 
        }
        return false;
    }
}

instead of adding a wss4j bean to the WebServiceConfig, we added our SmartEndpointInterceptor :

@Configuration
public class SoapWebServiceConfig extends WsConfigurationSupport {

    //Wss4jSecurityCallbackImpl refers to an implementation of https://sites.google.com/site/ddmwsst/ws-security-impl/ws-security-with-usernametoken
    @Bean
    public CustomSmartEndpointInterceptor customSmartEndpointInterceptor() {
        CustomSmartEndpointInterceptor customSmartEndpointInterceptor = new CustomSmartEndpointInterceptor();
        customSmartEndpointInterceptor.setValidationActions("UsernameToken");
        customSmartEndpointInterceptor.setValidationCallbackHandler(new Wss4jSecurityCallbackImpl(login, pwd)); 
        return customSmartEndpointInterceptor;
    }

  [...]
}

Hope this is clear enough :)