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...;.
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...
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-c...
<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/...
<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/...
<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@01D5A45E.524321B0][cid:image002.png@01D5A45E.524321B0]
Example Single Logout Service URL:
https://externalidp/profile/SAML2/Redirect/SLO<https://portal.salzburg...
[cid:image003.png@01D5A45E.524321B0][cid:image004.png@01D5A45E.524321B0]
* Client:
[cid:image005.png@01D5A45E.524321B0][cid:image006.png@01D5A45E.524321B0]
* Could these client settings have anything to do with the logout process?
[cid:image007.png@01D5A45E.524321B0][cid:image008.png@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@prisma-solutions.at<mailto:manuel.waltschek@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