com.mnt.base.web.DigestAuthenticator.java Source code

Java tutorial

Introduction

Here is the source code for com.mnt.base.web.DigestAuthenticator.java

Source

/**
 * $Revision: 1.0
 * $Date: 2013-5-21
 *
 * Copyright (C) 2013-2020 MNT. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */

package com.mnt.base.web;

import java.io.IOException;
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.mnt.base.util.BaseConfiguration;
import com.mnt.base.util.BeanContext;
import com.mnt.base.util.CommonUtil;

/**
 * Provide the method to verify the http digest authentication
 *  
 * refer the doc: http://en.wikipedia.org/wiki/Digest_access_authentication
 * 
 * 
 * HA1 = MD5(A1) = MD5(username:realm:password) 
 * 
 * If the qop directive's value is "auth" or is unspecified, then HA2 is
 * HA2 = MD5(A2) = MD5(method:digestURI)
 * If the qop directive's value is "auth-int", then HA2 is
 * HA2 = MD5(A2) = MD5(method:digestURI:MD5(entityBody))
 * If the qop directive's value is "auth" or "auth-int", then compute the response as follows:
 * response = MD5(HA1:nonce:nonceCount:clientNonce:qop:HA2)
 * If the qop directive is unspecified, then compute the response as follows:
 * response = MD5(HA1:nonce:HA2)
 * 
 * @author Peng Peng
 * #date 2012-10-19
 *
 *
 */
public class DigestAuthenticator {

    private static final Log log = LogFactory.getLog(DigestAuthenticator.class);

    public static String AUTHENTICATED_FLAG_KEY = "__authPassed";
    private static String AUTH_INFO_MAP = "__authInfoMap";

    private static AuthenticatorProvider authProvider;

    private static ThreadLocal<String> authUser = new ThreadLocal<String>();

    public static boolean authenticate(HttpServletRequest req, HttpServletResponse resp) {

        boolean result = false;

        HttpSession session = req.getSession();

        if (session != null) {

            result = session.getAttribute(AUTHENTICATED_FLAG_KEY) != null;

            if (!result) {

                session.setMaxInactiveInterval(60);

                Map<String, Object> authInfoMap = CommonUtil.uncheckedMapCast(session.getAttribute(AUTH_INFO_MAP));

                if (authInfoMap == null) {
                    authInfoMap = new HashMap<String, Object>();
                    session.setAttribute(AUTH_INFO_MAP, authInfoMap);
                }

                String authentication = req.getHeader("Authorization");

                if (CommonUtil.isEmpty(authentication) || !authentication.startsWith("Digest ")) {

                    postAuthRequired(req, resp, authInfoMap);

                } else {
                    result = authenticate(req.getMethod(), authentication, authInfoMap);

                    if (result) {

                        if (authProvider != null) {
                            try {
                                authProvider.authenticated(authUser.get(), true);
                            } catch (Exception e) {
                                log.error("error while invoke the authProvider.authenticated: " + authUser.get(),
                                        e);
                            }
                        }
                        session.setAttribute(AUTHENTICATED_FLAG_KEY, true);
                        session.removeAttribute(AUTH_INFO_MAP);
                        authInfoMap.clear();
                        authInfoMap = null;

                        session.setMaxInactiveInterval(1800);
                    } else {
                        authProvider.authenticated(authUser.get(), false);
                        authInfoMap.clear();
                        postAuthRequired(req, resp, authInfoMap);
                    }
                }
            }
        } else {
            System.err.println("Just support session available authentication.");
        }

        return result;
    }

    /**
     * WWW-Authenticate: Digest realm="testrealm@host.com",
      *                   qop="auth,auth-int",
      *                   nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
      *                   opaque="5ccc069c403ebaf9f0171e9517f40e41"
     * @param req 
     *
     * @param resp
     * @param authInfoMap
     */
    private static void postAuthRequired(HttpServletRequest req, HttpServletResponse resp,
            Map<String, Object> authInfoMap) {

        resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

        StringBuilder sb = new StringBuilder();
        String siteHost;
        siteHost = req.getHeader("Host");
        if (siteHost == null) {
            siteHost = "www.mntplay.com";
        }

        sb.append("Digest realm=\"" + siteHost + "\",algorithm=\"md5\",");
        sb.append("qop=\"auth,auth-int\",");

        String nonce = (String) authInfoMap.get("nonce");

        if (nonce == null) {
            nonce = UUID.randomUUID().toString();
        }

        String opaque = UUID.randomUUID().toString();

        sb.append("nonce=\"" + nonce + "\",");
        sb.append("opaque=\"" + opaque + "\"");

        authInfoMap.put("nonce", nonce);
        resp.setHeader("WWW-Authenticate", sb.toString());

        try {
            resp.flushBuffer();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Authorization: Digest username="Mufasa",
      *                realm="testrealm@host.com",
      *                nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
      *                uri="/dir/index.html",
      *                qop=auth,
      *                nc=00000001,
      *                cnonce="0a4f113b",
      *                response="6629fae49393a05397450978507c4ef1",
      *                opaque="5ccc069c403ebaf9f0171e9517f40e41"
     *
     * HA1 = MD5(A1) = MD5(username:realm:password) 
     * 
     * If the qop directive's value is "auth" or is unspecified, then HA2 is
     * HA2 = MD5(A2) = MD5(method:digestURI)
     * If the qop directive's value is "auth-int", then HA2 is
     * HA2 = MD5(A2) = MD5(method:digestURI:MD5(entityBody))
     * 
     * 
     * If the qop directive's value is "auth" or "auth-int", then compute the response as follows:
     * response = MD5(HA1:nonce:nonceCount:clientNonce:qop:HA2)
     * If the qop directive is unspecified, then compute the response as follows:
     * response = MD5(HA1:nonce:HA2)
     * 
     * @param authentication
     * @param authInfoMap
     * @return
     */
    private static boolean authenticate(String method, String authentication, Map<String, Object> authInfoMap) {

        boolean result = false;

        String authStr = authentication.substring(7);//remove "Digest " 

        Map<String, String> keyVal = new HashMap<String, String>();

        StringBuilder key = new StringBuilder();
        StringBuilder val = new StringBuilder();

        boolean keyFlag = true;
        boolean quotFlag = false;

        char[] chs = authStr.toCharArray();
        for (char ch : chs) {

            if ('=' == ch && !quotFlag) {
                keyFlag = false;
            } else if (',' == ch && !quotFlag) {
                keyFlag = true;
                keyVal.put(key.toString().trim(), val.toString().trim());

                key.setLength(0);
                val.setLength(0);

            } else if ('"' == ch) {
                quotFlag = !quotFlag;
            } else {
                if (keyFlag) {
                    key.append(ch);
                } else {
                    val.append(ch);
                }
            }
        }

        keyVal.put(key.toString().trim(), val.toString().trim());

        String validNonce = (String) authInfoMap.remove("nonce");

        //String nonce = keyVal.get("nonce");

        //System.out.println("nonce: [" + keyVal.get("nonce") + "]");
        //System.out.println("validNonce: [" + validNonce + "]");

        if (validNonce != null && validNonce.equals(keyVal.get("nonce"))) {
            String username = keyVal.get("username");
            authUser.set(username);
            String HA1 = md5(username, ":", keyVal.get("realm"), ":", getPasswordByUsername(username));
            String digestURI = keyVal.get("uri");

            /*
             * by unknown reason I replaced this question mark, but it block the request including ?
             * here I commented these sentences, it works well. Not sure if there is any other issue occur in the future.
             * Just make a note.
             * 
             * if(digestURI.contains("?")) {
               digestURI = digestURI.replace("?", "");
            }*/

            String HA2 = ("auth-int".equals(keyVal.get("qop")))
                    ? md5(method, ":", digestURI, ":", md5(authentication))
                    : md5(method, ":", digestURI);

            if (keyVal.get("qop") != null) {
                result = md5(HA1, ":", validNonce, ":", keyVal.get("nc"), ":", keyVal.get("cnonce"), ":",
                        keyVal.get("qop"), ":", HA2).equals(keyVal.get("response"));
            } else {
                result = md5(HA1, ":", validNonce, ":", HA2).equals(keyVal.get("response"));
            }
        }

        return result;
    }

    private static final char charArray[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
            'e', 'f' };

    static public String md5(String sb, char[] passwd) {

        byte[] unencodedPassword = sb.toString().getBytes();

        MessageDigest md = null;

        try {
            md = MessageDigest.getInstance("MD5");
        } catch (Exception e) {
            return sb.toString();
        }

        md.reset();
        md.update(unencodedPassword);

        for (int i = 0; i < passwd.length; i++) {
            md.update((byte) passwd[i]);
        }

        byte[] digest = md.digest();
        //StringBuffer buf = new StringBuffer();

        /*char j;
        for (int i = 0; i < encodedPassword.length; i++) {
               
           j = (char)((encodedPassword[i] >> 4) & 0xf);
               
           if(j <= 9) {
        buf.append((char)(j + '0'));
           } else {
        buf.append((char)(j + 'a' - 10));
           }
               
           j = (char)((encodedPassword[i]) & 0xf);
               
           if(j <= 9) {
        buf.append((char)(j + '0'));
           } else {
        buf.append((char)(j + 'a' - 10));
           }
        }*/

        StringBuffer res = new StringBuffer(digest.length * 2);
        for (int i = 0; i < digest.length; i++) {
            int hashchar = ((digest[i] >>> 4) & 0xf);
            res.append(charArray[hashchar]);
            hashchar = (digest[i] & 0xf);
            res.append(charArray[hashchar]);
        }

        return res.toString();
    }

    static public String md5(String... ss) {
        StringBuilder sb = new StringBuilder();
        if (ss != null) {
            for (String si : ss) {
                sb.append(si);
            }
        }

        byte[] unencodedPassword = sb.toString().getBytes();

        MessageDigest md = null;

        try {
            md = MessageDigest.getInstance("MD5");
        } catch (Exception e) {
            return sb.toString();
        }

        md.reset();
        md.update(unencodedPassword);
        byte[] digest = md.digest();
        //StringBuffer buf = new StringBuffer();

        /*char j;
        for (int i = 0; i < encodedPassword.length; i++) {
               
           j = (char)((encodedPassword[i] >> 4) & 0xf);
               
           if(j <= 9) {
        buf.append((char)(j + '0'));
           } else {
        buf.append((char)(j + 'a' - 10));
           }
               
           j = (char)((encodedPassword[i]) & 0xf);
               
           if(j <= 9) {
        buf.append((char)(j + '0'));
           } else {
        buf.append((char)(j + 'a' - 10));
           }
        }*/

        StringBuffer res = new StringBuffer(digest.length * 2);
        for (int i = 0; i < digest.length; i++) {
            int hashchar = ((digest[i] >>> 4) & 0xf);
            res.append(charArray[hashchar]);
            hashchar = (digest[i] & 0xf);
            res.append(charArray[hashchar]);
        }

        return res.toString();
    }

    public static String getPasswordByUsername(String username) {

        //System.err.println(username + " :: " + userMap);
        String password = null;
        if (authProvider != null) {
            try {
                password = authProvider.getPasswordByUsername(username);
            } catch (Exception e) {
                log.error("error while invoke authProvider.getPasswordByUsername: " + username, e);
            }

        }

        if (password == null) {
            password = userMap.get(username);
        }

        return password;
    }

    private static Map<String, String> userMap = new HashMap<String, String>();

    static {

        try {
            authProvider = BeanContext.getInstance().getBean("authenticatorProvider", AuthenticatorProvider.class);
        } catch (Exception e) {
            // no config, skip it
        }

        String ups = BaseConfiguration.getAdminUps();

        if (!CommonUtil.isEmpty(ups)) {
            String[] users = ups.split(";");

            if (users != null) {

                for (String user : users) {
                    String[] keyval = user.split(":");

                    if (keyval.length == 2) {
                        userMap.put(keyval[0], keyval[1]);
                    }
                }
            }
        }
    }

    //http://www.javadocexamples.com/java_source/sun/net/www/protocol/http/DigestAuthentication.java.html
    /*public static void main(String[] args){
        
       // 
       // keyval: {response=4b0e199f300a0420630e146d3294e4f7, cnonce=3b63996689cc9a85, username=admin, nc=00000001, qop=auth, nonce=3dcdefef-c019-4636-bc8f-65238d494422, realm=www.mntplay.com, opaque=f825ef87-c100-4587-b13f-9fea4fbba74f, uri=/monitor/, algorithm=MD5-sess}
        
       String authStr = "username=\"admin\", realm=\"www.mntplay.com\", nonce=\"3dcdefef-c019-4636-bc8f-65238d494422\", uri=\"/monitor/\", algorithm=MD5-sess, response=\"4b0e199f300a0420630e146d3294e4f7\", opaque=\"f825ef87-c100-4587-b13f-9fea4fbba74f\", qop=auth, nc=00000001, cnonce=\"3b63996689cc9a85\"";
           
       String authentication = "Digest " + authStr;
           
       Map<String, String> keyVal = new HashMap<String, String>();
           
       StringBuilder key = new StringBuilder();
       StringBuilder val = new StringBuilder();
           
       boolean quotFlag = false;
       boolean keyFlag = true;
           
       char[] chs = authStr.toCharArray();
       for(char ch : chs) {
         
     if('=' == ch) {
        keyFlag = false;
     } else if(',' == ch && !quotFlag) {
        keyFlag = true;
        keyVal.put(key.toString().trim(), val.toString().trim());
            
        key.setLength(0);
        val.setLength(0);
            
     } else if('"' == ch) {
        quotFlag = !quotFlag;
     } else {
        if(keyFlag) {
           key.append(ch);
        } else {
           val.append(ch);
        }
     }
       }
           
       keyVal.put(key.toString().trim(), val.toString().trim());
           
           
       String username = keyVal.get("username");
       String HA1 = md5(username + ":" + keyVal.get("realm") + ":", getPasswordByUsername(username).toCharArray());
       String digestURI = "http://localhost:9090/monitor/"; //keyVal.get("uri"); //
           
       String validNonce = keyVal.get("nonce");
       boolean result = false;
           
       String method = "GET";
           
       String HA2 = ("auth-int".equals(keyVal.get("qop"))) ? md5(method, ":", digestURI, ":", md5(authentication)) : md5(method, ":", digestURI);
           
       if(keyVal.get("qop") != null) {
         
     result = md5(HA1, ":", validNonce, ":", keyVal.get("nc"), ":", keyVal.get("cnonce"), ":auth:", HA2).equals(keyVal.get("response"));
       } else {
     result = md5(HA1, ":", validNonce, ":", HA2).equals(keyVal.get("response"));
       }
           
       System.out.println(keyVal);
           
       System.out.println(md5(HA1, ":", validNonce, ":", keyVal.get("nc"), ":", keyVal.get("cnonce"), ":", keyVal.get("qop"), ":", HA2));
        
       System.out.println(result);
    }*/
}