title: How does spring security work with client certificate authentication when using a POST method
body: <p>When I use RestTemplate of spring framework to connect to my web service which is secured by HTTPS with client authentication by spring security through Restful API, I found problem to use POST method. It seems that the X509AuthenticationFilter don't get the client certificate when I use POST method. I don't have the same problem when I use GET method. 
  The following is the XML configuration file for spring security in the server side.</p>
  
  <pre><code>&lt;http pattern="/resources/**" security="none" /&gt;
  &lt;http auto-config="true" use-expressions="true" entry-point-ref="forbiddenAuthEntryPoint"&gt;
      &lt;intercept-url pattern="/" access="permitAll" /&gt;
      &lt;intercept-url pattern="/service/**" access="hasRole('ROLE_ABC_USER')" /&gt;
      &lt;intercept-url pattern="/**" access="permitAll" /&gt;
  
      &lt;!-- &lt;x509 subject-principal-regex="CN=(.*?)," user-service-ref="userDetailsService" 
          /&gt; --&gt;
  
      &lt;custom-filter position="X509_FILTER" ref="myX509AuthenticationFilter" /&gt;
  &lt;/http&gt;
  
  
  &lt;bean:bean id="myX509AuthenticationFilter"
      class="com.ray.MyX509AuthenticationFilter"&gt;
      &lt;bean:property name="authenticationManager" ref="authenticationManager" /&gt;
  &lt;/bean:bean&gt;
  
  &lt;bean:bean id="preauthAuthenticationProvider"
      class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider"&gt;
      &lt;bean:property name="preAuthenticatedUserDetailsService" ref="authenticationUserDetailsService" /&gt;
  &lt;/bean:bean&gt;
  &lt;bean:bean id="authenticationUserDetailsService"
      class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper"&gt;
      &lt;bean:property name="userDetailsService" ref="userDetailsService" /&gt;
  &lt;/bean:bean&gt;
  
  &lt;bean:bean id="forbiddenAuthEntryPoint"
      class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint" /&gt;
  
  &lt;authentication-manager alias="authenticationManager"&gt;
      &lt;authentication-provider ref="preauthAuthenticationProvider" /&gt;
      &lt;authentication-provider&gt;
          &lt;user-service id="userDetailsService"&gt;
              &lt;user name="www.ray.insight" password="dummy" authorities="ROLE_ABC_USER" /&gt;
          &lt;/user-service&gt;
      &lt;/authentication-provider&gt;
  &lt;/authentication-manager&gt;
  </code></pre>
  
  <p>Originally, I use the standard x509 element in the security namespace as you can see from the comment out line. During my testing, I use the following MyX509AuthenticationFilter for debugging purpose. </p>
  
  <pre><code>public class MyX509AuthenticationFilter extends X509AuthenticationFilter {
  private static final Logger LOGGER = LoggerFactory.getLogger(MyX509AuthenticationFilter.class);
  
  protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
  
      X509Certificate[] certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
  
      if (certs != null &amp;&amp; certs.length &gt; 0) {
          LOGGER.debug("X.509 client authentication certificate:" + certs[0]);
      } else {
          LOGGER.debug("No client certificate found in request.");
      }
  
      return super.getPreAuthenticatedPrincipal(request);
  }
  
  }
  </code></pre>
  
  <p>In order to further debug the request. I have added the following servlet filter in web.xml before spring security to print out the client certificate.</p>
  
  <pre><code>public class MyRequestFilter implements Filter {
  Logger LOGGER = LoggerFactory.getLogger(MyRequestFilter.class);
  
  @Override
  public void doFilter(ServletRequest _servletRequest, ServletResponse _servletResponse, FilterChain _filterChain)
          throws IOException, ServletException {
      LOGGER.debug("MyRequestFilter doFilter(): Entering");
  
      if (_servletRequest instanceof HttpServletRequest) {
          HttpServletRequest httpServletRequest = (HttpServletRequest) _servletRequest;
          String requestURI = httpServletRequest.getRequestURI();
          String queryString = httpServletRequest.getQueryString();
          StringBuffer requestURL = httpServletRequest.getRequestURL();
  
          LOGGER.debug("requestURI -&gt;" + requestURI + "&lt;-");
          LOGGER.debug("queryString -&gt;" + queryString + "&lt;-");
          LOGGER.debug("requestURL -&gt;" + requestURL.toString() + "&lt;-");
  
          X509Certificate[] certs = (X509Certificate[]) httpServletRequest
                  .getAttribute("javax.servlet.request.X509Certificate");
  
          if (certs != null &amp;&amp; certs.length &gt; 0) {
              LOGGER.debug("X.509 client authentication certificate:" + certs[0]);
          } else {
              LOGGER.debug("No client certificate found in request.");
          }
  
      } else {
          LOGGER.debug("get non HttpServletRequest!!" + _servletRequest);
  
      }
      _filterChain.doFilter(_servletRequest, _servletResponse);
  }
  }
  </code></pre>
  
  <p>In order to use client authentication, I have also setup the tomcat to use client authentication and the following line is added to the server.xml.</p>
  
  <pre><code>    &lt;Connector SSLEnabled="true" acceptCount="100" clientAuth="true"
          disableUploadTimeout="true" enableLookups="false" keyAlias="abcServer"
          keypass="password" keystoreFile="tomcat8Cert2.jks" keystorePass="password"
          maxHttpHeaderSize="8192" maxSpareThreads="75" maxThreads="150"
          minSpareThreads="25" port="443" scheme="https" secure="true"
          sslProtocol="TLS" truststoreFile="trustStoreCert2.jks"
          truststorePass="password" /&gt;
  </code></pre>
  
  <p>By using the above program and configuration, if I involve web service through GET method, everything work fine. However, if I involve web service through POST method, the MyRequestFilter can print out the certificate with the correct CN, but the MyX509AuthenticationFilter has not printed out anything and spring security just return "org.springframework.web.client.HttpClientErrorException: 403 Forbidden". This exception should come from  Http403ForbiddenEntryPoint of spring security. </p>
  
  <p>I have further test the same web service after comment out spring security filter in my web.xml of the server side and involve it through the same client code. It work. Hence, I suspect something wrong with my setting that make the spring security cannot work property with the POST method to do the client certificate authentication.  Any body has idea what I have missed in spring security?  </p>
  
  <p>=====================</p>
  
  <p>In case you may interesting to know my client code. The following is the code snippet in the client side.</p>
  
  <p>I use the following to initialize the RestTemplate.</p>
  
  <pre><code>private void initialise() {
      restTemplate = new RestTemplate();
  
      HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient());
      factory.setReadTimeout(30000);
      factory.setConnectTimeout(30000);
      restTemplate.setRequestFactory(factory);
      restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
      restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
  }
  </code></pre>
  
  <p>The following code is used to return the httpclient with all the required keystore.</p>
  
  <pre><code>        KeyStore trustStore = KeyStore.getInstance(this.getKeyStoreType());
          FileInputStream instream = new FileInputStream(new File(this.getKeyStorePath()));
          try {
              trustStore.load(instream, this.getKeyStorePassword().toCharArray());
          } finally {
              instream.close();
          }
  
          // TODO: Should trust only authorized client 
          TrustStrategy allTrust = new TrustStrategy() {
              @Override
              public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                  return true;
              }
          };
          SSLContext sslcontext = SSLContexts.custom().loadTrustMaterial(
                  trustStore, allTrust)
                  .loadKeyMaterial(trustStore, this.getKeyStorePassword()
                          .toCharArray()) 
                  .build();
  
          SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext,
                  SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
          CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
  </code></pre>
  
  <p>Then the following code is used to involve the web service through POST.</p>
  
  <pre><code>        response = restTemplate.postForEntity(this.getBasePath() + path, httpEntity, String.class);
  </code></pre>
  
  <p>And the following code is used to involve the web service through GET.</p>
  
  <pre><code>        response = restTemplate.exchange(this.getBasePath() + path, HttpMethod.GET, httpEntity,
                  String.class);
  </code></pre>