tomcat下摘要认证(数据库配置用户角色)+java代码模拟请求

1. 如果你不明白摘要认证,可以看看这个网站:【http://zh.wikipedia.org/wiki/HTTP摘要认证】

 

2. 这篇文章解决了什么呢?①基于tomcat的摘要认证配置 ②用户名与角色存储到数据库中 ③java代码模拟客户端请求服务端;好了,让我们一起来理解摘要认证原理吧。

 

3. 怎么给自己的资源(被请求的文件)加上摘要认证呢?且按照下面一步步配置:

  • 准备好环境,我的环境是:linux虚拟机+tomcat7.0+mysql+jdk1.7
  • 需要在mysql数据库中插入这两张表,并且需要在里面加入几条数据,我想这肯定难不倒你们的,贴出我的表结构吧。
  • 用户表:

       CREATE TABLE `TNt_tomcat_users` (
         `TNc_user_name` varchar(20) NOT NULL,
         `TNc_user_pass` varchar(64) NOT NULL,
         PRIMARY KEY (`TNc_user_name`)
       ) ENGINE=MyISAM DEFAULT CHARSET=utf8;

  • 角色表:

       CREATE TABLE `TNt_tomcat_roles` (
         `TNc_user_name` varchar(20) NOT NULL,
         `TNc_role_name` varchar(20) NOT NULL,
         PRIMARY KEY (`TNc_user_name`,`TNc_role_name`)
       ) ENGINE=MyISAM DEFAULT CHARSET=utf8;

  • 用户数据:

       insert into TNt_tomcat_users (TNc_user_name, TNc_user_pass) values ( 'zhang3', '123456');

       insert into TNt_tomcat_users (TNc_user_name, TNc_user_pass) values ( 'li4', '123456');

  • 角色数据:

       insert into TNt_tomcat_roles (TNc_user_name, TNc_role_name) values ( 'zhang3', 'ADMIN');

       insert into TNt_tomcat_roles (TNc_user_name, TNc_role_name) values ( 'zhang3', 'USER');

       insert into TNt_tomcat_roles (TNc_user_name, TNc_role_name) values ( 'li4', 'USER');

       commit;

  •  在你的WEB工程"WebContent"-->"META-INF"下新建一个文件,文件名为:context.xml,请在里面配置以下信息(tomcat会启动时会加载这个文件,记得这个节点大小写敏感):
<?xml version="1.0" encoding="UTF-8"?>
<Context path="/tnDigest">
   <Realm className="org.apache.catalina.realm.JDBCRealm"
         connectionName="thinknet" connectionPassword="thinknet1234"
         connectionURL="jdbc:mysql://192.168.0.30:3306/TNd_platform?useUnicode=true&amp;characterEncoding=utf8"
         driverName="com.mysql.jdbc.Driver" roleNameCol="TNc_role_name"
         userCredCol="TNc_user_pass" userNameCol="TNc_user_name" userRoleTable="TNt_tomcat_roles" userTable="TNt_tomcat_users" />
</Context>
  •  在web.xml中配置以下信息:
        <!-- 设置需要认证的范围 -->
	<security-constraint>
		<display-name>TN Auth</display-name>
		<web-resource-collection>
			<web-resource-name>Protected Area</web-resource-name>
			<url-pattern>/ps/*</url-pattern>
			<url-pattern>/health/*</url-pattern>
			<url-pattern>/door/*</url-pattern>
			<url-pattern>/consume/*</url-pattern>
			<url-pattern>/app/*</url-pattern>
			<url-pattern>/poll/*</url-pattern>
			<http-method>DELETE</http-method>
			<http-method>GET</http-method>
			<http-method>POST</http-method>
			<http-method>PUT</http-method>
		</web-resource-collection>
		<auth-constraint>
			<role-name>ADMIN</role-name>
			<role-name>USER</role-name>
		</auth-constraint>
	</security-constraint>
	
	<!-- 设置该Web应用使用到的角色 -->
	<security-role>
		<role-name>ADMIN</role-name>
	</security-role>
	<security-role>
		<role-name>USER</role-name>
	</security-role>
	
	<!-- 摘要认证方式 -->
	<login-config>
		<auth-method>DIGEST</auth-method>
		<realm-name>www.think-net.cn</realm-name>
	</login-config>
  •  如果按照上面你去启动tomcat这时会出错,报缺少ClassNotFoundException:com.mysql.jdbc.Driver;所以你得把mysql的驱动包(mysql-connector-java-5.1.7-bin.jar)放到{tomcat}/lib目录下
  •  至此就已经完成了HTTP 摘要认证配置。

4. 让我们来请求刚刚设置需要认证的资源,看看浏览器会提示你什么:

 输入用户名与密码进行摘要认证

5. 当你输入正确的用户名与密码确定之后,你的请求得到服务器的认可,即可看到服务端响应的资源信息,不然你会得到一个错误码为401的信息,401表示你无权限访问该资源。

6. 如果你需要用代码访问一个带有摘要认证的资源时,如何编写这样的代码?你如果够认真的看完了【http://zh.wikipedia.org/wiki/HTTP摘要认证】这个链接中的介绍,或许就会知道要在你的request中加上一个头信息,它为Authorization(授权)、关键你得认真的加密得到一个response(下面讲解),在浏览器中你请求成功之后按“F12”看看你的request(请求)header(头)中有Authorization值,请注意红色部分,如图所示:

 浏览器中request信息

                                                                                                                  1.2

 7. 上图header-Authorization中有一个“response”这个值你可以理解成一个复杂的密码,它是由很多信息组装得来的,并且还有先后顺序,它是认证基准;整个校验过程是这样的(用自己与维基百科中得出的总结,这里以浏览器为客户端):在浏览器中输入http://192.168.0.27:8080/tnserver/ps,回车之后浏览器开始发送HTTP GET请求到对应的服务端,服务端由于需要摘要认证,这时会从客户端请求头中获得Authorization,由于刚开始客户端请求头中是没有带Authorization认证信息的,所以服务端会响应一个状态码为401的信息给客户端(浏览器),浏览器解析得知需要认证之后,会弹出一个框让用户输入”用户名与密码“,当输入完成并且点击确定之后,浏览器会拿到用户输入的用户名与密码,这没完,真正认证才刚刚开始;上面我已经说的比较清楚了,Authorization头中的response值是认证基准,所以浏览器必须要计算(组合加密)出这个值,这个值是由以下三个表达式得到的,HA1=MD5(username:realm:password)、HA2=MD5(method:uri)、response=(HA1:nonce:nc:cnonce:qop:HA2),username与password就是用户输入的用户名与密码(即数据库中的用户名与密码);realm就是你在web.xml中配置的一个域地址(www.think-net.cn这个好像可以任意设置);method是你请求的类型(HTTP 方法主要有四种GET、POST、PUT、DELETE;HEAD、OPTIONS、TRACE、PATCH[这四种不常用])我示例中为GET请求;uri为你请求的资源地址,但不需要hostname与port,我示例中为/tnserver/ps;HA1与HA2为通过MD5加密得出来的密文;nonce为服务端产生的一个随机数;nc为客户端产生的随机计数;cnonce为客户端产生的随机数;qop为质量保护,我示例中采用的是auth方式,如果你的不是这种方式,请不要按照上面的公式计算。关于最后一个表达式与维基百科中的有些不同,其实是一样的只是维基百科说得更加形象,而我是按照原始请求名称来表示的,如nc=nonceCount、cnonce=clientNonce,表达式中的冒号都是必须拼凑一起加密的。好了讲完了表达式得出了response,客户端就开始拼凑Authorization头,正确完整的Authorization头信息为上图1.2红色部分那样,客户端必须要带上username、response、uri、nc、cnonce这些信息,当然realm、nonce、opaque、qop这些信息也是非常重要的,如果其中任何信息不对都会导致认证失败;客户端完成认证头信息之后继续请求服务端上的资源,服务端收到请求之后开始解析请求Authorization头中的信息,这时服务端会从数据库中查找这个用户与密码,并且按照与客户端一样的表达式算出response,两者比较,如果匹配说明认证通过,如果不匹配则继续返回状态码为401的信息给客户端。

8. 以上红色字体介绍的原理,如果有兴趣可以认真看看,我知道还是会有人不会认真的看完以上文字,或是只是粗略的看一下,并没有完全理解,虽然比较简单,但往往小的问题才是阻碍进步的绊脚石,但我为了不让大家出错,或是让以后的我直接快速回忆我贴出以上表达式的示例:

String HA1 = MD5Object.encrypt("li4"+ ":"
                + "www.think-net.cn" + ":" + "123456");

String HA2 = MD5Object.encrypt("GET:" + "/tnserver/ps");

String response = MD5Object.encrypt(HA1 + ":" + "1401146352907:d154d4291a7eebcdecd3cb343d8bc887" + ":"
                + "00000003" + ":" + "bcb0b7171075d403" + ":"
                + "auth" + ":" + HA2);

 9. 说了这么多,该把模拟访问代码贴出来了(由于这次是测试代码,所以我没有把代码写规范,代码规范乃是衡量一个好程序员的重要标准之一,我不源承认下面是我写的代码,因为它不够完整性):

主体代码:

    public static void main(String[] args) throws Exception
    {
        DefaultHttpClient defHttp = new DefaultHttpClient();
        HttpHost httpHost = new HttpHost("192.168.0.27", 8080,"http");
        String uri = "/tnserver/ps";
        HttpGet httpGet = new HttpGet(uri);

        HttpResponse response = defHttp.execute(httpHost, httpGet);
        System.out.println(response.getStatusLine().getStatusCode());
        // 如果服务端返回401(鉴权失败)
        if (response.getStatusLine().getStatusCode() == 401)
        {
            // 服务端响应头中会带有一个WWW-Authenticate的信息
            Header[] authHeaders = response.getHeaders("WWW-Authenticate");
            Header authHeader = authHeaders[0];
            System.out.println(authHeader.getValue());
            
            // WWW-Authenticate value中有很多信息,如nonce、qop、opaque、realm信息
            Map<String, String> maps = getMapByKeyArray(authHeader.getValue()
                    .split(","));

            maps.put("username", "li4");

            maps.put("nc", "00000002");
            maps.put("cnonce", "6d9a4895d16b3021");
            maps.put("uri", uri);
            maps.put("response", getResponse(maps));

            // 开始拼凑Authorization 头信息
            StringBuffer authorizationHaderValue = new StringBuffer();
            authorizationHaderValue
                    .append("Digest username=\"")
                    .append(maps.get("username"))
                    .append("\", ")
                    .append("realm=\"")
                    .append(maps.get("realm"))
                    .append("\", ")
                    // .append("nonce=\"").append(maps.get("nonceTime")).append(maps.get("nonce")).append("\", ")
                    .append("nonce=\"").append(maps.get("nonce"))
                    .append("\", ").append("uri=\"").append(maps.get("uri"))
                    .append("\", ").append("response=\"")
                    .append(maps.get("response")).append("\", ")
                    .append("opaque=\"").append(maps.get("opaque"))
                    .append("\", ").append("qop=").append(maps.get("qop"))
                    .append(", ").append("nc=").append(maps.get("nc"))
                    .append(", ").append("cnonce=\"")
                    .append(maps.get("cnonce")).append("\"");

            System.out.println(authorizationHaderValue.toString());
            
            defHttp = new DefaultHttpClient();
            
            // 添加到请求头中
            httpGet.addHeader("Authorization",
                    authorizationHaderValue.toString());

            // 请求资源
            response = defHttp.execute(httpHost, httpGet);
            // 打印响应码
            System.out.println(response.getStatusLine().getStatusCode());
            // 打印响应的信息
            System.out.println(readResultStreamString(response.getEntity(),
                    defHttp));
        }

    }

    /**
     * 通过HTTP 摘要认证的算法得出response
     * @return String
     */
    public static String getResponse(Map<String, String> maps) throws Exception
    {
        String HA1 = MD5Object.encrypt(maps.get("username") + ":"
                + maps.get("realm") + ":" + "123456");
        System.out.println("HA1:" + HA1);

        String HA2 = MD5Object.encrypt("GET:" + maps.get("uri"));
        System.out.println("HA2:" + HA2);

        String response = MD5Object.encrypt(HA1 + ":" + maps.get("nonce") + ":"
                + maps.get("nc") + ":" + maps.get("cnonce") + ":"
                + maps.get("qop") + ":" + HA2);
        System.out.println(response);
        return response;
    }

    public static String getValueByName(String resourceStr)
    {
        return resourceStr.substring(resourceStr.indexOf("\"") + 1,
                resourceStr.lastIndexOf("\""));

    }

    public static Map<String, String> getMapByKeyArray(String[] resourceStr)
    {
        Map<String, String> maps = new HashMap<String, String>(8);
        for (String str : resourceStr)
        {
            if (str.contains("realm"))
            {
                maps.put("realm", getValueByName(str));
            }
            else if (str.contains("qop"))
            {
                maps.put("qop", getValueByName(str));
            }
            else if (str.contains("nonce"))
            {
                maps.put("nonce", getValueByName(str));
                // maps.put("nonce", getValueByName(str, "nonce"));
                // maps.put("nonceTime", getValueByName(str, "nonceTime") + ":");
            }
            else if (str.contains("opaque"))
            {
                maps.put("opaque", getValueByName(str));
            }
        }

        return maps;
    }

    /**
     * 用于读取字符串响应结果
     * 
     * @return String
     * 
     * @throws IOException
     */
    protected static String readResultStreamString(HttpEntity httpEntity,
            DefaultHttpClient defaultHttpClient) throws IOException
    {
        String result = null;
        InputStream resultStream = null;

        ByteArrayOutputStream outputStream = null;
        try
        {
            resultStream = httpEntity.getContent();

            outputStream = new ByteArrayOutputStream(45555);

            byte[] temp = new byte[4096];
            int length = resultStream.read(temp);
            while (length > 0)
            {
                outputStream.write(temp, 0, length);

                length = resultStream.read(temp);
            }

            defaultHttpClient.getConnectionManager().shutdown();
        }
        catch (IOException ioEx)
        {
            throw new IOException(ioEx);
        }
        finally
        {
            if (null != resultStream)
            {
                try
                {
                    resultStream.close();
                }
                catch (Exception ex)
                {
                    resultStream = null;
                }
            }
        }

        if (null != outputStream)
        {
            result = outputStream.toString();
        }
        return result;
    }

MD5帮助类:

package cn.thinknet.utils.encrypt;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/*
 * MD5 算法
*/
public class MD5Object {
    
    // 全局数组
    private final static String[] strDigits = { "0", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };

    public MD5Object() {
    }

    // 返回形式为数字跟字符串
    private static String byteToArrayString(byte bByte) {
        int iRet = bByte;
        // System.out.println("iRet="+iRet);
        if (iRet < 0) {
            iRet += 256;
        }
        int iD1 = iRet / 16;
        int iD2 = iRet % 16;
        return strDigits[iD1] + strDigits[iD2];
    }

    // 转换字节数组为16进制字串
    private static String byteToString(byte[] bByte) {
        StringBuffer sBuffer = new StringBuffer();
        for (int i = 0; i < bByte.length; i++) {
            sBuffer.append(byteToArrayString(bByte[i]));
        }
        return sBuffer.toString();
    }

    public static String encrypt(String strObj) {
        String resultString = null;
        try {
            resultString = new String(strObj);
            MessageDigest md = MessageDigest.getInstance("MD5");
            // md.digest() 该函数返回值为存放哈希值结果的byte数组
            resultString = byteToString(md.digest(strObj.getBytes()));
        } catch (NoSuchAlgorithmException ex) {
            ex.printStackTrace();
        }
        return resultString;
    }
}

如果你需要使用以上代码,你要得到这几jar包:httpclient-4.0.jar、httpcore-4.0.1.jar、httpmime-4.0.jar。好了,祝你成功!

1. 如果你不明白摘要认证,可以看看这个网站:【 http://zh.wikipedia.org/wiki/HTTP摘要认证】   2. 这篇文章解决了什么呢?①基于tomcat的摘要认证配置 ②用户名与角色存储到数据库中 ③java代码模拟客户端请求服务端;好了,让我们一起来理解摘要认证原理吧。   3. 怎么给自己的资源(被请求的文件)加上摘要认证呢?且按照下面一步步配置:
  • 准备好环境,我的环境是:linux虚拟机+tomcat7.0+mysql+jdk1.7
  • 需要在mysql数据库中插入这两张表,并且需要在里面加入几条数据,我想这肯定难不倒你们的,贴出我的表结构吧。
  • 用户表:

猜你喜欢

转载自lsz1023-126-com.iteye.com/blog/2072066