Search code examples
web-servicesauthenticationspring-ws

Spring WS Client — Authentication using KeyStore/TrustStore and Credentials (Basic Auth)


I have a Spring WS client that needs to authenticate using a keystore/trustore combination and also via basic auth.

This is the relevant Spring config that I currently have:

@Configuration
public class SpringWSConfig {
  @Bean
  public Jaxb2Marshaller jaxb2Marshaller() {
    Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
    marshaller.setPackagesToScan("io.shido.credit.domain");
    return marshaller;
  }

  @Bean
  public WebServiceTemplate webServiceTemplate() throws Exception {
    final WebServiceTemplate template = new WebServiceTemplate(jaxb2Marshaller(), jaxb2Marshaller());
    template.setDefaultUri("https://domain.tld/SVC/data");
    //template.setMessageSenders(new WebServiceMessageSender[]{ messageSender(), messageSender2() });
    //template.setInterceptors(new ClientInterceptor[] { wss4jSecurityInterceptor() });
    template.setMessageSender(messageSender());
    return template;
  }

  @Bean
  public HttpsUrlConnectionMessageSender messageSender() throws Exception {
    HttpsUrlConnectionMessageSender messageSender = new HttpsUrlConnectionMessageSender();
    messageSender.setTrustManagers(trustManagersFactoryBean().getObject()); // set the trust store(s)
    messageSender.setKeyManagers(keyManagersFactoryBean().getObject()); // set the key store(s)
    return messageSender;
  }

This works for the keystore/trustore part. I'm able to do the SSL handshake successfully, but right now I'm getting an HTTP 401 (Unauthorized). So I tried:

  • To have multiple senders; one of them HttpComponentsMessageSender with the username and password on it...but it doesn't work
  • To configure a ClientInterceptor with some Wss4jSecurityInterceptor config/settings...also doesn't work
  • To use a sender that inherits from HttpsUrlConnectionMessageSender, add a username and password fields, overwrite prepareConnection and set connection.setRequestProperty to use an Authorization header. This time I get an HTTP 405 (Method Not Allowed)

Any clues how to do this?


Solution

  • I ended up creating a new class and injecting it as a message sender in the Spring's WebServiceTemplate. That solves the HTTP 401 (Unauthorized) — don't quite remember about the HTTP 405 (Method Not Allowed).

    @Bean
    public HttpsUrlConnectionMessageSender messageSender() throws Exception {
      HttpsUrlConnectionMessageSender messageSender = new BasicAuthHttpsConnectionMessageSender(username, password);
      // ...
      return messageSender;
    }
    

    // You might need org.springframework.ws:spring-ws-support in order to
    // have HttpsUrlConnectionMessageSender
    public final class BasicAuthHttpsConnectionMessageSender extends HttpsUrlConnectionMessageSender {
      private String b64Creds;
    
      public BasicAuthHttpsConnectionMessageSender(String username, String password) {
        b64Creds = Base64.getUrlEncoder().encodeToString((username + ":" + password).getBytes(StandardCharsets.UTF_8));
      }
    
      @Override
      protected void prepareConnection(HttpURLConnection connection) throws IOException {
        connection.setRequestProperty(HttpHeaders.AUTHORIZATION, String.format("Basic %s", b64Creds));
        super.prepareConnection(connection);
      }
    }
    

    Refer to this answer also for more info. Both are related (if not almost the same).