FileDocCategorySizeDatePackage
DigestAuthenticator.javaAPI DocApache Tomcat 6.0.1415490Fri Jul 20 04:20:34 BST 2007org.apache.catalina.authenticator

DigestAuthenticator

public class DigestAuthenticator extends AuthenticatorBase
An Authenticator and Valve implementation of HTTP DIGEST Authentication (see RFC 2069).
author
Craig R. McClanahan
author
Remy Maucherat
version
$Revision: 467222 $ $Date: 2006-10-24 05:17:11 +0200 (mar., 24 oct. 2006) $

Fields Summary
private static org.apache.juli.logging.Log
log
protected static final org.apache.catalina.util.MD5Encoder
md5Encoder
The MD5 helper object for this class.
protected static final String
info
Descriptive information about this implementation.
protected static MessageDigest
md5Helper
MD5 message digest provider.
protected String
key
Private key.
Constructors Summary
public DigestAuthenticator()



    // ----------------------------------------------------------- Constructors


      
        super();
        try {
            if (md5Helper == null)
                md5Helper = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            throw new IllegalStateException();
        }
    
Methods Summary
public 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


        // Have we already authenticated someone?
        Principal principal = request.getUserPrincipal();
        //String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
        if (principal != null) {
            if (log.isDebugEnabled())
                log.debug("Already authenticated '" + principal.getName() + "'");
            // Associate the session with any existing SSO session in order
            // to get coordinated session invalidation at logout
            String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
            if (ssoId != null)
                associate(ssoId, request.getSessionInternal(true));
            return (true);
        }

        // NOTE: We don't try to reauthenticate using any existing SSO session,
        // because that will only work if the original authentication was
        // BASIC or FORM, which are less secure than the DIGEST auth-type
        // specified for this webapp
        //
        // Uncomment below to allow previous FORM or BASIC authentications
        // to authenticate users for this webapp
        // TODO make this a configurable attribute (in SingleSignOn??)
        /*
        // Is there an SSO session against which we can try to reauthenticate?
        if (ssoId != null) {
            if (log.isDebugEnabled())
                log.debug("SSO Id " + ssoId + " set; attempting " +
                          "reauthentication");
            // Try to reauthenticate using data cached by SSO.  If this fails,
            // either the original SSO logon was of DIGEST or SSL (which
            // we can't reauthenticate ourselves because there is no
            // cached username and password), or the realm denied
            // the user's reauthentication for some reason.
            // In either case we have to prompt the user for a logon
            if (reauthenticateFromSSO(ssoId, request))
                return true;
        }
        */

        // Validate any credentials already included with this request
        String authorization = request.getHeader("authorization");
        if (authorization != null) {
            principal = findPrincipal(request, authorization, context.getRealm());
            if (principal != null) {
                String username = parseUsername(authorization);
                register(request, response, principal,
                         Constants.DIGEST_METHOD,
                         username, null);
                return (true);
            }
        }

        // Send an "unauthorized" response and an appropriate challenge

        // Next, generate a nOnce token (that is a token which is supposed
        // to be unique).
        String nOnce = generateNOnce(request);

        setAuthenticateHeader(request, response, config, nOnce);
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
        //      hres.flushBuffer();
        return (false);

    
protected static java.security.PrincipalfindPrincipal(org.apache.catalina.connector.Request request, java.lang.String authorization, org.apache.catalina.Realm realm)
Parse the specified authorization credentials, and return the associated Principal that these credentials authenticate (if any) from the specified Realm. If there is no such Principal, return null.

param
request HTTP servlet request
param
authorization Authorization credentials from this request
param
realm Realm used to authenticate Principals


        //System.out.println("Authorization token : " + authorization);
        // Validate the authorization credentials format
        if (authorization == null)
            return (null);
        if (!authorization.startsWith("Digest "))
            return (null);
        authorization = authorization.substring(7).trim();

        // Bugzilla 37132: http://issues.apache.org/bugzilla/show_bug.cgi?id=37132
        String[] tokens = authorization.split(",(?=(?:[^\"]*\"[^\"]*\")+$)");

        String userName = null;
        String realmName = null;
        String nOnce = null;
        String nc = null;
        String cnonce = null;
        String qop = null;
        String uri = null;
        String response = null;
        String method = request.getMethod();

        for (int i = 0; i < tokens.length; i++) {
            String currentToken = tokens[i];
            if (currentToken.length() == 0)
                continue;

            int equalSign = currentToken.indexOf('=");
            if (equalSign < 0)
                return null;
            String currentTokenName =
                currentToken.substring(0, equalSign).trim();
            String currentTokenValue =
                currentToken.substring(equalSign + 1).trim();
            if ("username".equals(currentTokenName))
                userName = removeQuotes(currentTokenValue);
            if ("realm".equals(currentTokenName))
                realmName = removeQuotes(currentTokenValue, true);
            if ("nonce".equals(currentTokenName))
                nOnce = removeQuotes(currentTokenValue);
            if ("nc".equals(currentTokenName))
                nc = removeQuotes(currentTokenValue);
            if ("cnonce".equals(currentTokenName))
                cnonce = removeQuotes(currentTokenValue);
            if ("qop".equals(currentTokenName))
                qop = removeQuotes(currentTokenValue);
            if ("uri".equals(currentTokenName))
                uri = removeQuotes(currentTokenValue);
            if ("response".equals(currentTokenName))
                response = removeQuotes(currentTokenValue);
        }

        if ( (userName == null) || (realmName == null) || (nOnce == null)
             || (uri == null) || (response == null) )
            return null;

        // Second MD5 digest used to calculate the digest :
        // MD5(Method + ":" + uri)
        String a2 = method + ":" + uri;
        //System.out.println("A2:" + a2);

        byte[] buffer = null;
        synchronized (md5Helper) {
            buffer = md5Helper.digest(a2.getBytes());
        }
        String md5a2 = md5Encoder.encode(buffer);

        return (realm.authenticate(userName, response, nOnce, nc, cnonce, qop,
                                   realmName, md5a2));

    
protected java.lang.StringgenerateNOnce(org.apache.catalina.connector.Request request)
Generate a unique token. The token is generated according to the following pattern. NOnceToken = Base64 ( MD5 ( client-IP ":" time-stamp ":" private-key ) ).

param
request HTTP Servlet request


        long currentTime = System.currentTimeMillis();

        String nOnceValue = request.getRemoteAddr() + ":" +
            currentTime + ":" + key;

        byte[] buffer = null;
        synchronized (md5Helper) {
            buffer = md5Helper.digest(nOnceValue.getBytes());
        }
        nOnceValue = md5Encoder.encode(buffer);

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



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


                
       

        return (info);

    
protected java.lang.StringparseUsername(java.lang.String authorization)
Parse the username from the specified authorization string. If none can be identified, return null

param
authorization Authorization string to be parsed


        //System.out.println("Authorization token : " + authorization);
        // Validate the authorization credentials format
        if (authorization == null)
            return (null);
        if (!authorization.startsWith("Digest "))
            return (null);
        authorization = authorization.substring(7).trim();

        StringTokenizer commaTokenizer =
            new StringTokenizer(authorization, ",");

        while (commaTokenizer.hasMoreTokens()) {
            String currentToken = commaTokenizer.nextToken();
            int equalSign = currentToken.indexOf('=");
            if (equalSign < 0)
                return null;
            String currentTokenName =
                currentToken.substring(0, equalSign).trim();
            String currentTokenValue =
                currentToken.substring(equalSign + 1).trim();
            if ("username".equals(currentTokenName))
                return (removeQuotes(currentTokenValue));
        }

        return (null);

    
protected static java.lang.StringremoveQuotes(java.lang.String quotedString, boolean quotesRequired)
Removes the quotes on a string. RFC2617 states quotes are optional for all parameters except realm.

        //support both quoted and non-quoted
        if (quotedString.length() > 0 && quotedString.charAt(0) != '"" &&
                !quotesRequired) {
            return quotedString;
        } else if (quotedString.length() > 2) {
            return quotedString.substring(1, quotedString.length() - 1);
        } else {
            return new String();
        }
    
protected static java.lang.StringremoveQuotes(java.lang.String quotedString)
Removes the quotes on a string.

        return removeQuotes(quotedString, false);
    
protected voidsetAuthenticateHeader(org.apache.catalina.connector.Request request, org.apache.catalina.connector.Response response, org.apache.catalina.deploy.LoginConfig config, java.lang.String nOnce)
Generates the WWW-Authenticate header.

The header MUST follow this template :

WWW-Authenticate = "WWW-Authenticate" ":" "Digest"
digest-challenge

digest-challenge = 1#( realm | [ domain ] | nOnce |
[ digest-opaque ] |[ stale ] | [ algorithm ] )

realm = "realm" "=" realm-value
realm-value = quoted-string
domain = "domain" "=" <"> 1#URI <">
nonce = "nonce" "=" nonce-value
nonce-value = quoted-string
opaque = "opaque" "=" quoted-string
stale = "stale" "=" ( "true" | "false" )
algorithm = "algorithm" "=" ( "MD5" | token )

param
request HTTP Servlet request
param
response HTTP Servlet response
param
config Login configuration describing how authentication should be performed
param
nOnce nonce token


        // Get the realm name
        String realmName = config.getRealmName();
        if (realmName == null)
            realmName = request.getServerName() + ":"
                + request.getServerPort();

        byte[] buffer = null;
        synchronized (md5Helper) {
            buffer = md5Helper.digest(nOnce.getBytes());
        }

        String authenticateHeader = "Digest realm=\"" + realmName + "\", "
            +  "qop=\"auth\", nonce=\"" + nOnce + "\", " + "opaque=\""
            + md5Encoder.encode(buffer) + "\"";
        response.setHeader("WWW-Authenticate", authenticateHeader);