This article covers a Hippo CMS version 7.7. There's an updated version available that covers our most recent release.

HST-2 Authentication and Authorization Support 


Since HST-2.20, Hippo JAAS Login Module and Form-based Login Servlet are supported. Also, flexible authorization configuration for a mount or site map item is supported.

1. Introduction

For authentication purpose, HST-2 provides a default Hippo login module implementation. Login modules provides a standard way to expose authentication services for Java application. More information about login modules can be found in the JDKLoginModule interface documentation.

Also, for authorization purpose, HST-2 provides configurations to secure a mount with authorized roles or users.

Furthermore, HST-2 provides various extensible options to customize the form login page and login error page.

Recently a Hippo forge project has been added providing integration with Spring Security. The HST Spring Security Support forge project provides an authentication provider against Hippo Repository and a Spring Security aware Security Valve in order to let you leverage Spring Security instead of the default JAAS based security module.

2. Hippo Login Module Configuration

To enable Hippo Login Module, add the following configuration in /WEB-INF/hst-config.properties:

# HST JAAS login configuration
java.security.auth.login.config = classpath:/org/hippoecm/hst/security/impl/login.conf

With the configuration above, HST-2 Container will register the Hippo Login Module implementation as JAAS Login Module.

3. Realm Configuration

To configure Container Managed Security, you need to configure the Realm, which is normally configured in the application context configuration.

Please note that the Realm name must the same as the Realm name in your web.xml.

In Tomcat, you can configure the Realm like the following in the context configuration file:

<?xml version="1.0" encoding="UTF-8" ?>
<Context crossContext="true">
  <Realm className="org.apache.catalina.realm.JAASRealm"
         appName="HSTSITE"
         userClassNames="org.hippoecm.hst.security.TransientUser"
         roleClassNames="org.hippoecm.hst.security.TransientRole"
         useContextClassLoader="true"
         debug="0"/>
  
  <Valve className="org.apache.catalina.authenticator.FormAuthenticator" characterEncoding="UTF-8" />

  <!-- You may use BASIC Authentication as well instead of FORM Authentication.
  <Valve className="org.apache.catalina.authenticator.BasicAuthenticator" />
  -->

</Context>

In the configuration example above, you can normally use FORM Authentication option for more flexibility, but you may also use BASIC Authentication option as well by using the "org.apache.catalina.authenticator.BasicAuthenticator" valve instead of "org.apache.catalina.authenticator.FormAuthenticator" valve.

Also, in Jetty, one simplest way to configure this is to add jetty-env.xml into /WEB-INF/ directory like the following file:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://jetty.mortbay.org/configure.dtd">

<Configure class="org.mortbay.jetty.webapp.WebAppContext">
  <Get name="securityHandler">
    <Set name="userRealm">
      <New class="org.mortbay.jetty.plus.jaas.JAASUserRealm">
        <Set name="name">HST Site Realm</Set>
        <Set name="LoginModuleName">HSTSITE</Set>
        <Set name="CallbackHandlerClass">
          org.mortbay.jetty.plus.jaas.callback.DefaultCallbackHandler
        </Set>
        <Set name="roleClassNames">
          <Array type="String">
            <Item>org.hippoecm.hst.security.TransientRole</Item>
          </Array>
        </Set>
      </New>
    </Set>
  </Get>
</Configure>

4. Form-based Authentication Configuration with LoginServlet

To use a Login Module in web environment with Form-based authentication, you should add the following servlet configuration in your web.xml.

This Login Module is included in hst-security-2.2x.xx.jar. So, you could add this jar with the following dependency configuration in your project pom:

    <dependency>
      <groupId>org.onehippo.cms7.hst.components</groupId>
      <artifactId>hst-security</artifactId>
      <version>${hippo.hst.version}</version>
    </dependency>

And, add the following servlet configuration in your web.xml:

  <servlet>
    <servlet-name>LoginServlet</servlet-name>
    <servlet-class>org.hippoecm.hst.security.servlet.LoginServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>LoginServlet</servlet-name>
    <url-pattern>/login/*</url-pattern>
  </servlet-mapping>

Also, you need to add the following configuration:

  <security-constraint>
    <web-resource-collection>
      <web-resource-name>Login</web-resource-name>
      <url-pattern>/login/resource</url-pattern>
    </web-resource-collection>
    <auth-constraint>
      <role-name>everybody</role-name>
    </auth-constraint>
  </security-constraint>

  <login-config>
    <auth-method>FORM</auth-method>
    <realm-name>HSTSITE</realm-name>
    <form-login-config>
      <form-login-page>/login/login</form-login-page>
      <form-error-page>/login/error</form-error-page>
    </form-login-config>
  </login-config>

  <security-role>
    <description>Default role of Hippo Repository</description>
    <role-name>everybody</role-name>
  </security-role>

With the configurations above, you set a default protected web resource path (/login/resource), which is internally used by the LoginServlet, and FORM-based authentication with the internal login page path and error page path. Also, only for authentication purpose, the default role, 'everybody', is required for the default protected web resource path (/login/resource).

Now, if you restart your application, you can test the login.

Visit /login/form page path to try log on. For example, you can visit http://localhost:8085/site/login/form

Then, enter a CMS username and password to sign in. You will be able to get authenticated.

By the way, you can check if you are authenticated by using HttpServletRequest#getUserPrincipal(). If the method returns a non-null value, then you are authenticated!

If you enter a wrong user name or password, then you will be redirected to the default error page, /login/error.

Note: You should add hst:prefixexclusions in hst:hosts node because the urls with /login/* must be excluded from the HstFilter processing:

    <sv:property sv:name="hst:prefixexclusions" sv:type="String">
        <sv:value>/binaries</sv:value>
        <sv:value>/staticresource</sv:value>
        <sv:value>/images</sv:value>
        <sv:value>/login/</sv:value>
    </sv:property>

5. BASIC Authentication Configuration

If you are using Hippo CMS 7.4, you should use the Hippo Authfilter Forge Plugin.

If you want to use BASIC Authentication option instead of Form-based Authentication option, please follow this section.

Please note that you should use the following valve in the context configuration in order to use BASIC Authentication option in Tomcat. See the example in the comment block in the Section 3 above.

<Valve className="org.apache.catalina.authenticator.BasicAuthenticator" />

<!-- Commenting out the FormAuthenticator valve in order to use BasicAuthenticator valve.
<Valve className="org.apache.catalina.authenticator.FormAuthenticator" characterEncoding="UTF-8" />
-->

To use a Login Module in web environment with BASIC authentication, you should add the following configuration in your web.xml.

The Login Module is included in hst-security-2.2x.xx.jar. So, you could add this jar with the following dependency configuration in your project pom:

    <dependency>
      <groupId>org.onehippo.cms7.hst.components</groupId>
      <artifactId>hst-security</artifactId>
      <version>${hippo.hst.version}</version>
    </dependency>

Also, you need to add your security constraints like the following example. In the following example, let's suppose you want to protect all URL resources starting with /preview/.

  <security-constraint>
    <web-resource-collection>
      <web-resource-name>Preview</web-resource-name>
      <url-pattern>/preview/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
      <role-name>everybody</role-name>
    </auth-constraint>
  </security-constraint>

  <login-config>
    <auth-method>BASIC</auth-method>
    <realm-name>HSTSITE</realm-name>
  </login-config>

  <security-role>
    <description>Default role of Hippo Repository</description>
    <role-name>everybody</role-name>
  </security-role>

With the configurations above, you set your protected web resource path (/preview), which is protected by the servlet container with enabling BASIC Authentication. Also, only for demonstration purpose, the default role, 'everybody', is required for the protected web resource path (/preview).

Please note that you should configure all the URL paths to protect when you are using BASIC Authentication option. Please see SRV.12.8 in the Servlet Specification for more detail.

Now, if you restart your application, you can test the login.

Visit /preview page path. For example, you can visit http://localhost:8085/site/preview.

Then, your browser will popup a login dialog. Enter a CMS username and password to sign in. You will be able to get authenticated.

By the way, you can check if you are authenticated by using HttpServletRequest#getUserPrincipal(). If the method returns a non-null value, then you are authenticated!

If you enter a wrong user name or password, then the servlet container will return HTTP 401 error.

6. Authorization Configuration

(1) Web Application Level Authorization

You can secure a mount or sitemap item by setting "hst:authenticated (boolean)" property to true.

In order to secure the mount or sitemap item properly, you need to set "hst:roles" or "hst:users".

For example, if you set the following properties for either a mount or a sitemap item

- hst:authenticated = true
- hst:roles = editors, approvers

then, the mount will be allowed to users in 'editors' role or users in 'approvers' role. If a user is not authenticated and accesses this resource, then the request will meet HTTP Not Authenticated (401) error. Or if the user is authenticated but the user is in neither 'editors' nor 'approvers' role, then the request will meet a HTTP Forbidden error (403) by default.

HST-2 Container checks the user's roles by the Servlet / JAAS standard APIs such as HttpServletRequest#isUserInRole(roleName). So, with the example above, HST-2 Container will invoke request.isUserInRole("editors") andrequest.isUserInRole("approvers") at least.

Also, you can use or add "hst:users" property like this:

- hst:authenticated = true
- hst:users = editor1, approver1

With the example configuration above, HST-2 Container checks if the user's principal name is included in the "hst:users" property values. HST-2 Container will retrieve the user's principal name by using HttpServletRequest#getUserPrincipal().getName().

Finally, if you have both "hst:roles" and "hst:users", then HST-2 Container will look up both property values in order to see if the user can access the resource. If the user has any role or the user's principal name is included, then the user will be allowed.

Note: "hst:authenticated" property must be type of Boolean!

(2) Repository Level Authorization Integration

HST-2 also provides Repository level authorization integration, meaning JCR session used during the request will be tightly integrated with the authenticated user's subject.

If you want this option, set the following property for a mount:

- hst:subjectbasedsession = true

With the configuration above, JCR session during the request processing is created with the JCR credentials of the authenticated user's subject per each request. In this case, JCR session is not borrowed from the internal JCR session pool.

Optionally, the JCR session with this option can be stored in HTTP session and reused in the following requests for more efficiency. To use this session stateful JCR session usage option, configure the following property for a mount:

- hst:sessionstateful = true

As you can imagine, this option should be used in limited environments, such as application with limited users because it can increase memory size at runtime when used without caution.

Note: "hst:subjectbasedsession" and "hst:sessionstateful" properties must be type of Boolean!

(3) Security Domain Configuration in the Repository

To configure security domains and roles, please refer to the page, Repository Authorization and Permissions.

Hippo Repository provides security domain based authorization, meaning security roles can be differently configured per each security domain.

Therefore, Hippo JAAS Login Module uses only one security domain for authentication. By default the security domain used is 'everywhere'.

If you want to use another security domain, you can configure the following property in /WEB-INF/hst-config.properties:

# Hippo Login Module Authentication Provider configurations
security.authentication.role.domain = everywhere

7. How to utilize the default Form Login Page

There are default built in login screens with script templates (Since v2.24.00, the default login related templates are in freemarker, while the previous versions are in velocity).

These built in logins need some built in packaged css and images. Therefore, you need to add to the web.xml the following:

<servlet>
    <servlet-name>SecurityResourceServlet</servlet-name>
    <servlet-class>org.springframework.js.resource.ResourceServlet</servlet-class>
    <init-param>
    <param-name>jarPathPrefix</param-name>
    <param-value>/META-INF/hst/security</param-value>
    </init-param>
</servlet>

and

<servlet-mapping>
    <servlet-name>SecurityResourceServlet</servlet-name>
    <url-pattern>/login/hst/security/*</url-pattern>
</servlet-mapping>

If you want to only change the css, then you can override the existing css as follows

Add to your src/main/resource the following resources: /META-INF/hst/security/skin/. In this resource package create a file called screen.css

8. (Optional) How to customize Form Login Pages

(1) Custom Form Login page for a mount

You can configure a custom form login page for a mount by configuring "hst:formloginpage" property. For example,

- hst:formloginpage = /examples/custom_form_login.jsp

So, when the mount is protected with the following properties:

- hst:authenticated = true
- hst:roles = everybody
- hst:formloginpage = /examples/custom_form_login.jsp

HST-2 Container will redirect the request on the mount to the custom form login page (custom_form_login.jsp) if the user is not authenticated yet.

By the way, in your custom form login page, you may use the following HttpSession attributes to display the previous user name and redirecting destination after signing in:

Attribute name

Example value

Code example

org.hippoecm.hst.security.servlet.username

admin

session.getAttribute("org.hippoecm.hst.security.servlet.username")

org.hippoecm.hst.security.servlet.destination

/site/preview

session.getAttribute("org.hippoecm.hst.security.servlet.destination")

Also, you can customize the default login error page by setting the following session attribute in your custom form login page:

Attribute name

Example value

Code example

org.hippoecm.hst.security.servlet.loginErrorPage

/examples/custom_form_login_error.jsp

session.setAttribute("org.hippoecm.hst.security.servlet.loginErrorPage",
    "/examples/custom_form_login_error.jsp");

NOTE: The custom form login error page can be set only in the custom form login page!

(2) Configuring the default Form Login pages

The default form login page and form login error page are included in hst-security-2.10.xx.jar as script templates (Since v2.24.00, those templates are in freemarker, while the previous versions are in velocity).

However, you may configure different pages through the following servlet init parameters. When you configure these init parameters manually, you should configure context relative paths which can be dispatched by the servlet context.

Init Parameter Name

Description

Example Value

Default Value

loginFormPagePath

The initial login form page path

/WEB-INF/login/login_form.jsp
or
/WEB-INF/login/login_form.ftl
or
jcr:/hst:hst/.../login_form.ftl

classpath:/org/hippoecm/hst/security/servlet/login_form.ftl    (Since v2.24.00) 
classpath:/org/hippoecm/hst/security/servlet/login_form.vm

loginErrorPage

The login error page path

/WEB-INF/login/login_failure.jsp
or
/WEB-INF/login/login_failure.ftl
or
jcr:/hst:hst/.../login_failure.ftl

classpath:/org/hippoecm/hst/security/servlet/login_failure.ftl  (Since v2.24.00) 
classpath:/org/hippoecm/hst/security/servlet/login_failure.vm

So, it is possible to customize the script templates simply by adding templates in your application classpath (e.g. /WEB-INF/classes) with the same names.

(3) Customizing the default Form Login pages

The default form login page and form login error page are included in hst-security-2.10.xx.jar as script templates (Since v2.24.00, those templates are in freemarker, while the previous versions are in velocity):

The default form login page template:

classpath:/org/hippoecm/hst/security/servlet/login_form.ftl    (Since v2.24.00)
classpath:/org/hippoecm/hst/security/servlet/login_form.vm

The default form login error page template:

classpath:/org/hippoecm/hst/security/servlet/login_failure.ftl  (Since v2.24.00)
classpath:/org/hippoecm/hst/security/servlet/login_failure.vm

So, it is possible to customize the script templates simply by adding templates in your application classpath (e.g. /WEB-INF/classes) with the same names.

(4) Adding redirect to login page.

If you want to have unauthorised requests forward to the login page, add/modify an "error-page" tag in your web.xml:

<error-page>
<error-code>401</error-code>
<location>/WEB-INF/jsp/errorpages/ErrorPage401.jsp</location>
</error-page>

The jsp page should forward to the login page. See the  ErrorPage401.jsp for an example from the archetype

 HST versions older than 2.22.07  must configure a 403 instead of a 401

9. (Optional) How to customize AuthenticationProvider of HST-2 Security Components

Hippo JAAS LoginModule depends on HST-2 Security components, and HST-2 Security Components are designed in a highly modular way.

One of the core components of HST-2 Security Components is the AuthenticationProvider component, which is responsible for authenticating on login credentials and providing security roles for the authenticated user.

By default, the AuthenticationProvider component is defined like the following in a Spring Components assembly configuration:

<bean id="org.hippoecm.hst.security.AuthenticationProvider" class="org.hippoecm.hst.security.impl.HippoAuthenticationProvider">
  <!-- SNIP -->
</bean>

So, if you implement your custom AuthenticationProvider by implementing the following interface and configure it to override the default one, then Hippo JAAS LoginModule will use your custom AuthenticaitonProvider component!

package org.hippoecm.hst.security;

public interface AuthenticationProvider {

    /**
     * Authenticate a user.
     * 
     * @param userName The user name.
     * @param password The user password.
     * @return the {@link User}
     */
    User authenticate(String userName, char [] password) throws SecurityException;
    
    /**
     * Returns security roles of the given username
     * @param user
     * @return
     */
    Set<Role> getRolesByUsername(String username) throws SecurityException;
    
}

For example, you may implement a custom AuthenticationProvider to use a Database, LDAP, or another security application framework such as Spring Security UserDetailsService for instance. Then just configure the bean in an HST-2 Container Components Assembly Overriding XML file (e.g., classpath:/META-INF/hst-assembly/overrides/my-custom-auth-provider.xml) like the following example.

<bean id="org.hippoecm.hst.security.AuthenticationProvider" class="com.example.security.MyCustomAuthenticationProvider">
  <!-- Configure whatever to inject somethings for this bean here... -->
</bean>

With this customizability support for AuthenticationProvider component, you may have chances to use any kinds of users/roles security backend systems.

10. Problems and how-to-fix

(1) NullPointerException (see stack below) while rendering Velocity templates due to disallowed ` velocity.log ' file access.

Because Velocity-1.x tries to create an empty `velocity.log' file under the current working directory such as $CATALINA_HOME when running Tomcat, it can fail to render the embedded Velocity templates when the current working directory is not allowed to create the `velocity.log' file. This problem is described in detail in the HSTTWO-1794 issue.

Because Velocity community plans to fix the problem in v2.0, we have fixed this problem by replacing the existing Velocity templates by freemarker templates since v2.24.00.

In order to avoid this problem, you may choose either of the following:

The stacktrace:

ava.lang.NullPointerException
at org.apache.velocity.runtime.RuntimeInstance.parse(RuntimeInstance.java:1103)
at org.apache.velocity.runtime.RuntimeInstance.parse(RuntimeInstance.java:1086)
at org.apache.velocity.runtime.RuntimeInstance.evaluate(RuntimeInstance.java:1199)
at org.apache.velocity.runtime.RuntimeInstance.evaluate(RuntimeInstance.java:1165)
at org.apache.velocity.app.Velocity.evaluate(Velocity.java:191)
at org.hippoecm.hst.security.servlet.LoginServlet.renderTemplatePage(LoginServlet.java:486)
at org.hippoecm.hst.security.servlet.LoginServlet.renderAutoLoginPage(LoginServlet.java:420)
at org.hippoecm.hst.security.servlet.LoginServlet.doLoginLogin(LoginServlet.java:269)
at org.hippoecm.hst.security.servlet.LoginServlet.doGet(LoginServlet.java:194)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:617)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:646)
at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:436)
at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:374)
at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:302)
at org.apache.catalina.authenticator.FormAuthenticator.forwardToLoginPage(FormAuthenticator.java:321)
at org.apache.catalina.authenticator.FormAuthenticator.authenticate(FormAuthenticator.java:245)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:528)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:857)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
at java.lang.Thread.run(Thread.java:662)