| JBossSecurityMgrRealm.java |
/*
* JBoss, the OpenSource EJB server
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.web.tomcat.security;
import java.security.Principal;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Set;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.security.auth.Subject;
import EDU.oswego.cs.dl.util.concurrent.ConcurrentReaderHashMap;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.Realm;
import org.apache.catalina.realm.RealmBase;
import org.jboss.logging.Logger;
import org.jboss.security.CertificatePrincipal;
import org.jboss.security.RealmMapping;
import org.jboss.security.SimplePrincipal;
import org.jboss.security.SubjectSecurityManager;
import org.jboss.security.auth.certs.SubjectDNMapping;
/**
* An implementation of the catelinz Realm and Valve interfaces. The Realm
* implementation handles authentication and authorization using the JBossSX
* security framework. It relieas on the JNDI ENC namespace setup by the
* AbstractWebContainer. In particular, it uses the java:comp/env/security
* subcontext to access the security manager interfaces for authorization and
* authenticaton. <p/> The Valve interface is used to associated the
* authenticated user with the SecurityAssociation class when a request begins
* so that web components may call EJBs and have the principal propagated. The
* security association is removed when the request completes.
* @author Scott.Stark@jboss.org
* @version $Revision: 1.10.2.8 $
* @see org.jboss.security.AuthenticationManager
* @see org.jboss.security.CertificatePrincipal
* @see org.jboss.security.RealmMapping
* @see org.jboss.security.SimplePrincipal
* @see org.jboss.security.SecurityAssociation
* @see org.jboss.security.SubjectSecurityManager
*/
public class JBossSecurityMgrRealm extends RealmBase implements Realm
{
static Logger log = Logger.getLogger(JBossSecurityMgrRealm.class);
/**
* The converter from X509 cert chain to Princpal
*/
private CertificatePrincipal certMapping = new SubjectDNMapping();
/**
* ConcurrentReaderHashMap<UserPrincipal, GenericPrincipal> used to handle the
* HttpServletRequest.isUserInRole when the request principal has been
* overwritten by the CustomPrincipalValve
*/
private ConcurrentReaderHashMap roleMap = new ConcurrentReaderHashMap();
/**
* The JBossSecurityMgrRealm category trace flag
*/
private boolean trace;
/**
* Set the class name of the CertificatePrincipal used for mapping X509 cert
* chains to a Princpal.
* @param className the CertificatePrincipal implementation class that must
* have a no-arg ctor.
* @see org.jboss.security.CertificatePrincipal
*/
public void setCertificatePrincipal(String className)
{
try
{
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class cpClass = loader.loadClass(className);
certMapping = (CertificatePrincipal) cpClass.newInstance();
}
catch (Exception e)
{
log.error("Failed to load CertificatePrincipal: " + className, e);
certMapping = new SubjectDNMapping();
}
}
private Context getSecurityContext()
{
Context securityCtx = null;
// Get the JBoss security manager from the ENC context
try
{
InitialContext iniCtx = new InitialContext();
securityCtx = (Context) iniCtx.lookup("java:comp/env/security");
}
catch (NamingException e)
{
// Apparently there is no security context?
}
return securityCtx;
}
/**
* Override to allow a single realm to be shared as a realm and valve
*/
public void start() throws LifecycleException
{
if (super.started == true)
return;
super.start();
trace = log.isTraceEnabled();
}
/**
* Override to allow a single realm to be shared as a realm and valve
*/
public void stop() throws LifecycleException
{
if (super.started == false)
return;
super.stop();
}
/**
* Return the Principal associated with the specified chain of X509 client
* certificates. If there is none, return <code>null</code>.
* @param certs Array of client certificates, with the first one in the array
* being the certificate of the client itself.
*/
public Principal authenticate(X509Certificate[] certs)
{
Principal principal = null;
Context securityCtx = getSecurityContext();
if (securityCtx == null)
{
if (trace)
log.trace("No security context for authenticate(X509Certificate[])");
return null;
}
try
{
// Get the JBoss security manager from the ENC context
SubjectSecurityManager securityMgr = (SubjectSecurityManager) securityCtx.lookup("securityMgr");
Subject subject = new Subject();
principal = certMapping.toPrinicipal(certs);
if (securityMgr.isValid(principal, certs, subject))
{
if (trace)
log.trace("User: " + principal + " is authenticated");
SecurityAssociationActions.setPrincipalInfo(principal, certs, subject);
// Get the CallerPrincipal mapping
RealmMapping realmMapping = (RealmMapping) securityCtx.lookup("realmMapping");
Principal oldPrincipal = principal;
principal = realmMapping.getPrincipal(oldPrincipal);
if (trace)
{
log.trace("Mapped from input principal: " + oldPrincipal
+ "to: " + principal);
}
// Get the caching principal
principal = getCachingPrincpal(realmMapping, oldPrincipal,
principal, certs, subject);
}
else
{
if (trace)
log.trace("User: " + principal + " is NOT authenticated");
principal = null;
}
}
catch (NamingException e)
{
log.error("Error during authenticate", e);
}
return principal;
}
/**
* Return the Principal associated with the specified username, which matches
* the digest calculated using the given parameters using the method
* described in RFC 2069; otherwise return <code>null</code>.
* @param username Username of the Principal to look up
* @param digest Digest which has been submitted by the client
* @param nonce Unique (or supposedly unique) token which has been used for
* this request
* @param realm Realm name
* @param md5a2 Second MD5 digest used to calculate the digest : MD5(Method +
* ":" + uri)
*/
public Principal authenticate(String username, String digest, String nonce,
String nc, String cnonce, String qop, String realm, String md5a2)
{
return super.authenticate(username, digest, nonce,
nc, cnonce, qop, realm, md5a2);
}
/**
* Return the Principal associated with the specified username and
* credentials, if there is one; otherwise return <code>null</code>.
* @param username Username of the Principal to look up
* @param credentials Password or other credentials to use in authenticating
* this username
*/
public Principal authenticate(String username, String credentials)
{
if (trace)
log.trace("Begin authenticate, username=" + username);
Principal principal = null;
Context securityCtx = getSecurityContext();
if (securityCtx == null)
{
if (trace)
log.trace("No security context for authenticate(String, String)");
return null;
}
Principal caller = (Principal) SecurityAssociationValve.userPrincipal.get();
if (caller == null && username == null && credentials == null)
return null;
try
{
// Get the JBoss security manager from the ENC context
SubjectSecurityManager securityMgr = (SubjectSecurityManager) securityCtx.lookup("securityMgr");
principal = new SimplePrincipal(username);
Subject subject = new Subject();
if (securityMgr.isValid(principal, credentials, subject))
{
log.trace("User: " + username + " is authenticated");
SecurityAssociationActions.setPrincipalInfo(principal, credentials, subject);
// Get the CallerPrincipal mapping
RealmMapping realmMapping = (RealmMapping) securityCtx.lookup("realmMapping");
Principal oldPrincipal = principal;
principal = realmMapping.getPrincipal(oldPrincipal);
if (trace)
{
log.trace("Mapped from input principal: " + oldPrincipal
+ "to: " + principal);
}
// Get the caching principal
principal = getCachingPrincpal(realmMapping, oldPrincipal,
principal, credentials, subject);
}
else
{
principal = null;
if (trace)
log.trace("User: " + username + " is NOT authenticated");
}
}
catch (NamingException e)
{
principal = null;
log.error("Error during authenticate", e);
}
if (trace)
log.trace("End authenticate, principal=" + principal);
return principal;
}
/**
* Returns <code>true</code> if the specified user <code>Principal</code> has
* the specified security role, within the context of this
* <code>Realm</code>; otherwise return <code>false</code>. This will be true
* when an associated role <code>Principal</code> can be found whose
* <code>getName</code> method returns a <code>String</code> equalling the
* specified role.
* @param principal <code>Principal</code> for whom the role is to be
* checked
* @param role Security role to be checked
*/
public boolean hasRole(Principal principal, String role)
{
if ((principal == null) || (role == null))
{
return false;
}
if (principal instanceof JBossGenericPrincipal)
{
return super.hasRole(principal, role);
}
JBossGenericPrincipal gp = (JBossGenericPrincipal) roleMap.get(principal);
Set userRoles = gp.getUserRoles();
if( userRoles != null )
{
Iterator iter = userRoles.iterator();
while( iter.hasNext() )
{
Principal p = (Principal) iter.next();
if (role.equals(p.getName()))
{
return true;
}
}
}
return false;
}
/**
* Return the Principal associated with the specified username and
* credentials, if there is one; otherwise return <code>null</code>.
* @param username Username of the Principal to look up
* @param credentials Password or other credentials to use in authenticating
* this username
*/
public Principal authenticate(String username, byte[] credentials)
{
return authenticate(username, new String(credentials));
}
/**
* Return a short name for this Realm implementation, for use in log
* messages.
*/
protected String getName()
{
return getClass().getName();
}
/**
* Return the password associated with the given principal's user name.
*/
protected String getPassword(String username)
{
String password = null;
return password;
}
/**
* Return the Principal associated with the given user name.
*/
protected Principal getPrincipal(String username)
{
return new SimplePrincipal(username);
}
/**
* Access the set of role Princpals associated with the given caller princpal.
* @param principal - the Principal mapped from the authentication principal
* and visible from the HttpServletRequest.getUserPrincipal
* @return a possible null Set<Principal> for the caller roles
*/
protected Set getPrincipalRoles(Principal principal)
{
JBossGenericPrincipal gp = (JBossGenericPrincipal) roleMap.get(principal);
Set userRoles = gp.getUserRoles();
return userRoles;
}
/**
* Create the session principal tomcat will cache to avoid callouts to this
* Realm.
* @param realmMapping - the role mapping security manager
* @param authPrincipal - the principal used for authentication and stored in
* the security manager cache
* @param callerPrincipal - the possibly different caller principal
* representation of the authenticated principal
* @param credential - the credential used for authentication
* @return the tomcat session principal wrapper
*/
protected Principal getCachingPrincpal(RealmMapping realmMapping,
Principal authPrincipal, Principal callerPrincipal, Object credential,
Subject subject)
{
// Cache the user roles in the principal
Set userRoles = realmMapping.getUserRoles(authPrincipal);
ArrayList roles = new ArrayList();
if (userRoles != null)
{
Iterator iterator = userRoles.iterator();
while (iterator.hasNext())
{
Principal role = (Principal) iterator.next();
roles.add(role.getName());
}
}
JBossGenericPrincipal gp = new JBossGenericPrincipal(this, subject,
authPrincipal, callerPrincipal, credential, roles, userRoles);
// Cache the roles under the caller principal for isUserInRole calls
roleMap.put(callerPrincipal, gp);
return gp;
}
}
| JBossSecurityMgrRealm.java |