0

I try to implement an client-cert-authentication to access jetty-based content. E.g. the URL http://www.example.com/testsystem/idp/spapi should be only accessed with valid client-certificate.

I get following error on jetty-start:

2021-08-12 14:25:22.967:WARN :oejuc.AbstractLifeCycle:main: FAILED org.eclipse.jetty.server.session.SessionHandler1528923159==dftMaxIdleSec=1800: java.lang.IllegalStateException: No LoginService for org.eclipse.jetty.security.authentication.SslClientCertAuthenticator@49dbaaf3 in ConstraintSecurityHandler@6c284af{STARTING}

Using:

  • openjdk 11.0.12
  • Jetty 10.0.6

Configuration:

start.ini

--module=server
jetty.httpConfig.sendServerVersion=false
--module=jsp
--module=annotations
--module=deploy
--module=logging-jetty
--module=console-capture
--module=ext
--module=requestlog
--module=http-forwarded
--module=plus
--module=rewrite
--module=jstl
--module=servlets
--module=http
--module=ssl
--module=https
jetty.sslContext.keyStorePath=credentials/server.keystore
jetty.sslContext.keyStorePassword=mypassword
jetty.sslContext.keyManagerPassword=mypassword
jetty.sslContext.trustStorePath=credentials/server.keystore
jetty.sslContext.trustStorePassword=mypassword
jetty.sslContext.needClientAuth=true

idp.xml

<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
  <Set name="war">/opt/shibboleth-idp/war/idp.war</Set>
  <Set name="contextPath">/testsystem/idp</Set>
  <Set name="extractWAR">false</Set>
  <Set name="copyWebDir">false</Set>
  <Set name="copyWebInf">true</Set>
  <Set name="persistTempDirectory">false</Set>
</Configure>

web.xml

<?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://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">
     <display-name>Shibboleth Identity Provider</display-name>
    <!-- Spring application context files. Files are loaded in the order they appear with subsequent files overwriting 
        same named beans in previous files. -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:/META-INF/net.shibboleth.idp/preconfig.xml,${idp.home}/system/conf/global-system.xml,classpath*:/META-INF/net.shibboleth.idp/postconfig.xml</param-value>
    </context-param>
    
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>net.shibboleth.ext.spring.context.DelimiterAwareApplicationContext</param-value>
    </context-param>
    
    <context-param>
        <param-name>contextInitializerClasses</param-name>
        <param-value>net.shibboleth.idp.spring.IdPPropertiesApplicationContextInitializer</param-value>
    </context-param>

    <!-- Spring listener used to load up the configuration -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    <!-- Filters and filter mappings -->
    
    <!-- Try and force I18N, probably won't help much. -->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <!-- Automates SameSite handling until Java API catches up. -->
    <filter>
        <filter-name>SameSiteCookieFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>shibboleth.SameSiteCookieFilter</param-value>
        </init-param>
    </filter>
    <!-- Lets us lump repeated Set-Cookie headers into one, something containers rarely support. -->
    <filter>
        <filter-name>CookieBufferingFilter</filter-name>
        <filter-class>net.shibboleth.utilities.java.support.net.CookieBufferingFilter</filter-class>
    </filter>
    <!-- Allows control of response headers from within Spring beans. -->
    <filter>
        <filter-name>DynamicResponseHeaderFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>shibboleth.ResponseHeaderFilter</param-value>
        </init-param>
    </filter>
    <!-- Automates TLS-based propagation of HttpServletRequest/Response into beans. -->
    <filter>
        <filter-name>RequestResponseContextFilter</filter-name>
        <filter-class>net.shibboleth.utilities.java.support.net.RequestResponseContextFilter</filter-class>
    </filter>
    <!-- Manages logging MDC. -->
    <filter>
        <filter-name>SLF4JMDCServletFilter</filter-name>
        <filter-class>net.shibboleth.idp.log.SLF4JMDCServletFilter</filter-class>
    </filter>
    
    <filter-mapping>
        <filter-name>SameSiteCookieFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <filter-name>CookieBufferingFilter</filter-name>
        <url-pattern>/profile/admin/*</url-pattern>
        <url-pattern>/profile/Logout</url-pattern>
        <url-pattern>/profile/Shibboleth/SSO</url-pattern>
        <url-pattern>/profile/SAML2/Unsolicited/SSO</url-pattern>
        <url-pattern>/profile/SAML2/Redirect/SSO</url-pattern>
        <url-pattern>/profile/SAML2/POST/SSO</url-pattern>
        <url-pattern>/profile/SAML2/POST-SimpleSign/SSO</url-pattern>
        <url-pattern>/profile/SAML2/Artifact/SSO</url-pattern>
        <url-pattern>/profile/SAML2/Redirect/SLO</url-pattern>
        <url-pattern>/profile/SAML2/POST/SLO</url-pattern>
        <url-pattern>/profile/SAML2/POST-SimpleSign/SLO</url-pattern>
        <url-pattern>/profile/SAML2/Artifact/SLO</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <filter-name>DynamicResponseHeaderFilter</filter-name>
        <url-pattern>/profile/admin/*</url-pattern>
        <url-pattern>/profile/Shibboleth/SSO</url-pattern>
        <url-pattern>/profile/SAML2/Unsolicited/SSO</url-pattern>
        <url-pattern>/profile/SAML2/Redirect/SSO</url-pattern>
        <url-pattern>/profile/SAML2/POST/SSO</url-pattern>
        <url-pattern>/profile/SAML2/POST-SimpleSign/SSO</url-pattern>
        <url-pattern>/profile/SAML2/Artifact/SSO</url-pattern>
        <url-pattern>/Authn/*</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <filter-name>RequestResponseContextFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <filter-name>SLF4JMDCServletFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- Servlets and servlet mappings -->    
    <servlet>
        <servlet-name>idp</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>${idp.home}/system/conf/mvc-beans.xml, ${idp.home}/system/conf/webflow-config.xml</param-value>
        </init-param>
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>net.shibboleth.ext.spring.context.DelimiterAwareApplicationContext</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>idp</servlet-name>
        <url-pattern>/status</url-pattern>
        <url-pattern>/profile/*</url-pattern>
    </servlet-mapping>

    <!-- Servlet protected by container used for RemoteUser authentication -->
    <servlet>
        <servlet-name>RemoteUserAuthHandler</servlet-name>
        <servlet-class>net.shibboleth.idp.authn.impl.RemoteUserAuthServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>RemoteUserAuthHandler</servlet-name>
        <url-pattern>/Authn/RemoteUser</url-pattern>
    </servlet-mapping>

    <!-- Servlet protected by container used for X.509 authentication -->
    <servlet>
        <servlet-name>X509AuthHandler</servlet-name>
        <servlet-class>net.shibboleth.idp.authn.impl.X509AuthServlet</servlet-class>
        <load-on-startup>3</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>X509AuthHandler</servlet-name>
        <url-pattern>/Authn/X509</url-pattern>
    </servlet-mapping>

    <!-- Send request for the EntityID to the SAML metadata echoing JSP. -->
    <servlet>
        <servlet-name>shibboleth_jsp</servlet-name>
        <jsp-file>/WEB-INF/jsp/metadata.jsp</jsp-file>
    </servlet>
    <servlet-mapping>
        <servlet-name>shibboleth_jsp</servlet-name>
        <url-pattern>/shibboleth</url-pattern>
    </servlet-mapping>

    <!-- Send servlet errors through the IdP's MVC error handling. -->
    <error-page>
        <exception-type>net.shibboleth.idp.authn.ExternalAuthenticationException</exception-type>
        <location>/profile/RaiseError</location>
    </error-page>

    <session-config>
        <cookie-config>
            <http-only>true</http-only>
            <secure>true</secure>
        </cookie-config>
        <tracking-mode>COOKIE</tracking-mode>
    </session-config>

    <!-- Allow intended methods by using an absent auth-constraint. -->
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Non-API Content</web-resource-name>
            <url-pattern>/*</url-pattern>
            <http-method>GET</http-method>
            <http-method>HEAD</http-method>
            <http-method>OPTIONS</http-method>
            <http-method>POST</http-method>
            <http-method>PUT</http-method>
        </web-resource-collection>
        <!-- no auth-constraint tag here -->
    </security-constraint>

    <!-- Disallow other methods by using an empty auth-constraint. -->
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Non-API Content</web-resource-name>
            <url-pattern>/*</url-pattern>
            <http-method-omission>GET</http-method-omission>
            <http-method-omission>HEAD</http-method-omission>
            <http-method-omission>OPTIONS</http-method-omission>
            <http-method-omission>POST</http-method-omission>
        </web-resource-collection>
        <authn-constraint/>
    </security-constraint>

    <!-- Allow any HTTP methods to the API flows. -->
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Administrative APIs</web-resource-name>
            <url-pattern>/profile/admin/*</url-pattern>
        </web-resource-collection>
        <!-- no auth-constraint tag here -->
    </security-constraint>

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Api</web-resource-name>
            <url-pattern>/spapi/*</url-pattern>
        </web-resource-collection>
        <user-data-constraint>
            <transport-guarantee>CONFIDENTIAL</transport-guarantee>
        </user-data-constraint>
    </security-constraint>
    <login-config>
        <auth-method>CLIENT-CERT</auth-method>
    </login-config>
</web-app>

If I remove the last security-constraint Jetty starts without any error but also without any client-cert-support.

Any hints are welcome.

Joakim Erdfelt
  • 46,896
  • 7
  • 86
  • 136

1 Answers1

0

To use <auth-method>CLIENT-CERT</auth-method> you need a realm defined, that provides what Servlet security roles each Certificate Subject belongs to.

That means you need to define a LoginService that will pull that information for your "realm" in.

You have many options here.

  • JAASLoginService - use a dynamic JAAS source to configure the realm/subject/roles
  • HashLoginService - use a static text file to configure the realm/subject/roles
  • DataSourceLoginService - use a dynamic JNDI DataSource to configure the realm/subject/roles
  • JDBCLoginService - use a JDBC driver to configure the realm/subject/roles

Note: there are two LoginService implementations that do not support <auth-method>CLIENT-CERT</auth-method>, so ignore both ConfigurableSpnegoLoginService and OpenIDLoginService

Each implementation has it's own configuration techniques unique to that LoginService. JAAS is configured both on the server and the webapp. The rest are configured only on the webapp.

Are you sure you want all of this?
Or do you just want to enable TLS Client Certificates?

If so, you configure the SslContextFactory.Server and one (or both) of the options

See more information on these settings in Java here - https://stackoverflow.com/a/14876605/775715

Joakim Erdfelt
  • 46,896
  • 7
  • 86
  • 136