Set of valves classes that helps CERN applications with the integration in the CERN Authentication aka CERN SSO.
There are few scenarios where these components can be applied:
- Authentication: Applications composed by multiple contexts which make requests between them from the browser. For instance the edh travel request document (/Document/Claims context) makes a request to the ROOT context (/) to get the celebration dates (CERN official holidays).
- Applications requiring custom cookies in the request . Some examples:
- AI_SESSION for edh.cern.ch
- AI_USER for e-groups.cern.ch
- Applications requiring custom HTTP headers for identifying the authenticated user or authorize it. This is the case of the CERN Oracle APEX applications and the CERN ERP (aka Qualiac but recently aquired by CEGID, see https://www.cegid.com/fr/produits/cegid-xrp-ultimate/)
- Applications exposing documents with white spaces in the document name, using multiple proxies. The URI, and more specifically the document name has to be scaped. This was the case of some document listings for the treasury team. Hopefully this scenario has been deprecated with the removal of the
weblogic.servlet.proxy.HttpProxyServlet
from our infrastructure.
Ensure you have JDK 7 (or newer), Maven 3 (or newer) and Git installed:
java -version
mvn -version
git --version
The cern-tomcat-sso-test-suite has a dependency with the **cern-servlet-basic-checks ** one. This maven artifact is stored in our jeedy-applications maven repository. In order your maven installation is able to download it, you have to declare it in your maven settings configuration file.
<profile>
<id>jeedy-applications</id>
<repositories>
<repository>
<id>jeedy-applications</id>
<name>Repository for the CERN JEEDY applications (CERN SSO integration components)</name>
<url>https://jeedy-nexus-repo.web.cern.ch/repository/jeedy-applications/</url>
<layout>default</layout>
<snapshotPolicy>always</snapshotPolicy>
</repository>
</repositories>
</profile>
If you are not familiar with maven probably the Maven in 5 Minutes can be helpful. In the last times I've found very good material about maven, and in general about java and spring stuff, at baeldung web site. Also the good and old mykong.com is a reliable source.
Clone and build:
git clone https://:@gitlab.cern.ch:8443/jeedy/sso-integrations/tomcat-components/tomcat-sso-integration-components.git
cd tomcat-sso-integration-components/
mvn clean package
Once completed you will find the .jar files in the target folder of each module. The two libraries that have to be installed in tomcat are:
- cern-tomcat-authentication.jar
- cern-tomcat-session-utils.jar
The cern-tomcat-authentication-kit.jar, cern-tomcat-session-utils.jar and the keycloak tomcat saml adapter libraries must be added to the common-loader. Just edit the $CATALINA_BASE/conf/catalina.properties
and update the common.loader
entry:
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar","${catalina.home}/sso/keycloak/lib/7.0.0/*.jar","${catalina.home}/sso/cern-custom/lib/*.jar"
The Keycloaklibraries can be downloaded from the keycloak downloads page.
Just declare in the application context definition the required valve(s) that implements your scenario. The valves are executed in the order their declarations are defined in the context configuration file. For instance:
<Context path="/web-module-4" crossContext="true">
<Valve className="ch.cern.sso.tomcat.valves.mocks.KeycloakAuthenticatorValve"/>
<Valve className="ch.cern.sso.tomcat.valves.AiCookiesValve"/>
<Parameter name="groups.loginas" value="edh-team,it-dep-db-dar" override="true"/>
<Parameter name="aicookies" value="AI_USERNAME,AI_USER,AI_IDENTITY_CLASS,AI_LANG,AI_HRID" override="true"/>
</Context>
KeycloakAuthenticatorValve
will be executed in the first place and AiCookiesValve
right after. Contrary to servlet filters where you can define url-pattern
(see servlet spec 6.2.4 chapter), the valves are always inserted and executed in the request pipeline.
IMPORTANT: ensure that you declare org.apache.catalina.authenticator.SingleSignOn in your $CATALINA_BASE/conf/server.xml
. This valves keeps the tomcat SSO session that allows the calls between contexts withouth being redirected to the authentication page (login.cern.ch).
<Valve className="org.apache.catalina.authenticator.SingleSignOn"/>
You can find them at cern-tomcat-sso-test-suite.
Each of them run an embedded tomcat behind the scenes.
This class creates an instance of org.apache.catalina.realm.GenericPrincipal which contains an instance of org.keycloak.adapters.saml.SamlPrincipal with all the attributes attributes of the authenticated user.
The context.xml of each test webapp can be found under src/test/resources/keycloak-saml:
<Context path="/web-module-3" crossContext="true">
<Valve className="ch.cern.sso.tomcat.valves.mocks.AuthenticatorMockValve"/>
<Valve className="ch.cern.sso.tomcat.valves.AiSessionValve"/>
</Context>
For instance in the above example we want to test the different use cases of the AiSessionValve. For checking them we make use of the servlets created in the cern-servlet-basic-checks:
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2020 CERN
This software is distributed under the terms of the GNU General Public
Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING". In
applying this licence, CERN does not waive the privileges and immunities
granted to it by virtue of its status as an Intergovernmental Organization or
submit itself to any jurisdiction.
-->
<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://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<module-name>web-module-3</module-name>
<servlet>
<servlet-name>CookieInfoServlet</servlet-name>
<servlet-class>ch.cern.sso.sp.examples.CookieInfoServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CookieInfoServlet</servlet-name>
<url-pattern>/cookie-info</url-pattern>
</servlet-mapping>
<security-constraint>
<web-resource-collection>
<web-resource-name>secure</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>*</role-name>
</auth-constraint>
</security-constraint>
<security-role>
<role-name>*</role-name>
</security-role>
</web-app>
This class checks if there is an authorization header in the HTTP request. If not it invokes the doAuthenticate() method of the parent class, org.apache.catalina.authenticator.BasicAuthenticator
which will pop-up the good old classic basic authentication screen. You can enter any credentials. The credentials adds the authorization header in the request, making the valve to inject an instance of GenericPrincipal
in the request. Developers can configure username and roles via mock.username
and mock.roles
context parameters. Below you can find a test case for this valve:
@Test
public void testUserIsAuthenticated() {
WebDriver browser = getBrowser(null, null);
browser.get("http://localhost:8082/web-module-1/principal-info");
Utils.assertTitleEquals(browser, "HTTP Status 401 – Unauthorized");
browser = getBrowser("Authorization", "Basic YWxhZGRpbjpvcGVuc2VzYW1l");
browser.get("http://localhost:8082/web-module-1/principal-info");
Utils.assertStringIsDisplayed(browser, MockConstants.PRINCIPAL_NAME);
browser.close();
}
The context.xml of /web-module-1 would look like this:
<Context>
<Valve className="ch.cern.sso.tomcat.valves.mocks.BasicAuthenticatorMockPrincipalInjectionValve"/>
<Parameter name="mock.username" value="bob" override="true"/>
<Parameter name="mock.roles" value="edh-self-service-stores-catalog,it-dep-db-dar" override="true"/>
</Context>
And remember to add the <login-config>
element in your web.xml:
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>web-module-1 protected area</realm-name>
</login-config>
NOTE: you can always skip the tests with the -DskipTests=true maven option.
The <version>
element in the parent pom declares current SNAPSHOT version we are working on. The sub-modules inherit from the parent:
<parent>
<groupId>ch.cern.sso.sp.tomcat</groupId>
<artifactId>tomcat-sso-integration-components</artifactId>
<version>2.0.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
When you start to work in a new feature you can:
- Create a new branch linked with the corresponding JIRA issue
git checkout -b jeedy-1289-readme
- Increase the SNAPSHOT version. You can use
mvn versions:set -DnextSnapshot=true
. You can find more info about the versions plugin here.
Pushing your changes to the branch will trigger the snapshot-package-build in the gitlab-ci job, uploading the target jars to the snapshots nexus repository. Once the changes are merged, release-package-build will be triggered and the jars will be uploaded to the release nexus repository.
Extends from SamlAuthenticatorValve.
It checks if the user has been authenticated and if it has a valid tomcat session, if not it invokes the authenticate method of the parent SamlAuthenticatorValve. At the first login it creates an instance of org.apache.catalina.realm.GenericPrincipal setting to this an instance of org.keycloak.adapters.saml.SamlPrincipal with all the user attributes. Finally it register this principal in the current session and with our SingleSignOn valve.
If the URI request match the logout one (/saml2slo/saml) the SAML logout is called. This one kills the local tomcat session and invokes the next application in the SSO logout chain.
It adds to the request the AI_SESSION cookie based in some authenticated user's attributessome authenticated user's attributes. AI_SESSION is never set in the response, so it will never be present in the user browser. If an AI_SESSION is present in the user request it will be dropped.
If the status.loginas=true
and the user is member of any of the groups.loginas
(context parameters ) the value of the AI_SESSION cookie will be updated according with the AI_LOGIN_AS cookie value.
In the same fashion as the AiSessionValve this class will add the set of cookies declared in the aicookies
context param. As in the AiSessionValve, if present in the original user request these cookies will be dropped.
Some applications like the ERP (formerly known as Qualiac Qualiac) and CERN APEX applications require the injection of a header in the request with the authenticated user name. SsoHeadersValve decorates the http request, overriding the different getHeader methods of the HttpServletRequest. In this way when the application invokes any of these methods it will be invoking our code. You can find an example of its configuration below:
<Context path="/web-module-2" crossContext="true">
<Valve className="ch.cern.sso.tomcat.valves.mocks.AuthenticatorMockValve"/>
<Valve className="ch.cern.sso.tomcat.valves.SsoHeadersValve"/>
<Parameter name="sso.remote.headers" value="SSO_REMOTE_USER"/>
</Context>
This valve admits three headers:
SSO_REMOTE_USER
& REMOTE_USER
: both are filled with request.getUserPrincipal().getName()
. First one is used in the APEX applications and second one by Qualiac.
SSO_REMOTE_HOST
: takes its value from request.getRemoteAddr() It is used by mainly by the EDMS applications for identify the client. NOTE: this will not work in a "multi-proxy environment" like our kubernetes setup.
The ERP expose some URLs with listings of documents. In the current configuration these URLs are behind two proxies. This is causing some issues when the document names contain blank spaces. In order to solve this issues we have to provide an implementation of getRequestURI. Hopefully with the new simplified architecture this valve is not needed anymore.
With Oracle REST Data Services (ORDS) running on Tomcat there exists a problem when authenticating through realms other than UserDatabaseRealm. This valve solves that issue for ORDS >= 18.1.1. For more information see db-blog entry.
A parameter ords.role.prefixes
has to be set, in similar manner as for SsoHeadersValve, e.g:
...
<Parameter name="sso.remote.headers" value="ords-rest-access-"/>
...
Multiple values can be comma-separated:
...
<Parameter name="sso.remote.headers" value="ords-rest-access-,jeedy-"/>
...
If no value is provided, the roles are not filtered.
Yes, as there are few applications still using Java7 I have declared both maven.compiler.source
and maven.compiler.target
1.7
:(
Copyright (c) 2019 CERN