Hi guys,
I had a little time to think more about a security implementation,
and I wanted to run some thoughts by you all for comments. My
goal is to get a simple but extensible implementation up and
running.
First, I no longer think that Subject should be the class we
propogate, for several reasons: 1. it doesn't serialize its
credentials; 2. it would be complicated to handle multiple
principals, e.g. in getCallerPrincipal; and 3. it seems redundant;
the administrator can map that principal to additional principals if
needed (e.g. for authentication to connected resource factories). I
would be interested in hearing thoughts about this. For now, I
would add a credential member variable (of type Object) to
MethodInvocation, JRMPContainerInvoker, etc. This credential
might be a password, a digital signature, or whatever. It might also
be a wrapper of that credential to indicate that the jboss server
trusts the invoker about the principal and no authentication is
necessary (e.g. for in-VM invocations, or invocations from a web
container).
There would be two interfaces that the application server used
during an invocation--one to check a credential (authentication) and
one to protect a resource (authorization).
The authentication interface would be like this:
public interface org.jboss.system.SecurityManager
{
public boolean isValid( Principal principal, Object credential );
}
This interface would be on the system classpath (like
javax.transaction.TransactionManager for transactions). [I propose
to add an org.jboss.system package for interfaces that should be
on the system classpath. Right now I'm just dumping this package
into run.jar. Is there some other class loader that I should be
using, that is a parent to all the class loaders that might need to
use these interfaces?] Implementations would be provided as
MBeans, and could make judgements about validity based on the
Principal class type, Principal name, and credential. Typically, one
implementation would exist per security realm.
The implementations would register with a system-level
implementation of the SecurityManager registered in the JNDI
namespace as "SecurityManager." This system level
implementation would just delegate to the realm-level
implementations to see if any would allow that the
Principal/credential pair were valid.
The security manager would be set in the container and available to
the security plugin as follows (in the container factory):
// Set security manager
container.setSecurityManager((SecurityManager)new
InitialContext().lookup("SecurityManager"));
The SecurityInterceptor's first task would be to use the
SecurityManager to authenticate the Principal, based on the
credential available in MethodInvocation.
Next, the interface for authorization would be as follows:
public interface RealmMapping
{
public boolean doesUserHaveRole( Principal principal, Set
roleNames );
}
A RealmMapping maintains a relation between a list of principals,
and a set of roles assigned to each principal. Unlike
SecurityManagers, RealmMappings are specific to a particular
J2EE application. So the relationship is J2EE app has many
realms, a realm has many principals, a principal has many roles.
The RealmMapping interface would be used in conjunction with the
authorization information in the EJB 1.1 or 2.0 deployment
descriptor. It would also be used for the implementation of
"isCallerInRole"; the Set of roleNames would have only one role in
that case.
A CacheRealmMapping is a "meta-level" implementation of
RealmMapping, that handles lists of realms for a particular J2EE
application. I named it "CacheRealmMapping" because eventually
I would like to cache information about a particular principal if
access to the persistent mapping is expensive. Right now its
implementation would be pretty simple:
public class CacheRealmMapping implements RealmMapping
{
private LinkedList realms = new LinkedList();
public void addRealmMapping( RealmMapping realmMapping )
{
realms.add( realmMapping );
}
public boolean doesUserHaveRole( Principal principal, Set
roleNames )
{
Iterator iter=realms.iterator();
while( iter.hasNext() )
{
RealmMapping realmMapping = (RealmMapping)
iter.next();
if (realmMapping.doesUserHaveRole( principal,
roleNames ))
return true;
}
return false;
}
}
The actual implementations of mappings would be MBeans whose
names would be specified in the jboss container configuration.
This would allow mappings to use various persistence
mechanisms: an XML file saved in the EJB jar (like JAWS O/R
mappings), or a database, and would allow the mappings to be
edited at runtime based on any appropriate mechanism--a third
party application, JMX, etc. It would also allow mappings to be
reused when appropriate, simply by specifying the same MBean in
the container configuration. This will help the potentially common
case where EJBs roles and permissions are configured according
to consistent company-wide standards. It's possible that only a
single mapping MBean instance would be necessary for many
uses. (An ASP would obviously need to register many mapping
MBeans. These would probably point to various database tables.)
Notice that I didn't mention JAAS in this. It would be used for
authentication in realm-level MBean implementations of the
interface org.jboss.system.SecurityManager. A single MBean
might provide for authentication for multiple realms, with little
programming, simply by using JAAS. (JAAS could also be used
on the client side.)
I'd appreciate comments that anyone might have.
-Dan