I have successfully configured Apache (2.4.7) to require a client certificate and -- as a reverse proxy -- forward the information within the certificate to a Tomcat 8 server.
When trying to accomplish the same with Spring Boot however, it fails with
The proxy server received an invalid response from an upstream server
The proxy server could not handle the request GET /myapp
and returns a HTTP 502 error code.
The relevant, working Tomcat configuration is:
<Connector SSLEnabled="true" clientAuth="want" keyAlias="myalias"
keystoreFile="mystore.jks" keystorePass="mypassword" maxThreads="150"
port="8443" protocol="HTTP/1.1" scheme="https" secure="true"
sslProtocol="TLS" truststoreFile="mystore.jks" truststorePass="mypassword"/>
The relevant part of the Spring Boot application.properties file that won't work:
server.context-path=/myapp
server.port=8443
server.ssl.enabled=true
server.use-forward-headers=true
server.ssl.protocol=TLS
server.ssl.client-auth=need
server.ssl.key-alias=myalias
server.ssl.key-store=/path/to/mykeystore.jks
server.ssl.key-store-password=mypassword
server.ssl.key-password=mypassword
server.ssl.trust-store=/path/to/mykeystore.jks
server.ssl.trust-store-password=mypassword
server.tomcat.remote-ip-header=x-forwarded-for
server.tomcat.port-header=x-forwarded-port
Note, when accessing the app directly (i.e. requesting https://myapp.company.tld:12345/myapp) it works just fine, but using the reverse proxy (i.e. https://proxy-load-balancer.company.tld:12345/myapp) throws the error above.
The port difference (12345 vs the configured 8443) is due to an intermediate Docker layer: both the reverse proxy and the application runs in a container and their open ports (443 for Apache, 8443 for Tomcat/Spring Boot) are mapped to a different port, i.e. 12345.
Ok, so this all got sorted out. First, this Spring security setting
server.ssl.client-auth=need
will always force your embedded Tomcat to ask for a certificate, so in a client=>reverse proxy=>Tomcat situation you won't be able to authenticate unless maybe you use AJP, not sure about that.
But it turned out that using
server.ssl.client-auth=want
enables the further processing of your request and what people usually do is process the certificate at the reverse proxy level and forward some information to the backend server (Tomcat, Jetty, etc.).
In the end the developers had to adapt their Spring Boot application to handle this latter mode of operation, i.e. extract data from the forwarded HTTP request's header and proceed with the authentication based on that.