[keycloak-user] SAML logout issues

Manuel Waltschek manuel.waltschek at prisma-solutions.at
Tue Nov 26 07:35:11 EST 2019


Hello dear community!

My name is Manuel Waltschek and I work for a small company located in Austria.
We are working on a project that requires SAML SSO and we decided to use keycloak for this implementation.


Use Case


  1.  SSO with external IdP - keycloak brokering
     *   SP initiated and IdP initiated login
  2.  SSO with Keycloak as IdP - registration and user management with keycloak server


Basic Setup

Keycloak-Server Version

4.8.3.Final

Wildfly Version

10.1.0.Final

Wildfly Adapter Version

keycloak-saml-wildfly-adapter-dist-4.8.3.Final


We also use nginx as a proxy in front of our standalone wildfly servers.

nginx config related to keycloak:

        location ^~ /auth/ {
            proxy_pass          http://localhost:8180/auth/;
            proxy_set_header    Host               $host;
            proxy_set_header    X-Real-IP          $remote_addr;
            proxy_set_header    X-Forwarded-For    $proxy_add_x_forwarded_for;
            proxy_set_header    X-Forwarded-Host   $host;
            proxy_set_header    X-Forwarded-Server $host;
            proxy_set_header    X-Forwarded-Port   $server_port;
            proxy_set_header X-Forwarded-Proto $scheme;


        }

Deployment architecture

We deploy an ear which contains common jars and deployment-descriptors like jboss-deployment-structure.xml.
This ear also contains a war file which contains a jboss-web.xml and the keycloak-saml.xml for the client configuration.
It also contains multiple jars like auth.login.saml.web.jar in the following example.
The war itself has a web.xml and the jars in the lib folder have web-fragment.xml files.

app.ear
├── META-INF
│   ├── application.xml
│   ├── jboss-app.xml
│   ├── jboss-classloading.xml
│   ├── jboss-deployment-structure.xml
│   ├── MANIFEST.MF
└── mywar.war
    ├── META-INF
    │   ├── MANIFEST.MF
    └── WEB-INF
        ├── classes
        ├── jboss-web.xml
        ├── keycloak-saml.xml
        ├── lib
        │   └── auth.login.saml.web.jar
        │       ├── at
        │       │   └── prismasolutions
        │       │       └── ppcf
        │       │           └── core
        │       │               └── auth
        │       │                   ├── login
        │       │                          └── SAMLLoginServlet.class
        │       ├── META-INF
        │       │   ├── MANIFEST.MF
        │       │   └── web-fragment.xml
        └── web.xml


We have an underlying permission-based system, which requires to load roles and permissions from our database based on the Principals attributes.
This Principal is logged in via Jboss-Security or Keycloak Client Adapter. In SamlLoginServlet the SamlPrincipal is used to extract attributes and user it in the application.

Problem

The login process seems to work fine for both defined use cases

BUT

We do have some struggles and open issues concerning the logout process. Referring to another support request I have sent in June 2019, the logout does not work for our setup as documented:
https://lists.jboss.org/pipermail/keycloak-user/2019-June/018550.html

The documentation states:

"There are multiple ways you can logout from a web application. For Java EE servlet containers, you can call HttpServletRequest.logout(). For any other browser application, you can point the browser at any url of your web application that has a security constraint and pass in a query parameter GLO, i.e. http://myapp?GLO=true<http://myapp/?GLO=true<http://myapp/?GLO=true%3Chttp://myapp/?GLO=true>>. This will log you out if you have an SSO session with your browser."


In our application, the logout is implemented via a call to a Servlet, called GlobalLogoutServlet. It is registered in the web-fragment.xml in a jar contained in the war:

      <servlet-mapping>
            <servlet-name>GlobalLogoutServlet</servlet-name>
            <url-pattern>/login</url-pattern>
      </servlet-mapping>

To avoid confusion: There is a logout button in the client which refers to the URL /login?logout, this is why this is registered on the URL-Path /login.

The Implementation:

public class GlobalLogoutServlet extends HttpServlet {

  private static final String LOGOUT_PARAM = "logout";

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)  throws ServletException, IOException {
    String queryString = req.getQueryString();
    if (LOGOUT_PARAM.equalsIgnoreCase(queryString)) {
      requestGlobalLogout(req, resp);
    }
  }

  private void requestGlobalLogout(HttpServletRequest req,  HttpServletResponse resp)
      throws ServletException, IOException {
    AuthFacade authBean = new  CommonBeanAccessorGeneric<AuthFacade>().getFacade(AuthFacade.BEAN_NAME,  AuthFacade.class);
    try {
      authBean.logout(req, resp);
    } catch (PrismaLoginException e) {
      throw new ServletException(e);
    }
  }


As you can see there is a call to an EJB (AuthFacade) which does application login (loading roles and permissions from our database) and in this case the logout which is implemented as follows:

  @Override
  protected void doLogout(HttpServletRequest request,  HttpServletResponse response) throws PrismaLoginException {
    //HttpSession session = request.getSession();
    //session.invalidate();
    try {
      request.logout();
      String ctp = getGlobalLogoutUrl(request);
      response.sendRedirect(ctp);
    }catch(Exception e) {
      throw new PrismaLoginException(e);
    }
  }

  private String getGlobalLogoutUrl(HttpServletRequest request) {
    String ctp = request.getContextPath();
    ctp = cutTrailingSlash(ctp);
    ctp = ctp + "?GLO=true";
    return ctp;
  }

Referring to https://lists.jboss.org/pipermail/keycloak-user/2018-August/015164.html the call to HttpServletRequest.logout() is not a no-op (it does something with the SecurityContext?) , but it does not trigger a LogoutRequest to the IdP.
This is only done when I also call an URL with the queryParam "?GLO=true". This triggers another AuthnRequest (WHY?) so there is another login on the broker
After this, there happens one of two cases:


  1.  in idp initiated login setup - the response is sent to the application and there is an error page shown with something like "user already logged in" and a link to "back to application"
     *   also a logout request is never sent
  2.  in sp initiated login setup - there is a logout request and a logout response with status success follows up leading to a redirect to the master processing something like https://myapp/webcontext/saml and a 403 Forbidden

Please help me pin down these problems. I have read a lot of related posts, but I cannot get it to work.

https://lists.jboss.org/pipermail/keycloak-user/2018-August/015164.html

I also wrote a message to Red Hat and we might get a subscription in future to get more support, but they haven't answered yet.



I will provide some configuration details for the setup with the idp-initiated third party idp login:

jboss-web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<jboss-web>
  <security-domain>keycloak</security-domain>
</jboss-web>

web.xml in war:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee  http://www.oracle.com/webfolder/technetwork/jsc/xml/ns/javaee/web-app_3_0.xsd"
      version="3.0">
      <display-name>myapp</display-name>
      <context-param>
            <param-name>resteasy.scan</param-name>
            <param-value>true</param-value>
      </context-param>
</web-app>


web-fragment.xml of the login module jar:

<web-fragment metadata-complete="true" version="3.0"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee  http://java.sun.com/xml/ns/javaee/web-fragment_3_0.xsd">
    <name>SAMLLoginFragment</name>
    <servlet>
        <servlet-name>GlobalLogoutServlet</servlet-name>
        <servlet-class>at.prismasolutions.ppcf.core.auth.login.pvpsaml.GlobalLogoutServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>GlobalLogoutServlet</servlet-name>
        <url-pattern>/login</url-pattern>
    </servlet-mapping>
    <servlet>
        <servlet-name>PublicContentServlet</servlet-name>
        <servlet-class>at.prismasolutions.ppcf.core.common.service.PublicContentServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>PublicContentServlet</servlet-name>
        <url-pattern>/public/*</url-pattern>
    </servlet-mapping>
    <servlet>
        <servlet-name>SAMLLoginServlet</servlet-name>
        <servlet-class>at.prismasolutions.ppcf.core.auth.login.pvpsaml.SAMLLoginServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>SAMLLoginServlet</servlet-name>
        <url-pattern>/saml_login</url-pattern>
    </servlet-mapping>
    <security-role>
        <role-name>*</role-name>
    </security-role>
    <!-- protected -->
    <security-constraint>
        <web-resource-collection>
           <web-resource-name>The protected  resources</web-resource-name>
           <url-pattern>/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
           <role-name>*</role-name>
        </auth-constraint>
        <user-data-constraint>
           <transport-guarantee>NONE</transport-guarantee>
        </user-data-constraint>
    </security-constraint>
    <!-- unprotected -->
    <security-constraint>
        <web-resource-collection>
           <web-resource-name>The unprotected  resources</web-resource-name>
           <url-pattern>/public/*</url-pattern>
           <url-pattern>/saml_login</url-pattern>
        </web-resource-collection>
        <!-- note the missing auth-constraint -->
        <user-data-constraint>
           <transport-guarantee>NONE</transport-guarantee>
        </user-data-constraint>
    </security-constraint>
    <login-config>
        <auth-method>KEYCLOAK-SAML</auth-method>
    </login-config>
    <!-- Session Configuration -->
    <session-config>
        <cookie-config>
           <path>/</path>
        </cookie-config>
    </session-config>
</web-fragment>

Client keycloak-saml.xml

<keycloak-saml-adapter>
    <SP entityID="myapp"
        sslPolicy="EXTERNAL">
        <Keys>
            <Key signing="true">
                <KeyStore
                    file="${jboss.server.config.dir}/keystore/keystore.jks"
                    password="*****">
                    <PrivateKey
                        alias="mykey"
                        password="****" />
                    <Certificate
                        alias="mycert" />
                </KeyStore>
            </Key>
            <Key encryption="true">
                <KeyStore
                    file="${jboss.server.config.dir}/keystore/keystore.jks"
                    password="*****">
                    <PrivateKey
                        alias="mykey"
                        password="*****" />
                </KeyStore>
            </Key>
        </Keys>
        <PrincipalNameMapping policy="FROM_ATTRIBUTE"
            attribute="USERID" />
        <IDP entityID="idp" signatureAlgorithm="RSA_SHA256"
            signatureCanonicalizationMethod="http://www.w3.org/2001/10/xml-exc-c14n#">
            <SingleSignOnService signRequest="true"
                validateResponseSignature="true" validateAssertionSignature="true"
                requestBinding="POST"
                bindingUrl="https://mybroker/auth/realms/MYREALM/protocol/saml" />
            <SingleLogoutService signRequest="true"
                signResponse="true" validateRequestSignature="true"
                validateResponseSignature="true" requestBinding="POST"
                responseBinding="POST"
                postBindingUrl="https://mybroker/auth/realms/MYREALM/protocol/saml"
                redirectBindingUrl="https://mybroker/auth/realms/MYREALM/protocol/saml" />
        </IDP>
    </SP>
</keycloak-saml-adapter>




Keycloak related snippets from the standalone.xml:

<extension module="org.keycloak.keycloak-saml-adapter-subsystem"/>

<security-domain name="keycloak" cache-type="default"/>
</security-domains>

<subsystem xmlns="urn:jboss:domain:keycloak-saml:1.1"/>



<server name="default-server">
                <http-listener name="default" max-connections="100" socket-binding="http" max-post-size="10000000000$ redirect-socket="https" proxy-address-forwarding="true" enable-http2="true"/>

...


The imported metadata in the third party idp for idp initiated setup:

<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" entityID="https://mybroker/auth/realms/MYREALM">
    <SPSSODescriptor AuthnRequestsSigned="true" WantAssertionsSigned="true"
            protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol http://schemas.xmlsoap.org/ws/2003/07/secext">
        <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
        <KeyDescriptor use="signing">
          <dsig:KeyInfo xmlns:dsig="http://www.w3.org/2000/09/xmldsig#<http://www.w3.org/2000/09/xmldsig>">
            <dsig:KeyName>*****</dsig:KeyName>
            <dsig:X509Data>
              <dsig:X509Certificate>*****</dsig:X509Certificate>
            </dsig:X509Data>
          </dsig:KeyInfo>
        </KeyDescriptor>
        <KeyDescriptor use="encryption">
          <dsig:KeyInfo xmlns:dsig="http://www.w3.org/2000/09/xmldsig#<http://www.w3.org/2000/09/xmldsig>">
            <dsig:KeyName>*****</dsig:KeyName>
            <dsig:X509Data>
              <dsig:X509Certificate>*****</dsig:X509Certificate>
            </dsig:X509Data>
          </dsig:KeyInfo>
        </KeyDescriptor>
        <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://mybroker/auth/realms/MYREALM/broker/myidp/endpoint"/<https://mybroker/auth/realms/MYREALM/broker/myidp/endpoint>>
        <AssertionConsumerService
                Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://mybroker/auth/realms/MYREALM/broker/myidp/endpoint/clients/myapp"
                index="1" isDefault="true" />
    </SPSSODescriptor>
</EntityDescriptor>


I would be happy about any additional information concerning saml logout:


  *   Informations about all configurations related to logout in any parts of the system
  *   Informations about all Keycloak classes the logout process depends on
  *   Do we HAVE to specify a logout.jsp?


  *   Additional Keycloak settings - what do these really do?


     *   Idp Broker:

[cid:image001.png at 01D5A45E.524321B0][cid:image002.png at 01D5A45E.524321B0]

Example Single Logout Service URL: https://externalidp/profile/SAML2/Redirect/SLO<https://portal.salzburg.gv.at/stdportal-idp/portalverbund.gv.at/profile/SAML2/Redirect/SLO>

[cid:image003.png at 01D5A45E.524321B0][cid:image004.png at 01D5A45E.524321B0]


     *   Client:
[cid:image005.png at 01D5A45E.524321B0][cid:image006.png at 01D5A45E.524321B0]


  *   Could these client settings have anything to do with the logout process?
[cid:image007.png at 01D5A45E.524321B0][cid:image008.png at 01D5A45E.524321B0]

I will be really grateful for any help resolving this issue. Please feel free to ask for more information, I will provide anything I can.

Thankfully,

Manuel Waltschek


[Logo]

Manuel Waltschek BSc.

+43 660 86655 47<tel:+436608665547>
manuel.waltschek at prisma-solutions.at<mailto:manuel.waltschek at prisma-solutions.at>
https://www.prisma-solutions.com

PRISMA solutions EDV-Dienstleistungen GmbH
Klostergasse 18, 2340 Mödling, Austria
Firmenbuch: FN 239449 g, Landesgericht Wiener Neustadt
-------------- next part --------------
A non-text attachment was scrubbed...
Name: image001.png
Type: image/png
Size: 3055 bytes
Desc: image001.png
Url : http://lists.jboss.org/pipermail/keycloak-user/attachments/20191126/c4960579/attachment-0009.png 
-------------- next part --------------
A non-text attachment was scrubbed...
Name: image002.png
Type: image/png
Size: 7091 bytes
Desc: image002.png
Url : http://lists.jboss.org/pipermail/keycloak-user/attachments/20191126/c4960579/attachment-0010.png 
-------------- next part --------------
A non-text attachment was scrubbed...
Name: image003.png
Type: image/png
Size: 2800 bytes
Desc: image003.png
Url : http://lists.jboss.org/pipermail/keycloak-user/attachments/20191126/c4960579/attachment-0011.png 
-------------- next part --------------
A non-text attachment was scrubbed...
Name: image004.png
Type: image/png
Size: 4427 bytes
Desc: image004.png
Url : http://lists.jboss.org/pipermail/keycloak-user/attachments/20191126/c4960579/attachment-0012.png 
-------------- next part --------------
A non-text attachment was scrubbed...
Name: image005.png
Type: image/png
Size: 2603 bytes
Desc: image005.png
Url : http://lists.jboss.org/pipermail/keycloak-user/attachments/20191126/c4960579/attachment-0013.png 
-------------- next part --------------
A non-text attachment was scrubbed...
Name: image006.png
Type: image/png
Size: 4153 bytes
Desc: image006.png
Url : http://lists.jboss.org/pipermail/keycloak-user/attachments/20191126/c4960579/attachment-0014.png 
-------------- next part --------------
A non-text attachment was scrubbed...
Name: image007.png
Type: image/png
Size: 8824 bytes
Desc: image007.png
Url : http://lists.jboss.org/pipermail/keycloak-user/attachments/20191126/c4960579/attachment-0015.png 
-------------- next part --------------
A non-text attachment was scrubbed...
Name: image008.png
Type: image/png
Size: 21151 bytes
Desc: image008.png
Url : http://lists.jboss.org/pipermail/keycloak-user/attachments/20191126/c4960579/attachment-0016.png 
-------------- next part --------------
A non-text attachment was scrubbed...
Name: image009.png
Type: image/png
Size: 6418 bytes
Desc: image009.png
Url : http://lists.jboss.org/pipermail/keycloak-user/attachments/20191126/c4960579/attachment-0017.png 


More information about the keycloak-user mailing list