Uploaded image for project: 'Spring Web Services'
  1. Spring Web Services
  2. SWS-991

MessageSender incompatibility in SSL Mutual Auth and Basic Auth scenario

    Details

    • Type: Bug
    • Status: Open
    • Priority: Major
    • Resolution: Unresolved
    • Affects Version/s: 2.4.0
    • Fix Version/s: None
    • Component/s: Core, Documentation
    • Labels:
      None
    • Environment:
      Java 8 (OS irrelevant)

      Description

      Environment prerequisites

      We are integrating with a client that provides WSDL for their services. Their setup involves:

      • a properly signed server SSL certificate (Symantec Class 3 - G4, Verisign G5 root)
      • a mutual-authentication setup, where we produced a hostname CSR and they signed with their own (internal) CA, not certified by known root certificates
      • a load balancer (we assume NGINX but could be a heavy iron one) handling the SSL & Mutual Auth setup and authentication
      • an internal WS (Spring Boot) server application presenting Basic Authentication requests to the client

      Action Plan

      Using WS to generate and create client, we configure the WebServiceTemplate with the relevant jaxb2 marshaller/unmarshaller and proceed to configure message senders.

      Two senders were configured:

      • HttpsUrlConnectionMessageSender, for the keystore/truststore configuration and hostname verifier
      • HttpComponentsMessageSender, for the Basic Authentication to the end service

      Issue manifestation

      It seems that using the webServiceTemplate.setMessageSenders() call to set the array of message senders, the combination of the two produces weird results:

      • if the array contains the HttpsUrlConnectionMessageSender first, the SSL mutual auth consistently fails - the SSL trace shows that we do not produce a client certificate at all
      • if the array contains the HttpComponentsMessageSender first, the Basic Auth fails - there is no Basic Auth header added to the request. However, the SSL client mutual auth succeeds!

      Conclusion

      We believe that these two message senders are incompatible. Whichever goes last overrides (or uses different URL factories or something) and makes the first configuration to disappear. This is not evident in documentation anywhere and for our case it took us more than 2 days debugging with the client (initially thinking it was an SSL configuration issue on their side) to understand that this was an incompatibility.

      Available workaround

      In our scenario of SSL Mutual Auth and Basic Auth, we proceeded to add a custom override of the prepareConnection() call of the HttpsUrlConnectionMessageSender as below:

      @Override
      protected void prepareConnection(HttpURLConnection connection) throws IOException {
      	super.prepareConnection(connection);
      	String userCredentials = name + ":" + password;
      	String basicAuth = "Basic " + new String(Base64.getEncoder().encode(userCredentials.getBytes()));
      	connection.setRequestProperty ("Authorization", basicAuth);
      }
      

      By doing that, we used only the SSL configuration MessageSender which successfully passed the client Mutual Authentication and also configured the header for the Basic Authentication.

      PS: Flagging as major due to lack of documentation and complexity of the debugging involved.

        Activity

        thanosa75 Thanos Angelatos created issue -
        thanosa75 Thanos Angelatos made changes -
        Field Original Value New Value
        Description h2. Environment prerequisites

        We are integrating with a client that provides WSDL for their services. Their setup involves:
        * a properly signed server SSL certificate (Symantec Class 3 - G4, Verisign G5 root)
        * a mutual-authentication setup, where we produced a hostname CSR and they signed with their own (internal) CA, not certified by known root certificates
        * a load balancer (we assume NGINX but could be a heavy iron one) handling the SSL & Mutual Auth setup and authentication
        * an internal WS (Spring Boot) server application presenting Basic Authentication requests to the client

        h2. Action Plan

        Using WS to generate and create client, we configure the WebServiceTemplate with the relevant jaxb2 marshaller/unmarshaller and proceed to configure message senders.

        Two senders were configured:
        * HttpsUrlConnectionMessageSender, for the keystore/truststore configuration and hostname verifier
        * HttpComponentsMessageSender, for the Basic Authentication to the end service

        h2. Issue manifestation
        It seems that using the {{webServiceTemplate.setMessageSenders()}} call to set the array of message senders, the combination of the two produces weird results:
        * if the array contains the HttpsUrlConnectionMessageSender first, the SSL mutual auth consistently fails - the SSL trace shows that we do not produce a client certificate at all
        * if the array contains the HttpComponentsMessageSender first, the Basic Auth fails - there is no Basic Auth header added to the request. However, the SSL client mutual auth succeeds!

        h2. Conclusion
        We believe that these two message senders are incompatible. Whichever goes last overrides (or uses different URL factories or something) and makes the first configuration to disappear. This is not evident in documentation anywhere and for our case it took us more than 2 days debugging with the client (initially thinking it was an SSL configuration issue on their side) to understand that this was an incompatibility.

        h3. Available workaround
        In our scenario of SSL Mutual Auth and Basic Auth, we proceeded to add a custom override of the {{prepareConnection()}} call of the HttpsUrlConnectionMessageSender as below:

        {noformat}
        @Override
        protected void prepareConnection(HttpURLConnection connection) throws IOException {
        super.prepareConnection(connection);
        String userCredentials = name + ":" + password;
        String basicAuth = "Basic " + new String(Base64.getEncoder().encode(userCredentials.getBytes()));
        connection.setRequestProperty ("Authorization", basicAuth);
        }
        {noformat}

        By doing that, we used only the SSL configuration MessageSender which successfully passed the client Mutual Authentication and also configured the header for the Basic Authentication.



        PS: Flagging as major due to lack of documentation and complexity of the debugging involved.
        h2. Environment prerequisites

        We are integrating with a client that provides WSDL for their services. Their setup involves:
        * a properly signed server SSL certificate (Symantec Class 3 - G4, Verisign G5 root)
        * a mutual-authentication setup, where we produced a hostname CSR and they signed with their own (internal) CA, not certified by known root certificates
        * a load balancer (we assume NGINX but could be a heavy iron one) handling the SSL & Mutual Auth setup and authentication
        * an internal WS (Spring Boot) server application presenting Basic Authentication requests to the client

        h2. Action Plan

        Using WS to generate and create client, we configure the WebServiceTemplate with the relevant jaxb2 marshaller/unmarshaller and proceed to configure message senders.

        Two senders were configured:
        * {{HttpsUrlConnectionMessageSender}}, for the keystore/truststore configuration and hostname verifier
        * {{HttpComponentsMessageSender}}, for the Basic Authentication to the end service

        h2. Issue manifestation
        It seems that using the {{webServiceTemplate.setMessageSenders()}} call to set the array of message senders, the combination of the two produces weird results:
        * if the array contains the {{HttpsUrlConnectionMessageSender}} first, the SSL mutual auth consistently fails - the SSL trace shows that we do not produce a client certificate at all
        * if the array contains the {{HttpComponentsMessageSender}} first, the Basic Auth fails - there is no Basic Auth header added to the request. However, the SSL client mutual auth succeeds!

        h2. Conclusion
        We believe that these two message senders are incompatible. Whichever goes last overrides (or uses different URL factories or something) and makes the first configuration to disappear. This is not evident in documentation anywhere and for our case it took us more than 2 days debugging with the client (initially thinking it was an SSL configuration issue on their side) to understand that this was an incompatibility.

        h3. Available workaround
        In our scenario of SSL Mutual Auth and Basic Auth, we proceeded to add a custom override of the {{prepareConnection()}} call of the {{HttpsUrlConnectionMessageSender}} as below:

        {noformat}
        @Override
        protected void prepareConnection(HttpURLConnection connection) throws IOException {
        super.prepareConnection(connection);
        String userCredentials = name + ":" + password;
        String basicAuth = "Basic " + new String(Base64.getEncoder().encode(userCredentials.getBytes()));
        connection.setRequestProperty ("Authorization", basicAuth);
        }
        {noformat}

        By doing that, we used only the SSL configuration MessageSender which successfully passed the client Mutual Authentication and also configured the header for the Basic Authentication.



        PS: Flagging as major due to lack of documentation and complexity of the debugging involved.
        Hide
        gregturn Greg Turnquist added a comment -

        You aren't really supposed to configure two MessageSenders targeting one message. The purpose is to have different MessageSenders for different message types. Hence the supports() call. In other words, they don't act like layered filters, one feeding the next. Instead, a given MessageSender attempts to create a connection and then send the message.

        The two strategies, perhaps as you've read in the reference docs, for sending messages over HTTP use either build in Java HttpUrlConnection or the more sophisticated one, using Apache HttpClient. For complex stuff, which your use case clearly is in, hints at using Apache HttpClient. This is where you get to configure everything in the HttpClient and then inject it into HttpComponentsMessageSender via constructor injection (preferred) or setter injection.

        To use HttpClient, there are gobs of StackOverflow articles showing many permutations of configuration. The trick is the fact that mutual TLS is a relatively obscure configuration, so I understand how difficult it can be to find such an article and then mix it with BASIC authorization headers.

        Given the fact that you found a suitable workaround by merely extending HttpsUrlConnectionMessageSender and tweaking the HttpURLConnection object by adding a single header, I'm inclined to agree that this was the best solution for your use case. Who wants to perform complex configurations against Apache HttpClient if this works?

        What I am trying to investigate is whether this warrants augmenting Spring WS code, or if documenting this in the reference documents is really the best approach?

        • Adding an Authorization header isn't confined to SSL, so such code would need to work for both HttpsUrlConnectionMessageSender AND HttpUrlConnectionMessageSender. This implies the code belongs in the parent class, HttpUrlConnectionMessageSender.
        • Authorization headers aren't the only headers others may wish to add to HTTP-based messages.

        So documenting the ability to extend and modify may be of the most benefit to all users vs. something actually added to the framework's code base.

        Show
        gregturn Greg Turnquist added a comment - You aren't really supposed to configure two MessageSenders targeting one message. The purpose is to have different MessageSenders for different message types. Hence the supports() call. In other words, they don't act like layered filters, one feeding the next. Instead, a given MessageSender attempts to create a connection and then send the message. The two strategies, perhaps as you've read in the reference docs, for sending messages over HTTP use either build in Java HttpUrlConnection or the more sophisticated one, using Apache HttpClient. For complex stuff, which your use case clearly is in, hints at using Apache HttpClient. This is where you get to configure everything in the HttpClient and then inject it into HttpComponentsMessageSender via constructor injection (preferred) or setter injection. To use HttpClient, there are gobs of StackOverflow articles showing many permutations of configuration. The trick is the fact that mutual TLS is a relatively obscure configuration, so I understand how difficult it can be to find such an article and then mix it with BASIC authorization headers. Given the fact that you found a suitable workaround by merely extending HttpsUrlConnectionMessageSender and tweaking the HttpURLConnection object by adding a single header, I'm inclined to agree that this was the best solution for your use case. Who wants to perform complex configurations against Apache HttpClient if this works? What I am trying to investigate is whether this warrants augmenting Spring WS code, or if documenting this in the reference documents is really the best approach? Adding an Authorization header isn't confined to SSL, so such code would need to work for both HttpsUrlConnectionMessageSender AND HttpUrlConnectionMessageSender. This implies the code belongs in the parent class, HttpUrlConnectionMessageSender. Authorization headers aren't the only headers others may wish to add to HTTP-based messages. So documenting the ability to extend and modify may be of the most benefit to all users vs. something actually added to the framework's code base.
        Hide
        thanosa75 Thanos Angelatos added a comment -

        Hello Greg,

        thanks for taking the time to prepare such a thorough analysis. I will agree that this may be an "obscure" configuration that was probably not very requested in the past, but in recent times we've been faced with such configurations more than once in the last 2-3 months. Nevertheless, I would like to focus to your last sentence - and documentation is always the first place we go when we have such doubts. So +1 there.

        On the other hand, I find really confusing your 1st sentence - If I'm not supposed to configure two message senders, why there is a webServiceTemplate.setMessageSenders(...) API call? What is it for? It directly implies a chain of MessageSenders - which was then quite an obvious path leading down the rabbit hole for us.

        Maybe a rethinking of the API is necessary. Or much more documentation, or both...

        Show
        thanosa75 Thanos Angelatos added a comment - Hello Greg, thanks for taking the time to prepare such a thorough analysis. I will agree that this may be an "obscure" configuration that was probably not very requested in the past, but in recent times we've been faced with such configurations more than once in the last 2-3 months. Nevertheless, I would like to focus to your last sentence - and documentation is always the first place we go when we have such doubts. So +1 there. On the other hand, I find really confusing your 1st sentence - If I'm not supposed to configure two message senders, why there is a webServiceTemplate.setMessageSenders(...) API call? What is it for? It directly implies a chain of MessageSenders - which was then quite an obvious path leading down the rabbit hole for us. Maybe a rethinking of the API is necessary. Or much more documentation, or both...
        Hide
        gregturn Greg Turnquist added a comment -

        The concept of setMessageSenders() is to support either a different sender for each type, or a different sender per transport. Imagine an array of senders, one for HTTP, one for JMS, one for email. (SOAP has this nice transport neutrality people often overlook).

        The concept is also, one message sender for Order objects, a different message sender for ExpiredUser-based messages.

        I can see how that API is confusing since it's not the same as an array of nested filters.

        Show
        gregturn Greg Turnquist added a comment - The concept of setMessageSenders() is to support either a different sender for each type, or a different sender per transport. Imagine an array of senders, one for HTTP, one for JMS, one for email. (SOAP has this nice transport neutrality people often overlook). The concept is also, one message sender for Order objects, a different message sender for ExpiredUser-based messages. I can see how that API is confusing since it's not the same as an array of nested filters.

          People

          • Assignee:
            Unassigned
            Reporter:
            thanosa75 Thanos Angelatos
          • Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

            • Created:
              Updated: