Wednesday, 10 October 2012

Custom Glassfish Security Realm

Following on from the last post, our client required an user management console that delegated authentication to the container to leverage the SSO capabilities that were available.

Glassfish allows creating a JDBC realm that authenticates users and retrieves their roles from a database. 
The above screenshot shows how a new JDBC realm can be created from the Glassfish administrative console. This JDBC realm however specifies that there needs to be an User table with username and password columns, and a Group table with group name column with a foreign key to the user table as shown below:

T_USERS
USERNAME (PK)
PASSWORD
T_GROUPS
USERNAME (FK)
GROUPNAME
This is fine if the tables do not exist, but what if the database already exists or you require a mapping table for the many-to-many relationship between users and groups? For example, as our application was using Spring Security we already had a database schema as follows:
T_USERS
USERNAME (PK)
PASSWORD
....
T_USER_AUTHORITIES
USERNAME (FK)
ROLE_ID (FK)
T_ROLES
ROLE_ID (PK)
ROLENAME
In such a scenario, the Glassfish provided JDBCRealm is insufficient and a custom realm needs to be created. The custom jdbc realm will allow Glassfish to authenticate using the same database as the application is using to manage the users.

Creating a custom realm requires providing a custom JAAS login module class and custom realm class.
  • The custom login module must extend com.sun.appserv.security.AppservPasswordLoginModule.This class implements the javax.security.auth.spi.LoginModule.
  • The custom realm class must extend the abstract com.sun.appserv.security.AppservRealm class. You may find it easier to view the source code of the com.sun.enterprise.security.auth.realm.jdbc.JDBCRealm class to provide a base from which to develop the custom realm class. Unfortunately, the JDBCRealm class is final and so cannot be extended. The realm class must also contain the HK2 @Service annotation to allow integration with the Glassfish OSGI module system.
  • Update the <GF_HOME>/domains/<DOMAIN-NAME>/config/login.conf to add an entry for the custom realm referring to the custom LoginModule.
  • Finally there should be a file named javax.security.auth.spi.LoginModule in the META-INF/services directory with the fully qualified name of the LoginModule class.
Once these classes have been written they need to be packaged as an OSGI bundle to be activated by Glassfish. For this purpose, we used the maven-bundle-plugin
 <dependencies>

<dependency>
            <groupId>org.glassfish.security</groupId>
            <artifactId>security</artifactId>
            <version>3.1.1-b11</version>
        </dependency>            
       
</dependencies>
<build>
       <plugins>
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <extensions>true</extensions>
                <configuration>
                    <instructions>
                        <Export-Package>                            ${project.groupId}.glassfish.auth;version=${project.version}
                        </Export-Package>
                        <Import-Package>
                            java.util,
                            javax.security.auth,
                            javax.security.auth.login,
                            javax.sql,                                                   
                            org.glassfish.security.common,
                            org.jvnet.hk2.annotations,
                            org.jvnet.hk2.component,
                            com.sun.appserv.connectors.internal.api,
                            com.sun.appserv.security,
                            com.sun.enterprise.security.common,
                            com.sun.enterprise.util,
                            com.sun.enterprise.util.i18n,                           
                            com.sun.enterprise.security.auth.realm,
                            com.sun.enterprise.security.auth.realm.jdbc,
                            com.sun.enterprise.security.auth.login.common,
                            com.sun.enterprise.security.auth.digest.api,
                            com.sun.enterprise.universal                           
                        </Import-Package>
                    </instructions>
                </configuration>
              </plugin>
</plugins>   
</build>
Note the export-package element should contain the name of the packages of the custom LoginModule class and custom AppservRealm classes.
Once the OSGI bundle has been created it can be loaded by copying it to <GF_HOME>/domains/<DOMAIN-NAME>/autodeploy/bundles.

The realm can now be created from the Glassfish administrative console. When an application that utilizes this realm is accessed the LoginModule will be initialized.




Friday, 5 October 2012

Container Based Authentication with Spring Security

There are occasions where applications may be required to delegate authentication to a central identity access manager or Single Sign On (SSO) provider. This post describes how an application can use Spring Security for authorization but also delegate authentication to the application server.

These scenarios are referred to as "pre-authenticated" as the user has been reliably authenticated prior to accessing the application. Spring Security provides a number of classes to support pre-authentication such as the PreAuthenticatedAuthenticationProvider class. This post demonstrates how to convert a web application based on Spring Security authentication to one that uses Container based authentication.


    <sec:authentication-manager alias="authenticationManager">
        <sec:authentication-provider ref='preAuthenticatedAuthenticationProvider'/>
    </sec:authentication-manager>

    <bean id="preAuthenticatedAuthenticationProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
        <property name="preAuthenticatedUserDetailsService" ref="preAuthenticatedUserDetailsService"/>
    </bean>

    <bean id="preAuthenticatedUserDetailsService"
            class="org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesUserDetailsService"/>



As Spring Security will no longer be authenticating the user, we need to modify our login page to target the standard j_security_check URI instead of the Spring j_spring_security_check.
We also need to specify the security constraints within the web.xml such as form-based login and the security role names.

Note that although we will be using Spring Security for specifying our access control rules, we still need to specify some access control within the web.xml so that the JEE container knows when to request authentication from the user.


    <filter>
        <!-- <filter-name>springSecurityFilterChain</filter-name> -->
        <filter-name>filterChainProxy</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>

    <filter-mapping>
        <!-- <filter-name>springSecurityFilterChain</filter-name> -->
        <filter-name>filterChainProxy</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

       <!-- for container based authentication -->       
    <login-config>
        <auth-method>FORM</auth-method>
        <realm-name>file</realm-name>
       <form-login-config>
              <form-login-page>/login</form-login-page>
              <form-error-page>/login</form-error-page>
       </form-login-config>       
    </login-config>

    <security-role>
        <role-name>ROLE_CONSUMER_SUPPORT</role-name>
    </security-role>
    <security-role>
        <role-name>ROLE_CONSUMER_SUPPORT_READ_ONLY</role-name>
    </security-role>
    
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>All areas</web-resource-name>
            <url-pattern>/users/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>ROLE_CONSUMER_SUPPORT_READ_ONLY</role-name>
            <role-name>ROLE_CONSUMER_SUPPORT</role-name>
        </auth-constraint>
    </security-constraint>
 

We have also replaced the springSecurityFilterChain bean with the filterChainProxy bean.This allows specifying (via the Spring context file) which Spring bean filters will be involved in the security process.

Note
See the source code of Spring Security Config's HttpSecurityBeanDefinitionParser#registerFilterChainProxyIfNecessary method to understand how springSecurityFilterChain is used as the default bean id for the FilterChainProxy when using the http security namespace.

So which filters are required to configure the FilterChainProxy for this container based pre-authenticated scenario?
<bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">

    <sec:filter-chain-map path-type="ant">
        <sec:filter-chain pattern="/**" filters="sif,j2eePreAuthFilter,logoutFilter,etf,fsi"/>
    </sec:filter-chain-map>
</bean>
The above shows five filters that have been specified in the Spring Security pre-auth sample. Lets see what each one is responsible for.


<bean id="sif" class="org.springframework.security.web.context.SecurityContextPersistenceFilter"/>

<bean id="j2eePreAuthFilter" class="org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter">
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="authenticationDetailsSource">
            <bean class="org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource">
                <property name="mappableRolesRetriever">
                    <bean class="org.springframework.security.web.authentication.preauth.j2ee.WebXmlMappableAttributesRetriever" />
                </property>
                <property name="userRoles2GrantedAuthoritiesMapper">
                    <bean class="org.springframework.security.core.authority.mapping.SimpleAttributes2GrantedAuthoritiesMapper">
                        <property name="convertAttributeToUpperCase" value="true"/>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>
The SecurityContextPersistenceFilter is responsible for populating the SecurityContextHolder and is required to be the first filter to execute.
The J2eePreAuthFilter is what retrieves the JEE user principle name and associated roles for the pre-authenticated principle. As the container has performed the authentication, the user principle is available with associated roles and these are mapped to Spring's GrantedAuthority instances.

<bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">

    <constructor-arg value="/"/>
    <constructor-arg>
        <list>
            <bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>
        </list>
    </constructor-arg>
</bean>
The LogoutFilter will invoke the configured list of LogoutHandler implementations and then direct the user to the url specified in the first argument. It is also possible to provide a LogoutSuccessHandler to implement any custom logic after a successful logout. The LogoutHandler's are responsible for implementing the actual logout behaviour such as invalidating the session.

<bean id="preAuthenticatedProcessingFilterEntryPoint"            class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint"/>


<bean id="etf" class="org.springframework.security.web.access.ExceptionTranslationFilter">
    <property name="authenticationEntryPoint" ref="preAuthenticatedProcessingFilterEntryPoint"/>
</bean>
The ExceptionTranslationFilter is responsible for handling any AccessDeniedException and AuthenticationException's thrown within the filter chain and mapping them to HTTP responses. It will delegate to the authenticationEntryPoint on an AuthenticationException. In the pre-authenticated scenario, the authenticationEntryPoint will simply return the HTTP error code 403 rather than commence the re-authentication process.

<bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter"/>


<bean id="httpRequestAccessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
    <property name="allowIfAllAbstainDecisions" value="false"/>
    <property name="decisionVoters">
        <list>
            <ref bean="roleVoter"/>
        </list>
    </property>
</bean>

<bean id="fsi" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
    <property name="authenticationManager" ref="authenticationManager"/>
    <property name="accessDecisionManager" ref="httpRequestAccessDecisionManager"/>
    <property name="securityMetadataSource">
        <sec:filter-invocation-definition-source>
            <sec:intercept-url pattern="/secure/extreme/**" access="ROLE_SUPERVISOR"/>
            <sec:intercept-url pattern="/secure/**" access="ROLE_USER"/>
            <sec:intercept-url pattern="/**" access="ROLE_USER"/>
        </sec:filter-invocation-definition-source>
    </property>
</bean>
The final filter in the chain is the FilterSecurityInterceptor that implements the access control rules for HTTP resources similar to the ones defined in the HTTP security namespace. The FilterSecurityInterceptor will first check if authentication is required, and then ask the accessDecisionManager to decide whether to grant access to a request resource. The AccessDecisionManager collaborates with the configured list of AccessDecisionVoters to resolve the authorization request.

These filters and some changes to the web.xml and login, logout forms enable transitioning a web application based on Spring Security authentication to one that uses container based authentication.

Finally, depending on the application server used, there will be some configuration required for mapping the JEE security roles.
<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE glassfish-web-app PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN" "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd">
<glassfish-web-app>

       <context-root>preauth</context-root>
       <security-role-mapping>
              <role-name>ROLE_USER</role-name>
              <group-name>users</group-name>
       </security-role-mapping>
       <security-role-mapping>
              <role-name>ROLE_SUPERVISOR</role-name>
              <group-name>managers</group-name>
       </security-role-mapping>  
</glassfish-web-app>

glassfish-web.xml