Search code examples
javaspringopensamlsimplesamlphpspring-saml

Forward error by integrating Spring SAML (as SP) and SimpleSAML (as IdP)


With the aim to support SSO and SLO in my webapp, I've implemented a Service Provider by using Spring Security SAML Extension.

The code is available on GitHub: vdenotaris/spring-boot-saml.

By using SSOCircle as Identity Provider all works fine.

Now, I'm trying to link another IdP based on Simple SAML, and in this case the SSO fails.

Analyzing the log on my application server (Apache Tomcat 7.0.54), I've found these errors:

root@pr01tom01:/opt/apache-tomcat-7.0.54/logs# tail -n 10000 localhost.2014-07-14.log | grep SEVERE
    SEVERE: Servlet.service() for servlet dispatcherServlet threw exception
    SEVERE: Servlet.service() for servlet dispatcherServlet threw exception
    SEVERE: Servlet.service() for servlet dispatcherServlet threw exception
    SEVERE: Servlet.service() for servlet dispatcherServlet threw exception
    SEVERE: Servlet.service() for servlet dispatcherServlet threw exception
    SEVERE: Servlet.service() for servlet dispatcherServlet threw exception
    SEVERE: Servlet.service() for servlet dispatcherServlet threw exception
    SEVERE: Servlet.service() for servlet dispatcherServlet threw exception
    SEVERE: Servlet.service() for servlet dispatcherServlet threw exception
root@pr01tom01:/opt/apache-tomcat-7.0.54/logs# tail -n 10000 localhost.2014-07-14.log | grep java.lang.IllegalStateException
    java.lang.IllegalStateException: Cannot forward after response has been committed
    java.lang.IllegalStateException: Cannot forward after response has been committed
    java.lang.IllegalStateException: Cannot forward after response has been committed
    java.lang.IllegalStateException: Cannot forward after response has been committed
    java.lang.IllegalStateException: Cannot forward after response has been committed
    java.lang.IllegalStateException: Cannot forward after response has been committed
    java.lang.IllegalStateException: Cannot forward after response has been committed

Is there a way to fix this issue?

Regards, V.


Spring DEBUG Log:

[2014-07-15 18:20:57.314] boot - 1092 DEBUG [http-bio-443-exec-6] --- SAMLContextProviderImpl: Using user specified IDP https://prisma-idp.cloud.ba.infn.it/simplesaml/saml2/idp/metadata.php from request
[2014-07-15 18:20:57.314] boot - 1092 DEBUG [http-bio-443-exec-6] --- ChainingMetadataProvider: Checking child metadata provider for entity descriptor with entity ID: https://prisma-idp.cloud.ba.infn.it/simplesaml/saml2/idp/metadata.php
[2014-07-15 18:20:57.314] boot - 1092 DEBUG [http-bio-443-exec-6] --- AbstractMetadataProvider: Searching for entity descriptor with an entity ID of https://prisma-idp.cloud.ba.infn.it/simplesaml/saml2/idp/metadata.php
[2014-07-15 18:20:57.315] boot - 1092 DEBUG [http-bio-443-exec-6] --- AbstractMetadataProvider: Metadata document did not contain a descriptor for entity https://prisma-idp.cloud.ba.infn.it/simplesaml/saml2/idp/metadata.php
[2014-07-15 18:20:57.315] boot - 1092 DEBUG [http-bio-443-exec-6] --- AbstractMetadataProvider: Metadata document did not contain any role descriptors of type {urn:oasis:names:tc:SAML:2.0:metadata}IDPSSODescriptor for entity https://prisma-idp.cloud.ba.infn.it/simplesaml/saml2/idp/metadata.php
[2014-07-15 18:20:57.315] boot - 1092 DEBUG [http-bio-443-exec-6] --- AbstractMetadataProvider: Metadata document does not contain a role of type {urn:oasis:names:tc:SAML:2.0:metadata}IDPSSODescriptor supporting protocol urn:oasis:names:tc:SAML:2.0:protocol for entity https://prisma-idp.cloud.ba.infn.it/simplesaml/saml2/idp/metadata.php
[2014-07-15 18:20:57.315] boot - 1092 DEBUG [http-bio-443-exec-6] --- ChainingMetadataProvider: Checking child metadata provider for entity descriptor with entity ID: https://prisma-idp.cloud.ba.infn.it/simplesaml/saml2/idp/metadata.php
[2014-07-15 18:20:57.316] boot - 1092 DEBUG [http-bio-443-exec-6] --- AbstractMetadataProvider: Searching for entity descriptor with an entity ID of https://prisma-idp.cloud.ba.infn.it/simplesaml/saml2/idp/metadata.php
[2014-07-15 18:20:57.316] boot - 1092 DEBUG [http-bio-443-exec-6] --- AbstractMetadataProvider: Metadata document did not contain a descriptor for entity https://prisma-idp.cloud.ba.infn.it/simplesaml/saml2/idp/metadata.php
[2014-07-15 18:20:57.316] boot - 1092 DEBUG [http-bio-443-exec-6] --- AbstractMetadataProvider: Metadata document did not contain any role descriptors of type {urn:oasis:names:tc:SAML:2.0:metadata}IDPSSODescriptor for entity https://prisma-idp.cloud.ba.infn.it/simplesaml/saml2/idp/metadata.php
[2014-07-15 18:20:57.316] boot - 1092 DEBUG [http-bio-443-exec-6] --- AbstractMetadataProvider: Metadata document does not contain a role of type {urn:oasis:names:tc:SAML:2.0:metadata}IDPSSODescriptor supporting protocol urn:oasis:names:tc:SAML:2.0:protocol for entity https://prisma-idp.cloud.ba.infn.it/simplesaml/saml2/idp/metadata.php
[2014-07-15 18:20:57.317] boot - 1092 DEBUG [http-bio-443-exec-6] --- ChainingMetadataProvider: Checking child metadata provider for entity descriptor with entity ID: https://prisma-idp.cloud.ba.infn.it/simplesaml/saml2/idp/metadata.php
[2014-07-15 18:20:57.317] boot - 1092 DEBUG [http-bio-443-exec-6] --- AbstractMetadataProvider: Searching for entity descriptor with an entity ID of https://prisma-idp.cloud.ba.infn.it/simplesaml/saml2/idp/metadata.php
[2014-07-15 18:20:57.317] boot - 1092 DEBUG [http-bio-443-exec-6] --- SAMLUtil: Index for AssertionConsumerService not specified, returning default
[2014-07-15 18:20:57.317] boot - 1092 DEBUG [http-bio-443-exec-6] --- SAML2MetadataHelper: Selecting default IndexedEndpoint
[2014-07-15 18:20:57.318] boot - 1092 DEBUG [http-bio-443-exec-6] --- SAML2MetadataHelper: Selected IndexedEndpoint with explicit isDefault of true
[2014-07-15 18:20:57.318] boot - 1092 DEBUG [http-bio-443-exec-6] --- SAMLEntryPoint: Processing SSO using WebSSO profile
[2014-07-15 18:20:57.318] boot - 1092 DEBUG [http-bio-443-exec-6] --- SAML2MetadataHelper: Selecting default IndexedEndpoint
[2014-07-15 18:20:57.318] boot - 1092 DEBUG [http-bio-443-exec-6] --- SAML2MetadataHelper: Selected IndexedEndpoint with explicit isDefault of true
[2014-07-15 18:20:57.318] boot - 1092 DEBUG [http-bio-443-exec-6] --- SAML2MetadataHelper: Selecting default IndexedEndpoint
[2014-07-15 18:20:57.318] boot - 1092 DEBUG [http-bio-443-exec-6] --- SAML2MetadataHelper: Selected IndexedEndpoint with explicit isDefault of true
[2014-07-15 18:20:57.319] boot - 1092 DEBUG [http-bio-443-exec-6] --- SAML2MetadataHelper: Selecting default IndexedEndpoint
[2014-07-15 18:20:57.319] boot - 1092 DEBUG [http-bio-443-exec-6] --- SAML2MetadataHelper: Selected IndexedEndpoint with explicit isDefault of true
[2014-07-15 18:20:57.319] boot - 1092 DEBUG [http-bio-443-exec-6] --- WebSSOProfileImpl: Using default consumer service with binding urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact
[2014-07-15 18:20:57.319] boot - 1092 DEBUG [http-bio-443-exec-6] --- BaseMessageEncoder: Beginning encode message to outbound transport of type: org.opensaml.ws.transport.http.HttpServletResponseAdapter
[2014-07-15 18:20:57.320] boot - 1092 DEBUG [http-bio-443-exec-6] --- HTTPRedirectDeflateEncoder: Deflating and Base64 encoding SAML message
[2014-07-15 18:20:57.320] boot - 1092 DEBUG [http-bio-443-exec-6] --- BaseMessageEncoder: Marshalling message
[2014-07-15 18:20:57.323] boot - 1092 DEBUG [http-bio-443-exec-6] --- HTTPRedirectDeflateEncoder: Building URL to redirect client to
[2014-07-15 18:20:57.323] boot - 1092 DEBUG [http-bio-443-exec-6] --- HTTPRedirectDeflateEncoder: Generating signature with key type 'RSA', algorithm URI 'http://www.w3.org/2000/09/xmldsig#rsa-sha1' over query string 'SAMLRequest=fVLBbuMgFPwVxN1gbKdpUZzK3W61lhptFCettJcVsXFNhMHLg6j79%2Bs4iba99PjEvJl5Myzu33uNjtKBsibHjMQYSVPbRpm3HO%2B2T9Etvl8uQPQ6GXgRfGc28k%2BQ4NG4aICfX3IcnOFWgAJuRC%2BB%2B5pXxeqZJyTmg7Pe1lZjVABI50epb9ZA6KWrpDuqWu42zznuvB%2BAUzo4Bb2AgdTahoY4Oei%2FRAaeZSldlbvN7%2FWmrFZFlBAWvX5%2F2JX0ZIJW1U8qtBJAG9mKoH1xGjB6HM0qI%2Fx04GeNSDVXlb0gyrSGKE9B9YOWE%2Bd0HR1RJ%2FaLVzJ0A0ZP1tVyyiPHrdAgMSofcyyyVBz27K1Juzi7m6Us7W4Oop7v2aFlsh1BsBYA6ij%2FrwEEWRrwwvgcJzHLongesdmW3fAk5rM5SdndL4zWlxQflDm381Xk%2BzMI%2BI%2Ftdh0VY%2BatqD1GL9emRxC%2B9MonB%2B5joV%2BTi2uLeHmCKc97FRw%2FZ7qgH0mXl%2FHz31n%2BAw%3D%3D&SigAlg=http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1'
[2014-07-15 18:20:57.324] boot - 1092 DEBUG [http-bio-443-exec-6] --- SigningUtil: Computing signature over input using private key of type RSA and JCA algorithm ID SHA1withRSA
[2014-07-15 18:20:57.350] boot - 1092 DEBUG [http-bio-443-exec-6] --- SigningUtil: Computed signature: 821f8f391e4aab64ce8c79374d5840c052b2a169432d835977057d0a33bd72dc40a8b9594a48ab70c4d58af36eff3ab9eb8a61c2f3766b59071781b984ba3255acf53f56e028442ef2f372a29edbbe8cd791df32588b9043cbc7aa9e7ab15ffbe47337f78ebd5d1a929fd2b3077ed32c364383746455fd24c7b2ce7cc1dd09eda1b2099dd09fc38801cdeeb7262437a04782dad45ac15fd1f053fff925bd77f5e9b7e5a3f1464d4f08a07c0d453d479803aaf75256ea074eff00a8a5575233cf997649abe0687d9a732ea03c32659990b8f00784a8bb38be9240085db96affa030c39fe7173a85f100d28547da5141faa6b062f31ea69deabe61bea78f809561
[2014-07-15 18:20:57.350] boot - 1092 DEBUG [http-bio-443-exec-6] --- HTTPRedirectDeflateEncoder: Generated digital signature value (base64-encoded) gh+POR5Kq2TOjHk3TVhAwFKyoWlDLYNZdwV9CjO9ctxAqLlZSkircMTVivNu/zq564phwvN2a1kHF4G5hLoyVaz1P1bgKEQu8vNyop7bvozXkd8yWIuQQ8vHqp56sV/75HM39469XRqSn9KzB37TLDZDg3RkVf0kx7LOfMHdCe2hsgmd0J/DiAHN7rcmJDegR4La1FrBX9HwU//5Jb139em35aPxRk1PCKB8DUU9R5gDqvdSVuoHTv8AqKVXUjPPmXZJq+BofZpzLqA8MmWZkLjwB4Souzi+kkAIXblq/6Aww5/nFzqF8QDShUfaUUH6prBi8x6mneq+Yb6nj4CVYQ==
[2014-07-15 18:20:57.353] boot - 1092 DEBUG [http-bio-443-exec-6] --- PROTOCOL_MESSAGE:
<?xml version="1.0" encoding="UTF-8"?><saml2p:AuthnRequest xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="https://prismasp.cloud.reply.eu:443/MIUR_PRISMA-2.1-WEBUI/saml/SSO/alias/defaultAlias" Destination="https://prisma-idp.cloud.ba.infn.it/simplesaml/saml2/idp/SSOService.php" ForceAuthn="false" ID="a43ajb1gd3h0495313h6jac7b1jf1ef" IsPassive="false" IssueInstant="2014-07-15T16:20:57.319Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Version="2.0">
   <saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">urn:it:miur:prisma</saml2:Issuer>
</saml2p:AuthnRequest>

[2014-07-15 18:20:57.354] boot - 1092 DEBUG [http-bio-443-exec-6] --- BaseMessageEncoder: Successfully encoded message.
[2014-07-15 18:20:57.354] boot - 1092  INFO [http-bio-443-exec-6] --- SAMLDefaultLogger: AuthNRequest;SUCCESS;10.7.4.185

Tomcat server.xml

<?xml version='1.0' encoding='utf-8'?>
<!--
  Licensed to the Apache Software Foundation (ASF) under one or more
  contributor license agreements.  See the NOTICE file distributed with
  this work for additional information regarding copyright ownership.
  The ASF licenses this file to You under the Apache License, Version 2.0
  (the "License"); you may not use this file except in compliance with
  the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
-->
<!-- Note:  A "Server" is not itself a "Container", so you may not
     define subcomponents such as "Valves" at this level.
     Documentation at /docs/config/server.html
 -->
<Server port="8005" shutdown="SHUTDOWN">
  <!-- Security listener. Documentation at /docs/config/listeners.html
  <Listener className="org.apache.catalina.security.SecurityListener" />
  -->
  <!--APR library loader. Documentation at /docs/apr.html -->
  <!--Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" /-->
  <!--Initialize Jasper prior to webapps are loaded. Documentation at /docs/jasper-howto.html -->
  <Listener className="org.apache.catalina.core.JasperListener" />
  <!-- Prevent memory leaks due to use of particular java/javax APIs-->
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

  <!-- Global JNDI resources
       Documentation at /docs/jndi-resources-howto.html
  -->
  <GlobalNamingResources>
    <!-- Editable user database that can also be used by
         UserDatabaseRealm to authenticate users
    -->
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>

  <!-- A "Service" is a collection of one or more "Connectors" that share
       a single "Container" Note:  A "Service" is not itself a "Container",
       so you may not define subcomponents such as "Valves" at this level.
       Documentation at /docs/config/service.html
   -->
  <Service name="Catalina">

    <!--The connectors can use a shared executor, you can define one or more named thread pools-->
    <!--
    <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
        maxThreads="150" minSpareThreads="4"/>
    -->

    <!-- A "Connector" represents an endpoint by which requests are received
         and responses are returned. Documentation at :
         Java HTTP Connector: /docs/config/http.html (blocking & non-blocking)
         Java AJP  Connector: /docs/config/ajp.html
         APR (HTTP/AJP) Connector: /docs/apr.html
         Define a non-SSL HTTP/1.1 Connector on port 8080
    -->
    <Connector port="80" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    <!-- A "Connector" using the shared thread pool-->
    <!--
    <Connector executor="tomcatThreadPool"
               port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    -->
    <!-- Define a SSL HTTP/1.1 Connector on port 8443
         This connector uses the BIO implementation that requires the JSSE
         style configuration. When using the APR/native implementation, the
         OpenSSL style configuration is required as described in the APR/native
         documentation -->

        <Connector port="443" protocol="org.apache.coyote.http11.Http11Protocol"
            maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
            clientAuth="false" sslProtocol="TLS"
            keystoreFile="/root/.keystore"
            keystorePass="mystore"
            keyAlias="appalias"
            keyPass="mycert"              />


    <!-- Define an AJP 1.3 Connector on port 8009 -->
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />


    <!-- An Engine represents the entry point (within Catalina) that processes
         every request.  The Engine implementation for Tomcat stand alone
         analyzes the HTTP headers included with the request, and passes them
         on to the appropriate Host (virtual host).
         Documentation at /docs/config/engine.html -->

    <!-- You should set jvmRoute to support load-balancing via AJP ie :
    <Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">
    -->
    <Engine name="Catalina" defaultHost="localhost">

      <!--For clustering, please take a look at documentation at:
          /docs/cluster-howto.html  (simple how to)
          /docs/config/cluster.html (reference documentation) -->
      <!--
      <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
      -->

      <!-- Use the LockOutRealm to prevent attempts to guess user passwords
           via a brute-force attack -->
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <!-- This Realm uses the UserDatabase configured in the global JNDI
             resources under the key "UserDatabase".  Any edits
             that are performed against this UserDatabase are immediately
             available for use by the Realm.  -->
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>

      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">

        <!-- SingleSignOn valve, share authentication between web applications
             Documentation at: /docs/config/valve.html -->
        <!--
        <Valve className="org.apache.catalina.authenticator.SingleSignOn" />
        -->

        <!-- Access log processes all example.
             Documentation at: /docs/config/valve.html
             Note: The pattern used is equivalent to using pattern="common" -->
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log." suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />

      </Host>
    </Engine>
  </Service>
</Server>

Solution

  • When running your code in my environment I had to first of all disable CSRF token checking in WebSecurityConfig#configure(HttpSecurity), as it was preventing acceptance of SAML Response from the Prisma IDP. I did so using:

    http.csrf().disable();
    

    After that single sign-on started working, the Response message was accepted, but your demo.components.SAMLUserDetailsServiceImpl rejected it, as the user's ID doesn't match your condition if (userID.compareTo("[email protected]") != 0) which is specified there. (the user ID returned from the Prisma service is an identifier similar to "246c7aaffc73d7b3cea43d35f14bf86a59557b37").

    The SSOUserAccountNotExistsException exception you throw sends user back to the EntryPoint (and this could probably cause an eternal loop).

    Once I removed the if condition I was able to successfully finish the single sign-on process.

    I believe that the logs you posted above were still incomplete. I had to enable logging by creating file src/main/resources/logback.xml with the following content:

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
        <include resource="org/springframework/boot/logging/logback/base.xml"/>
        <logger name="org.springframework.security" level="DEBUG"/>
        <logger name="org.springframework.security.saml" level="DEBUG"/>
        <logger name="org.opensaml" level="DEBUG"/>
        <logger name="PROTOCOL_MESSAGE" level="DEBUG"/>
    </configuration>