Pages

Tuesday, December 22, 2009

EJB Session Bean Security in Weblogic

With Weblogic it is relative easy to protect your deployed EJB's by adding roles. These roles are mapped to WLS groups or users. In this blog I will show you, how you can protect for example the EJB Session beans.
I will start with securing an EJB3 Session Bean and later on in this article I will do the same only then in 2.1 way ( ejb-jar ).

First we create the Remote interface ( JDeveloper can create the EJB Session bean and remote or local interface for you )

package nl.whitehorses.ejb.security.services;

import java.util.List;
import javax.ejb.Remote;

import nl.whitehorses.ejb.security.entities.Departments;

@Remote
public interface HrModelSessionEJB {
Departments mergeDepartments(Departments departments);

List<Departments> getDepartmentsFindAll();
}

Create the EJB Session Bean. First we can optional add the @Resource annotation ( SessionContext ctx ) so we can retrieve the principal ( ctx.getCallerPrincipal) or check if the principal has the right role ( ctx.isCallerInRole )
To protect the Session bean methods we can add the @RolesAllowed or @PermitAll annotation ( @RolesAllowed({"adminsEJB"}) )

package nl.whitehorses.ejb.security.services;

import java.security.Principal;

import java.util.List;

import javax.annotation.Resource;

import javax.annotation.security.RolesAllowed;

import javax.ejb.Remote;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import nl.whitehorses.ejb.security.entities.Departments;


@Stateless(name = "HrModelSessionEJB", mappedName = "EJB_Security-HrModel-HrModelSessionEJB")
@Remote
public class HrModelSessionEJBBean implements HrModelSessionEJB {
@PersistenceContext(unitName="HrModel")
private EntityManager em;

public HrModelSessionEJBBean() {
}

@Resource SessionContext ctx;

@RolesAllowed({"adminsEJB"})
public Departments mergeDepartments(Departments departments) {
return em.merge(departments);
}

@RolesAllowed({"adminsEJB","usersEJB"})
public List<Departments> getDepartmentsFindAll() {
Principal cp = ctx.getCallerPrincipal();
System.out.println("getname:" + cp.getName());
if ( ctx.isCallerInRole("adminsEJB")) {
System.out.println("user has admins role");
}
return em.createNamedQuery("Departments.findAll").getResultList();
}
}

The last thing we need to do is to add the weblogic ejb deployment descriptor ( weblogic-ejb-jar ) , This maps the EJB roles to users or groups in Weblogic Security Realm ( myrealm ). Do manually or use JDeveloper 11g R1 PS1, this version has a nice visual editor for the weblogic descriptors files.
The role-name must match with the roles names used in RolesAllowed annotation and the principal-name must match with a user or role in Weblogic Security Realm.

<?xml version = '1.0' encoding = 'windows-1252'?>
<weblogic-ejb-jar xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.bea.com/ns/weblogic/weblogic-ejb-jar http://www.bea.com/ns/weblogic/weblogic-ejb-jar/1.0/weblogic-ejb-jar.xsd"
xmlns="http://www.bea.com/ns/weblogic/weblogic-ejb-jar">
<weblogic-enterprise-bean>
<ejb-name>HrModelSessionEJB</ejb-name>
<stateless-session-descriptor/>
<enable-call-by-reference>true</enable-call-by-reference>
</weblogic-enterprise-bean>
<security-role-assignment>
<role-name>usersEJB</role-name>
<principal-name>scott</principal-name>
</security-role-assignment>
<security-role-assignment>
<role-name>adminsEJB</role-name>
<principal-name>edwin</principal-name>
</security-role-assignment>
</weblogic-ejb-jar>

Deploy your Session Bean to the Weblogicserver. In the Weblogic console we can check the permissions by going to the EJB deployment.
Check the EJB roles
Which WLS users or groups are connected to the EJB role


check the permissions of the Session methods.

Now we only have to make a test client and change the java.naming.security.principal property.

package nl.whitehorses.ejb.security.test;

import java.util.List;

import java.util.Properties;

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

import nl.whitehorses.ejb.security.entities.Departments;
import nl.whitehorses.ejb.security.services.HrModelSessionEJB;

public class HrClient {
public HrClient() {
try {
Properties parm = new Properties();
parm.setProperty("java.naming.factory.initial","weblogic.jndi.WLInitialContextFactory");
parm.setProperty("java.naming.provider.url","t3://localhost:7101");
parm.setProperty("java.naming.security.principal","edwin");
parm.setProperty("java.naming.security.credentials","weblogic1");
final Context context = new InitialContext(parm);

HrModelSessionEJB hr = (HrModelSessionEJB)context.lookup("EJB_Security-HrModel-HrModelSessionEJB#nl.whitehorses.ejb.security.services.HrModelSessionEJB");

for (Departments departments : (List<Departments>)hr.getDepartmentsFindAll()) {
System.out.println( "departmentId = " + departments.getDepartmentId() );
System.out.println( "departmentName = " + departments.getDepartmentName() );
System.out.println( "locationId = " + departments.getLocationId() );
}
} catch (Exception ex) {
ex.printStackTrace();
}
}

public static void main(String[] args) {
HrClient hrClient = new HrClient();
}
}


The EJB 2.1 way is a bit different, now we need the use the ebj-jar.xml and this requires an remote home interface. So first we need to create this.

package nl.whitehorses.ejb.security.services;

import java.rmi.RemoteException;

import javax.ejb.CreateException;
import javax.ejb.EJBHome;

public interface HrModelSessionEJBHome extends EJBHome {
HrModelSessionEJB create() throws RemoteException, CreateException;
}

The remote interface is also different then its 3.0 version.

package nl.whitehorses.ejb.security.services;

import java.rmi.RemoteException;
import java.util.List;
import javax.ejb.EJBObject;

import nl.whitehorses.ejb.security.entities.Departments;

public interface HrModelSessionEJB extends EJBObject {
Departments mergeDepartments(Departments departments) throws RemoteException;

List<Departments> getDepartmentsFindAll() throws RemoteException;
}

The EJB Session Bean

package nl.whitehorses.ejb.security.services;

import java.security.Principal;

import java.util.List;
import javax.annotation.Resource;

import javax.ejb.EJBHome;
import javax.ejb.EJBObject;
import javax.ejb.Handle;
import javax.ejb.Init;
import javax.ejb.Remote;
import javax.ejb.RemoteHome;
import javax.ejb.Remove;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;


import nl.whitehorses.ejb.security.entities.Departments;

@Stateless(name = "HrModelSessionEJB", mappedName = "EJB_Security-HrModel-HrModelSessionEJB")
@Remote({HrModelSessionEJB.class})
@RemoteHome(HrModelSessionEJBHome.class)
public class HrModelSessionEJBBean implements HrModelSessionEJB {
@PersistenceContext(unitName="HrModel")
private EntityManager em;

public HrModelSessionEJBBean() {
}

@Resource SessionContext ctx;


public Departments mergeDepartments(Departments departments) {
return em.merge(departments);
}

/** <code>select o from Departments o</code> */
public List<Departments> getDepartmentsFindAll() {
Principal cp = ctx.getCallerPrincipal();
System.out.println("getname:" + cp.getName());
if ( ctx.isCallerInRole("adminsEJB")) {
System.out.println("user has admins role");
}
return em.createNamedQuery("Departments.findAll").getResultList();
}

@Init
public void create(){};

@Remove
public void remove(){};


public EJBHome getEJBHome() {
return null;
}

public Object getPrimaryKey() {
return null;
}

public Handle getHandle() {
return null;
}

public boolean isIdentical(EJBObject ejbObject) {
return false;
}
}

We can use the same weblogic-ejb-jar.xml, now we only need to create ebj-jar.xml descriptor. This descriptor does the same as the RolesAllowed annotation

<?xml version = '1.0' encoding = 'windows-1252'?>
<ejb-jar xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd"
version="3.0" xmlns="http://java.sun.com/xml/ns/javaee">
<enterprise-beans>
<session>
<ejb-name>HrModelSessionEJB</ejb-name>
<home>nl.whitehorses.ejb.security.services.HrModelSessionEJBHome</home>
<remote>nl.whitehorses.ejb.security.services.HrModelSessionEJB</remote>
<ejb-class>nl.whitehorses.ejb.security.services.HrModelSessionEJBBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
<security-role-ref>
<role-name>usersEJB</role-name>
</security-role-ref>
<security-role-ref>
<role-name>adminsEJB</role-name>
</security-role-ref>
</session>
</enterprise-beans>
<assembly-descriptor>
<security-role>
<role-name>usersEJB</role-name>
</security-role>
<security-role>
<role-name>adminsEJB</role-name>
</security-role>
<method-permission>
<role-name>usersEJB</role-name>
<role-name>adminsEJB</role-name>
<method>
<ejb-name>HrModelSessionEJB</ejb-name>
<method-name>getDepartmentsFindAll</method-name>
</method>
</method-permission>
<method-permission>
<role-name>adminsEJB</role-name>
<method>
<ejb-name>HrModelSessionEJB</ejb-name>
<method-name>mergeDepartments</method-name>
</method>
</method-permission>
</assembly-descriptor>
</ejb-jar>

And the test client.

package nl.whitehorses.ejb.security.test;

import java.util.List;

import java.util.Properties;

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

import nl.whitehorses.ejb.security.entities.Departments;
import nl.whitehorses.ejb.security.services.HrModelSessionEJB;
import nl.whitehorses.ejb.security.services.HrModelSessionEJBHome;

public class HrClient {
public HrClient() {
try {
Properties parm = new Properties();
parm.setProperty("java.naming.factory.initial","weblogic.jndi.WLInitialContextFactory");
parm.setProperty("java.naming.provider.url","t3://localhost:7101");
parm.setProperty("java.naming.security.principal","edwin");
parm.setProperty("java.naming.security.credentials","weblogic1");
final Context context = new InitialContext(parm);

HrModelSessionEJBHome hRSessionEJB = (HrModelSessionEJBHome)context.lookup("EJB_Security-HrModel-HrModelSessionEJB#nl.whitehorses.ejb.security.services.HrModelSessionEJBHome");
HrModelSessionEJB hr = hRSessionEJB.create();

for (Departments departments : (List<Departments>)hr.getDepartmentsFindAll()) {
System.out.println( "departmentId = " + departments.getDepartmentId() );
System.out.println( "departmentName = " + departments.getDepartmentName() );
System.out.println( "locationId = " + departments.getLocationId() );
}


} catch (Exception ex) {
ex.printStackTrace();
}
}

public static void main(String[] args) {
HrClient hrClient = new HrClient();
}

}

Here is the example workspace

8 comments:

  1. Hi Edwin,
    I've some test cases with EJB 3 with WLS policies could you please help me to resolve, thanks in advance

    Cases are:-
    1. EJB WS Remote Invocation with Message Protection and Identity Propagation
    Outline: User authenticates with web client using OAM that invokes BPELwhich further invokes EJB WS with Message Protection and Identity Propagation
    Setup web client app (ADF app) to be protected using OAM. After successful authentication, the user context is set in Java Subject of the client app.
    The client app accesses username from Java Subject, and sends SOAP request to BPEL with SAML token for the logged in user.
    Attach wss10_saml_token_service_policy to BPEL WS.
    Attach wss10_saml_token_with_message_protection_client_policy to BPEL's reference point that calls the EJB WS.
    Attach WLS native policy to the EJB WS. The request message to the service will have SAML token with full body signed and encrypted, and the response message needs to be fully signed and encrypted.
    Test it by invoking the SOA client application from a test client passing in username/password which in turn calls the EJB WS
    Authenticate against LDAP, then switch authentication provider from LDAP to OAM.
    ------------------

    2. EJB WS Remote Invocation with Message Protection
    Outline: BPEL invokes EJB WS with Message Protection
    Username token with sign/encrypt
    Attach WLS native policy to the EJB WS service. The request message to the service will have username token with full body signed and encrypted, and the response message needs to be fully signed and encrypted.
    Create a SOA app that invokes the EJB WS service
    Attach wss10_username_token_with_message_protection_client_policy to its reference point.
    Test it by invoking the Shipping service from the SOA client application Authenticate against LDAP, then switch authentication provider from LDAP to OAM.

    ReplyDelete
  2. Any comments on above my comments Edwin

    ReplyDelete
  3. Hi,

    this is too much for a comment, you should do P.O.C. with a security expert consultant.

    thanks

    ReplyDelete
  4. Hi Edwin,

    Using JDeveloper 11.1.1.4.0, I have created an EJB Session Bean for my root application module (No Enabling Client Data Binding, Interface: Remote, Session Type: Statless, Transaction: Container Managed).
    In my generated Session Bean, I have a method like this:
    public int test(Number testId) throws ServiceException {
    int _ret;
    try {
    acquireResources();
    _ret = getTestAM().test(testId);
    commit();
    return _ret;
    } catch (JboException ex) {
    ex.printStackTrace();
    throw new ServiceException(ex);
    } finally {
    try {
    releaseResources();
    } catch (JboException ex) {
    }
    }
    }
    When I call this method from an EJB client, I have NPE because the method getTestAM() returns null.
    Do you have any idea how can I avoid this? More details about this problem is at the following link: https://forums.oracle.com/forums/message.jspa?messageID=10382043#10382043

    If I manage to overcome this problem, the main purpose, is to secure the Stateless EJB Bean deployed on a WebLogic server.
    I need to do this when this EJB is called from an ADF application that is protected by SSO.
    So what I'm trying to do is to make an identity propagation using EJB technology from JDeveloper 11.1.1.4.0. Is this possible (and without implementing
    the ADF security)?
    If this is not possible, then I will protect the EJB by providing a user and password for the Web Logic (Like in your Client example).

    Thank you and best regards,
    Alex

    ReplyDelete
    Replies
    1. Hi,

      Skip adf BC , batch mode for adf BC is not great and will be desupported. With ejb use jpa , it is not that hard and leave transaction and datasource to the ejb,jpa

      And you can generate a datacontrol on the interface where you can set principal and credentials .

      Thanks

      Delete
  5. Hi Edwin,

    I am using Jdeveloper 11g R2, i tried annotating the session bean(EJB3) method with "@RolesAllowed". Added the weblogic ejb deployment descriptor as explained. And deployed the session bean. I can see the User with the roles but i am not able to see annotated Session methods.
    Please let me know if some more configuration is required to add permissions.

    Thanks in advance.

    ReplyDelete
    Replies
    1. Hi,

      does it work , maybe they changed the weblogic console.

      thanks Edwin

      Delete
  6. what value are u passing to java.naming.security.credentials?? where is this configured??

    ReplyDelete