EJB3 Authentication With SPNEGO

Starting with Negotiation 2.0.4 we introduce a new feature: Single Sign On (SSO) for clients invoking EJB3s.


Introduction

When Negotiation was first released it allowed customers to use SSO for web applications using Kerberos Tickets but web applications are not the only resource that need to be secured in EE environment. We wanted to provide a solution to allow customers to protect their EJB3s with a security domain based on Kerberos so that clients could invoke those EJBs using only their Kerberos Tickets as well.


Example

For this exercise I used JBoss Enterprise Application Platform (EAP) 5.1 and a snapshot of JBoss Negotiation 2.1.1.


I. Server Configuration

a) Security Domain Configuration

Negotiation requires two security domains to be configured in the server side. One to authenticate the server on the Key Distribution Center (KDC) and another to validate the client token and eventually assign the user's roles. These security domains are configured in conf/login-config.xml and can have any name. Here is the configuration used in this exercise:

 

<?xml version='1.0'?>
<policy>

 ...

  <application-policy name="host">
    <authentication>
      <login-module code="com.sun.security.auth.module.Krb5LoginModule"
         flag="required">
         <module-option name="storeKey">true</module-option>
         <module-option name="useKeyTab">true</module-option>
         <module-option name="principal">jboss/mmoyses@EXAMPLE.COM</module-option>
         <module-option name="keyTab">/home/mmoyses/krb5keytabs/jboss.keytab</module-option>
         <module-option name="doNotPrompt">true</module-option>
         <module-option name="debug">true</module-option>
      </login-module>
    </authentication>
  </application-policy>
  <application-policy name="SPNEGO">
    <authentication>
      <login-module code="org.jboss.security.negotiation.spnego.SPNEGOLoginModule"
         flag="required">
         <module-option name="password-stacking">useFirstPass</module-option>
         <module-option name="serverSecurityDomain">host</module-option>
      </login-module>
      <login-module code="org.jboss.security.auth.spi.UsersRolesLoginModule"
         flag="required">
         <module-option name="usersProperties">props/spnego-users.properties</module-option>
         <module-option name="rolesProperties">props/spnego-roles.properties</module-option>
         <module-option name="password-stacking">useFirstPass</module-option>
      </login-module>
    </authentication>
  </application-policy>
</policy>


b) EJB3 Connector Configuration

To complete the server side configuration we need to modify the EJB3 connector to use different server and client socket factories. The configuration is done in deploy/ejb3-connectors-jboss-beans.xml. Here is the configuration used in this exercise:

 

<?xml version="1.0" encoding="UTF-8"?>
<!--

  EJB3 Connectors

-->

<deployment xmlns="urn:jboss:bean-deployer:2.0">
    <!--  We don't want the AOPDependencyBuilder  -->
    <annotation>@org.jboss.aop.microcontainer.annotations.DisableAOP</annotation>

  <!--

    JBoss Remoting Connector

    Note: Bean Name "org.jboss.ejb3.RemotingConnector" is used
    as a lookup value; alter only after checking java references
    to this key.

  -->
  <bean name="org.jboss.ejb3.RemotingConnector"
    class="org.jboss.remoting.transport.Connector">

    <property name="invokerLocator">

      <value-factory bean="ServiceBindingManager"
        method="getStringBinding">
        <parameter>
          jboss.remoting:type=Connector,name=DefaultEjb3Connector,handler=ejb3
        </parameter>
        <parameter>
          <null />
        </parameter>
        <parameter>socket://${jboss.bind.address}:${port}?socketFactory=org.jboss.security.negotiation.net.SPNEGOSocketFactory&amp;useAllSocketFactoryParams=true</parameter>
        <parameter>
          <null />
        </parameter>
        <parameter>3873</parameter>
      </value-factory>

    </property>
    <property name="serverConfiguration">
      <inject bean="ServerConfiguration" />
    </property>
    <property name="serverSocketFactory">
      <inject bean="SPNEGOServerSocketFactory" />
    </property>
  </bean>

  <bean name="SPNEGOServerSocketFactory" class="org.jboss.security.negotiation.net.SPNEGOServerSocketFactory">
    <constructor>
      <parameter>SPNEGO</parameter>
      <parameter>host</parameter>
    </constructor>
  </bean>

  <!-- Remoting Server Configuration -->
  <bean name="ServerConfiguration"
    class="org.jboss.remoting.ServerConfiguration">
    <property name="invocationHandlers">
      <map keyClass="java.lang.String" valueClass="java.lang.String">
        <entry>
          <key>AOP</key>
          <value>
            org.jboss.aspects.remoting.AOPRemotingInvocationHandler
          </value>
        </entry>
      </map>
    </property>
  </bean>

</deployment>

So for the invokerLocator we need to add the socketFactory and useAllSocketFactoryParams parameters so that a different socket factory is used on the client side. We also need to inject in the serverSocketFactory property a new factory (SPNEGOServerSocketFactory) where we configure the name of the security domains set up in step a).

II. Client Configuration

The client needs a security domain configured for Kerberos. You can set the name of the security domain to be used by adding the org.jboss.security.negotiation.default.client.security.domain system property in the client side. For example: -Dorg.jboss.security.negotiation.default.client.security.domain=mySecurityDomain. The default value for the security domain name is com.sun.security.jgss.krb5.initiate.

 

com.sun.security.jgss.krb5.initiate {
  com.sun.security.auth.module.Krb5LoginModule required
        doNotPrompt=true
        debug=true
        useTicketCache=true;
};

 

The client also needs to set the server name to connect to (this is the principal name of the JBoss server as set in the "host" security domain. See step I.a). To do this, add the org.jboss.security.negotiation.server.principal system property. For example: -Dorg.jboss.security.negotiation.server.principal=jboss/mmoyses

 

III. EJB3 Example

The EJB3 application used in this exercise is very simple. Here is the interface:

 

/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2010, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package example.ejb3;

import javax.ejb.Remote;

@Remote
public interface Session
{

   public void echo(String echo);
}

and the session bean implementation:

 

/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2010, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package example.ejb3;

import javax.annotation.Resource;
import javax.annotation.security.RolesAllowed;
import javax.ejb.EJBContext;
import javax.ejb.Stateless;

import org.jboss.ejb3.annotation.SecurityDomain;

@Stateless
@SecurityDomain("SPNEGO")
@RolesAllowed("JBossAdmin")
public class SessionBean implements Session
{

   @Resource
   private EJBContext context;

   public void echo(String echo)
   {
      System.out.println(echo);
      System.out.println("Principal.getClass(): " + context.getCallerPrincipal().getClass());
      System.out.println("Principal.getName(): " + context.getCallerPrincipal().getName());
      System.out.println("isCallerInRole('JBossAdmin')? " + context.isCallerInRole("JBossAdmin"));
   }

}

The attached file test.jar contains the compiled EJB3 application and needs to be copied to the deploy/ directory in order to be deployed.


IV. Running the client application

The client application simply invokes the EJB3 twice:

 

/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2010, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package example.test;

import java.util.Properties;

import javax.naming.Context;
import javax.naming.InitialContext;

import example.ejb3.Session;

public class KerberosEJB3Test
{

   public static void main(String[] args) throws Exception
   {
      Properties env = new Properties();
      env.put(Context.PROVIDER_URL, "localhost:1099");
      env.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
      env.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces");
      InitialContext ctx = new InitialContext(env);
      Session session = (Session) ctx.lookup("SessionBean/remote");
      System.out.println("Invoking first time");
      session.echo("testing...");
      ctx.close();

      ctx = new InitialContext(env);
      session = (Session) ctx.lookup("SessionBean/remote");
      System.out.println("Invoking second time");
      session.echo("testing2...");
      ctx.close();
   }
}

To run the application I used this command line:

 

java -Djava.security.auth.login.config=auth.conf -Dsun.security.spnego.debug=true -Dorg.jboss.security.negotiation.default.client.security.domain=com.sun.security.jgss.krb5.initiate -Dorg.jboss.security.negotiation.server.principal=jboss/mmoyses -cp /opt/jboss-eap-5.1/jboss-as/common/lib/jboss-negotiation.jar:/opt/jboss-eap-5.1/jboss-as/client/jbossall-client.jar:. example.test.KerberosEJB3Test

Note: auth.conf in the file containing the client security domain configuration set up in step II.

And here is the output on the server side:

 

...
12:07:27,498 INFO  [STDOUT] testing...
12:07:27,520 INFO  [STDOUT] Principal.getClass(): class javax.security.auth.kerberos.KerberosPrincipal
12:07:27,520 INFO  [STDOUT] Principal.getName(): mmoyses@EXAMPLE.COM
12:07:27,537 INFO  [STDOUT] isCallerInRole('JBossAdmin')? true
...
12:07:28,018 INFO  [STDOUT] testing2...
12:07:28,018 INFO  [STDOUT] Principal.getClass(): class javax.security.auth.kerberos.KerberosPrincipal
12:07:28,018 INFO  [STDOUT] Principal.getName(): mmoyses@EXAMPLE.COM
12:07:28,019 INFO  [STDOUT] isCallerInRole('JBossAdmin')? true

So as we can see I can protect EJB3s with a Kerberos security domain and invoke them with clients that are already authenticated in the same KDC without providing adicional login information such as username or password.