Version 6

    Overview

     

    In order for JBoss AS to secure access to EJB methods it is necessary for the user's identity to be known at the time the method call is made.

     

    A user's identity in the server is represented either by a javax.security.auth.Subject instance or an org.jboss.security.RunAsIdentity instance.

     

    Both these classes store one or more principals that represent the identity and a list of roles that the identity possesses. In the case of the javax.security.auth.Subject a list of credentials is also stored.

     

    In the <assembly-descriptor> section of the ejb-jar.xml deployment descriptor you specify one or more roles that a user must have to access the various ejb methods.

     

    A comparision of these lists reveals whether the user has one of the roles necessary to access the EJB method.

     

    -


     

    Creation of an org.jboss.security.RunAsIdentity

     

    If a element in the ejb-jar.xml file.

      <session>
        ...
        <security-identity>
          <run-as>
            <role-name>Admin</role-name>
          </run-as>
        </security-identity>
        ...
      </session>
    

    then this signifies that a RunAsIdentity needs to be created with the Admin role.

     

    You can specify a principal using the matching <security-identity> element in the jboss.xml file.

      <session>
        ...
        <security-identity>
          <run-as-principal>Mark</run-as-principal>
        </security-identity>
        ...
      </session>
    

    If you don't specify a principal then an anonymous principal is assigned, i.e. calling ctx.getCallerPrincipal() will return 'anonymous'.

     

    At deployment time we parse the <security-identity> element in both the ejb-jar.xml and jboss.xml files and store the <run-as> role name together with the <run-as-principal> in the org.jboss.metadata.SecurityIdentityMetaData class.

     

    Assigning multiple roles to a RunAsIdentity

     

    It is possible to assign more roles to the RunAsIdentity by mapping roles to principals in the jboss.xml deployment descriptor.

      <assembly-descriptor>
        ...
        <security-role>
          <role-name>Support</role-name>
          <principal-name>Mark</principal-name>
          <principal-name>Bruce</principal-name>
          <principal-name>Tom</principal-name>
        </security-role>
        ...
      </assembly-descriptor>
    

    In this case, because we  specified a <run-as-principal> of 'Mark', we add the 'Support' role to our RunAsIdentity along with the 'Admin' role that we started with.

     

    At deployment time we parse the <security-role> element in both the ejb-jar.xml and jboss.xml file and store the role name together with any principals in the org.jboss.metadata.SecurityRoleMetaData class.

     

    Creating the RunAsIdentity

    The RunAsIdentity for an EJB is created within the org.jboss.ejb.plugins.SecurityInterceptor by querying the EJB container for the SecurityIdentityMetaData object.

             BeanMetaData beanMetaData = container.getBeanMetaData();
             ApplicationMetaData applicationMetaData = beanMetaData.getApplicationMetaData();
             AssemblyDescriptorMetaData assemblyDescriptor = applicationMetaData.getAssemblyDescriptor();
             securityRoles = assemblyDescriptor.getSecurityRoles();
    
             SecurityIdentityMetaData secMetaData = beanMetaData.getSecurityIdentityMetaData();
             if (secMetaData != null && secMetaData.getUseCallerIdentity() == false)
             {
                String roleName = secMetaData.getRunAsRoleName();
                String principalName = secMetaData.getRunAsPrincipalName();
    
                // the run-as principal might have extra roles mapped in the assembly-descriptor
                Set extraRoleNames = assemblyDescriptor.getSecurityRoleNamesByPrincipal(principalName);
                runAsIdentity = new RunAsIdentity(roleName, principalName, extraRoleNames);
             }
    

    During the invoke() method on the interceptor after the authenticity and authorization of the user has been verified then the RunAsIdentity is pushed onto a stack.

       public Object invoke(Invocation mi) throws Exception
       {
          // Authenticate the subject and apply any declarative security checks
          checkSecurityAssociation(mi);
    
          /* If a run-as role was specified, push it so that any calls made
           by this bean will have the runAsRole available for declarative
           security checks.
          */
          SecurityActions.pushRunAsIdentity(runAsIdentity);
    
          try
          {
             Object returnValue = getNext().invoke(mi);
             return returnValue;
          }
          finally
          {
             SecurityActions.popRunAsIdentity();
          }
       }
    

    If there was no run-as identity object created then a null value is pushed onto the stack. The peek(int depth) method of the stack in org.jboss.Security.SecurityAssociation$RunAsThreadLocalStack skips over any null entries so this is OK.

          /**
           * Look for the first non-null run-as identity on the stack starting
           * with the value at depth.
           * @return The run-as identity if one exists, null otherwise.
           */
          RunAsIdentity peek(int depth)
          {
             ArrayList stack = (ArrayList) super.get();
             RunAsIdentity runAs = null;
             final int stackSize = stack.size();
             do
             {
                int index = stackSize - 1 - depth;
                if( index >= 0 )
                   runAs = (RunAsIdentity) stack.get(index);
                depth ++;
             }
             while (runAs == null && depth <= stackSize - 1);
             return runAs;
          }
    

    Checking the RunAsIdentity

    Before an EJB method can be called it is necessary to check that the user has the required roles. The org.jboss.ejb.plugins.SecurityInterceptor looks at the RunAsIdentity stack to check for a RunAsIdentity and uses its roles if it finds one.

          // authenticate the current principal
          RunAsIdentity callerRunAsIdentity = SecurityActions.peekRunAsIdentity();
          ...
    
          // Check if the caller is allowed to access the method
          if (methodRoles.contains(AnybodyPrincipal.ANYBODY_PRINCIPAL) == false)
          {
             // The caller is using a the caller identity
             if (callerRunAsIdentity == null)
             {
               ...
             }
    
             // The caller is using a run-as identity
             else
             {
                // Check that the run-as role is in the set of method roles
                if (callerRunAsIdentity.doesUserHaveRole(methodRoles) == false)
                {
                   String method = mi.getMethod().getName();
                   String msg = "Insufficient method permissions, runAsPrincipal=" + callerRunAsIdentity.getName()
                      + ", method=" + method + ", interface=" + iface
                      + ", requiredRoles=" + methodRoles + ", runAsRoles=" + callerRunAsIdentity.getRunAsRoles();
                   SecurityException e = new SecurityException(msg);
                   throw e;
                }
              }
    

     

    The actual checking is done by the org.jboss.security.RunAsIdentity class.

       /**
        * True if the run-as principal has any of the method roles
        */
       public boolean doesUserHaveRole(Set methodRoles)
       {
          Iterator it = methodRoles.iterator();
          while (it.hasNext())
          {
             Principal role = (Principal) it.next();
             if (doesUserHaveRole(role))
                return true;
          }
          return false;
       }