FileDocCategorySizeDatePackage
AuthenticatorBase.javaAPI DocApache Tomcat 6.0.1429175Fri Jul 20 04:20:32 BST 2007org.apache.catalina.authenticator

AuthenticatorBase

public abstract class AuthenticatorBase extends org.apache.catalina.valves.ValveBase implements org.apache.catalina.Lifecycle, org.apache.catalina.Authenticator
Basic implementation of the Valve interface that enforces the <security-constraint> elements in the web application deployment descriptor. This functionality is implemented as a Valve so that it can be ommitted in environments that do not require these features. Individual implementations of each supported authentication method can subclass this base class as required.

USAGE CONSTRAINT: When this class is utilized, the Context to which it is attached (or a parent Container in a hierarchy) must have an associated Realm that can be used for authenticating users and enumerating the roles to which they have been assigned.

USAGE CONSTRAINT: This Valve is only useful when processing HTTP requests. Requests of any other type will simply be passed through.

author
Craig R. McClanahan
version
$Revision: 500626 $ $Date: 2007-01-27 22:25:41 +0100 (sam., 27 janv. 2007) $

Fields Summary
private static org.apache.juli.logging.Log
log
protected static final String
DEFAULT_ALGORITHM
The default message digest algorithm to use if we cannot use the requested one.
protected static final int
SESSION_ID_BYTES
The number of random bytes to include when generating a session identifier.
protected String
algorithm
The message digest algorithm to be used when generating session identifiers. This must be an algorithm supported by the java.security.MessageDigest class on your platform.
protected boolean
cache
Should we cache authenticated Principals if the request is part of an HTTP session?
protected org.apache.catalina.Context
context
The Context to which this Valve is attached.
protected MessageDigest
digest
Return the MessageDigest implementation to be used when creating session identifiers.
protected String
entropy
A String initialization parameter used to increase the entropy of the initialization of our random number generator.
protected static final String
info
Descriptive information about this implementation.
protected boolean
disableProxyCaching
Flag to determine if we disable proxy caching, or leave the issue up to the webapp developer.
protected boolean
securePagesWithPragma
Flag to determine if we disable proxy caching with headers incompatible with IE
protected org.apache.catalina.util.LifecycleSupport
lifecycle
The lifecycle event support for this component.
protected Random
random
A random number generator to use when generating session identifiers.
protected String
randomClass
The Java class name of the random number generator class to be used when generating session identifiers.
protected static final org.apache.catalina.util.StringManager
sm
The string manager for this package.
protected SingleSignOn
sso
The SingleSignOn implementation in our request processing chain, if there is one.
protected boolean
started
Has this component been started?
private static final String
DATE_ONE
"Expires" header always set to Date(1), so generate once only
Constructors Summary
Methods Summary
public voidaddLifecycleListener(org.apache.catalina.LifecycleListener listener)
Add a lifecycle event listener to this component.

param
listener The listener to add


        lifecycle.addLifecycleListener(listener);

    
protected voidassociate(java.lang.String ssoId, org.apache.catalina.Session session)
Associate the specified single sign on identifier with the specified Session.

param
ssoId Single sign on identifier
param
session Session to be associated


        if (sso == null)
            return;
        sso.associate(ssoId, session);

    
protected abstract booleanauthenticate(org.apache.catalina.connector.Request request, org.apache.catalina.connector.Response response, org.apache.catalina.deploy.LoginConfig config)
Authenticate the user making this request, based on the specified login configuration. Return true if any specified constraint has been satisfied, or false if we have created a response challenge already.

param
request Request we are processing
param
response Response we are creating
param
config Login configuration describing how authentication should be performed
exception
IOException if an input/output error occurs

public org.apache.catalina.LifecycleListener[]findLifecycleListeners()
Get the lifecycle listeners associated with this lifecycle. If this Lifecycle has no listeners registered, a zero-length array is returned.


        return lifecycle.findLifecycleListeners();

    
protected synchronized java.lang.StringgenerateSessionId()
Generate and return a new session identifier for the cookie that identifies an SSO principal.


        // Generate a byte array containing a session identifier
        byte bytes[] = new byte[SESSION_ID_BYTES];
        getRandom().nextBytes(bytes);
        bytes = getDigest().digest(bytes);

        // Render the result as a String of hexadecimal digits
        StringBuffer result = new StringBuffer();
        for (int i = 0; i < bytes.length; i++) {
            byte b1 = (byte) ((bytes[i] & 0xf0) >> 4);
            byte b2 = (byte) (bytes[i] & 0x0f);
            if (b1 < 10)
                result.append((char) ('0" + b1));
            else
                result.append((char) ('A" + (b1 - 10)));
            if (b2 < 10)
                result.append((char) ('0" + b2));
            else
                result.append((char) ('A" + (b2 - 10)));
        }
        return (result.toString());

    
public java.lang.StringgetAlgorithm()
Return the message digest algorithm for this Manager.



    // ------------------------------------------------------------- Properties


                 
       

        return (this.algorithm);

    
public booleangetCache()
Return the cache authenticated Principals flag.


        return (this.cache);

    
public org.apache.catalina.ContainergetContainer()
Return the Container to which this Valve is attached.


        return (this.context);

    
protected synchronized java.security.MessageDigestgetDigest()
Return the MessageDigest object to be used for calculating session identifiers. If none has been created yet, initialize one the first time this method is called.


        if (this.digest == null) {
            try {
                this.digest = MessageDigest.getInstance(algorithm);
            } catch (NoSuchAlgorithmException e) {
                try {
                    this.digest = MessageDigest.getInstance(DEFAULT_ALGORITHM);
                } catch (NoSuchAlgorithmException f) {
                    this.digest = null;
                }
            }
        }

        return (this.digest);

    
public booleangetDisableProxyCaching()
Return the flag that states if we add headers to disable caching by proxies.

        return disableProxyCaching;
    
public java.lang.StringgetEntropy()
Return the entropy increaser value, or compute a semi-useful value if this String has not yet been set.


        // Calculate a semi-useful value if this has not been set
        if (this.entropy == null)
            setEntropy(this.toString());

        return (this.entropy);

    
public java.lang.StringgetInfo()
Return descriptive information about this Valve implementation.


        return (info);

    
protected synchronized java.util.RandomgetRandom()
Return the random number generator instance we should use for generating session identifiers. If there is no such generator currently defined, construct and seed a new one.


        if (this.random == null) {
            try {
                Class clazz = Class.forName(randomClass);
                this.random = (Random) clazz.newInstance();
                long seed = System.currentTimeMillis();
                char entropy[] = getEntropy().toCharArray();
                for (int i = 0; i < entropy.length; i++) {
                    long update = ((byte) entropy[i]) << ((i % 8) * 8);
                    seed ^= update;
                }
                this.random.setSeed(seed);
            } catch (Exception e) {
                this.random = new java.util.Random();
            }
        }

        return (this.random);

    
public java.lang.StringgetRandomClass()
Return the random number generator class name.


        return (this.randomClass);

    
public booleangetSecurePagesWithPragma()
Return the flag that states, if proxy caching is disabled, what headers we add to disable the caching.

        return securePagesWithPragma;
    
public voidinvoke(org.apache.catalina.connector.Request request, org.apache.catalina.connector.Response response)
Enforce the security restrictions in the web application deployment descriptor of our associated Context.

param
request Request to be processed
param
response Response to be processed
exception
IOException if an input/output error occurs
exception
ServletException if thrown by a processing element


        if (log.isDebugEnabled())
            log.debug("Security checking request " +
                request.getMethod() + " " + request.getRequestURI());
        LoginConfig config = this.context.getLoginConfig();

        // Have we got a cached authenticated Principal to record?
        if (cache) {
            Principal principal = request.getUserPrincipal();
            if (principal == null) {
                Session session = request.getSessionInternal(false);
                if (session != null) {
                    principal = session.getPrincipal();
                    if (principal != null) {
                        if (log.isDebugEnabled())
                            log.debug("We have cached auth type " +
                                session.getAuthType() +
                                " for principal " +
                                session.getPrincipal());
                        request.setAuthType(session.getAuthType());
                        request.setUserPrincipal(principal);
                    }
                }
            }
        }

        // Special handling for form-based logins to deal with the case
        // where the login form (and therefore the "j_security_check" URI
        // to which it submits) might be outside the secured area
        String contextPath = this.context.getPath();
        String requestURI = request.getDecodedRequestURI();
        if (requestURI.startsWith(contextPath) &&
            requestURI.endsWith(Constants.FORM_ACTION)) {
            if (!authenticate(request, response, config)) {
                if (log.isDebugEnabled())
                    log.debug(" Failed authenticate() test ??" + requestURI );
                return;
            }
        }

        Realm realm = this.context.getRealm();
        // Is this request URI subject to a security constraint?
        SecurityConstraint [] constraints
            = realm.findSecurityConstraints(request, this.context);
       
        if ((constraints == null) /* &&
            (!Constants.FORM_METHOD.equals(config.getAuthMethod())) */ ) {
            if (log.isDebugEnabled())
                log.debug(" Not subject to any constraint");
            getNext().invoke(request, response);
            return;
        }

        // Make sure that constrained resources are not cached by web proxies
        // or browsers as caching can provide a security hole
        if (disableProxyCaching && 
            // FIXME: Disabled for Mozilla FORM support over SSL 
            // (improper caching issue)
            //!request.isSecure() &&
            !"POST".equalsIgnoreCase(request.getMethod())) {
            if (securePagesWithPragma) {
                // FIXME: These cause problems with downloading office docs
                // from IE under SSL and may not be needed for newer Mozilla
                // clients.
                response.setHeader("Pragma", "No-cache");
                response.setHeader("Cache-Control", "no-cache");
            } else {
                response.setHeader("Cache-Control", "private");
            }
            response.setHeader("Expires", DATE_ONE);
        }

        int i;
        // Enforce any user data constraint for this security constraint
        if (log.isDebugEnabled()) {
            log.debug(" Calling hasUserDataPermission()");
        }
        if (!realm.hasUserDataPermission(request, response,
                                         constraints)) {
            if (log.isDebugEnabled()) {
                log.debug(" Failed hasUserDataPermission() test");
            }
            /*
             * ASSERT: Authenticator already set the appropriate
             * HTTP status code, so we do not have to do anything special
             */
            return;
        }

        // Since authenticate modifies the response on failure,
        // we have to check for allow-from-all first.
        boolean authRequired = true;
        for(i=0; i < constraints.length && authRequired; i++) {
            if(!constraints[i].getAuthConstraint()) {
                authRequired = false;
            } else if(!constraints[i].getAllRoles()) {
                String [] roles = constraints[i].findAuthRoles();
                if(roles == null || roles.length == 0) {
                    authRequired = false;
                }
            }
        }
             
        if(authRequired) {  
            if (log.isDebugEnabled()) {
                log.debug(" Calling authenticate()");
            }
            if (!authenticate(request, response, config)) {
                if (log.isDebugEnabled()) {
                    log.debug(" Failed authenticate() test");
                }
                /*
                 * ASSERT: Authenticator already set the appropriate
                 * HTTP status code, so we do not have to do anything
                 * special
                 */
                return;
            } 
        }
    
        if (log.isDebugEnabled()) {
            log.debug(" Calling accessControl()");
        }
        if (!realm.hasResourcePermission(request, response,
                                         constraints,
                                         this.context)) {
            if (log.isDebugEnabled()) {
                log.debug(" Failed accessControl() test");
            }
            /*
             * ASSERT: AccessControl method has already set the
             * appropriate HTTP status code, so we do not have to do
             * anything special
             */
            return;
        }
    
        // Any and all specified constraints have been satisfied
        if (log.isDebugEnabled()) {
            log.debug(" Successfully passed all security constraints");
        }
        getNext().invoke(request, response);

    
protected booleanreauthenticateFromSSO(java.lang.String ssoId, org.apache.catalina.connector.Request request)
Attempts reauthentication to the Realm using the credentials included in argument entry.

param
ssoId identifier of SingleSignOn session with which the caller is associated
param
request the request that needs to be authenticated


        if (sso == null || ssoId == null)
            return false;

        boolean reauthenticated = false;

        Container parent = getContainer();
        if (parent != null) {
            Realm realm = parent.getRealm();
            if (realm != null) {
                reauthenticated = sso.reauthenticate(ssoId, realm, request);
            }
        }

        if (reauthenticated) {
            associate(ssoId, request.getSessionInternal(true));

            if (log.isDebugEnabled()) {
                log.debug(" Reauthenticated cached principal '" +
                          request.getUserPrincipal().getName() +
                          "' with auth type '" +  request.getAuthType() + "'");
            }
        }

        return reauthenticated;
    
protected voidregister(org.apache.catalina.connector.Request request, org.apache.catalina.connector.Response response, java.security.Principal principal, java.lang.String authType, java.lang.String username, java.lang.String password)
Register an authenticated Principal and authentication type in our request, in the current session (if there is one), and with our SingleSignOn valve, if there is one. Set the appropriate cookie to be returned.

param
request The servlet request we are processing
param
response The servlet response we are generating
param
principal The authenticated Principal to be registered
param
authType The authentication type to be registered
param
username Username used to authenticate (if any)
param
password Password used to authenticate (if any)


        if (log.isDebugEnabled())
            log.debug("Authenticated '" + principal.getName() + "' with type '"
                + authType + "'");

        // Cache the authentication information in our request
        request.setAuthType(authType);
        request.setUserPrincipal(principal);

        Session session = request.getSessionInternal(false);
        // Cache the authentication information in our session, if any
        if (cache) {
            if (session != null) {
                session.setAuthType(authType);
                session.setPrincipal(principal);
                if (username != null)
                    session.setNote(Constants.SESS_USERNAME_NOTE, username);
                else
                    session.removeNote(Constants.SESS_USERNAME_NOTE);
                if (password != null)
                    session.setNote(Constants.SESS_PASSWORD_NOTE, password);
                else
                    session.removeNote(Constants.SESS_PASSWORD_NOTE);
            }
        }

        // Construct a cookie to be returned to the client
        if (sso == null)
            return;

        // Only create a new SSO entry if the SSO did not already set a note
        // for an existing entry (as it would do with subsequent requests
        // for DIGEST and SSL authenticated contexts)
        String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
        if (ssoId == null) {
            // Construct a cookie to be returned to the client
            ssoId = generateSessionId();
            Cookie cookie = new Cookie(Constants.SINGLE_SIGN_ON_COOKIE, ssoId);
            cookie.setMaxAge(-1);
            cookie.setPath("/");
            
            // Bugzilla 41217
            cookie.setSecure(request.isSecure());

            // Bugzilla 34724
            String ssoDomain = sso.getCookieDomain();
            if(ssoDomain != null) {
                cookie.setDomain(ssoDomain);
            }

            response.addCookie(cookie);

            // Register this principal with our SSO valve
            sso.register(ssoId, principal, authType, username, password);
            request.setNote(Constants.REQ_SSOID_NOTE, ssoId);

        } else {
            // Update the SSO session with the latest authentication data
            sso.update(ssoId, principal, authType, username, password);
        }

        // Fix for Bug 10040
        // Always associate a session with a new SSO reqistration.
        // SSO entries are only removed from the SSO registry map when
        // associated sessions are destroyed; if a new SSO entry is created
        // above for this request and the user never revisits the context, the
        // SSO entry will never be cleared if we don't associate the session
        if (session == null)
            session = request.getSessionInternal(true);
        sso.associate(ssoId, session);

    
public voidremoveLifecycleListener(org.apache.catalina.LifecycleListener listener)
Remove a lifecycle event listener from this component.

param
listener The listener to remove


        lifecycle.removeLifecycleListener(listener);

    
public voidsetAlgorithm(java.lang.String algorithm)
Set the message digest algorithm for this Manager.

param
algorithm The new message digest algorithm


        this.algorithm = algorithm;

    
public voidsetCache(boolean cache)
Set the cache authenticated Principals flag.

param
cache The new cache flag


        this.cache = cache;

    
public voidsetContainer(org.apache.catalina.Container container)
Set the Container to which this Valve is attached.

param
container The container to which we are attached


        if (!(container instanceof Context))
            throw new IllegalArgumentException
                (sm.getString("authenticator.notContext"));

        super.setContainer(container);
        this.context = (Context) container;

    
public voidsetDisableProxyCaching(boolean nocache)
Set the value of the flag that states if we add headers to disable caching by proxies.

param
nocache true if we add headers to disable proxy caching, false if we leave the headers alone.

        disableProxyCaching = nocache;
    
public voidsetEntropy(java.lang.String entropy)
Set the entropy increaser value.

param
entropy The new entropy increaser value


        this.entropy = entropy;

    
public voidsetRandomClass(java.lang.String randomClass)
Set the random number generator class name.

param
randomClass The new random number generator class name


        this.randomClass = randomClass;

    
public voidsetSecurePagesWithPragma(boolean securePagesWithPragma)
Set the value of the flag that states what headers we add to disable proxy caching.

param
securePagesWithPragma true if we add headers which are incompatible with downloading office documents in IE under SSL but which fix a caching problem in Mozilla.

        this.securePagesWithPragma = securePagesWithPragma;
    
public voidstart()
Prepare for the beginning of active use of the public methods of this component. This method should be called after configure(), and before any of the public methods of the component are utilized.

exception
LifecycleException if this component detects a fatal error that prevents this component from being used


        // Validate and update our current component state
        if (started)
            throw new LifecycleException
                (sm.getString("authenticator.alreadyStarted"));
        lifecycle.fireLifecycleEvent(START_EVENT, null);
        started = true;

        // Look up the SingleSignOn implementation in our request processing
        // path, if there is one
        Container parent = context.getParent();
        while ((sso == null) && (parent != null)) {
            if (!(parent instanceof Pipeline)) {
                parent = parent.getParent();
                continue;
            }
            Valve valves[] = ((Pipeline) parent).getValves();
            for (int i = 0; i < valves.length; i++) {
                if (valves[i] instanceof SingleSignOn) {
                    sso = (SingleSignOn) valves[i];
                    break;
                }
            }
            if (sso == null)
                parent = parent.getParent();
        }
        if (log.isDebugEnabled()) {
            if (sso != null)
                log.debug("Found SingleSignOn Valve at " + sso);
            else
                log.debug("No SingleSignOn Valve is present");
        }

    
public voidstop()
Gracefully terminate the active use of the public methods of this component. This method should be the last one called on a given instance of this component.

exception
LifecycleException if this component detects a fatal error that needs to be reported


        // Validate and update our current component state
        if (!started)
            throw new LifecycleException
                (sm.getString("authenticator.notStarted"));
        lifecycle.fireLifecycleEvent(STOP_EVENT, null);
        started = false;

        sso = null;