`

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

    博客分类:
  • 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。好了,祝你成功!

  • 大小: 37.7 KB
  • 大小: 131.3 KB
分享到:
评论
5 楼 hotapple 2016-12-12  
4 楼 lcathm 2015-08-12  
Map<String, String> maps = getMapByKeyArray(authHeader.getValue().split(",")); 
楼主,你这句有点问题,如果WWW-Authenticate中有qop=auth,auth-int,这里有“,”,你用逗号来split就有问题了
3 楼 407381392 2015-05-23  
代码测试已经通过,可能是我对digest认证原理没完全高明白,我再理一理过程原理。ps:你真是大牛啊!膜拜!!!确实是好文,只可惜识货的人太少...
2 楼 407381392 2015-05-23  
我有个疑问:
你下面java实现我大致能懂,但是你的算法(HA1等等)是从哪里来的,你这样访问服务器,服务器识别的时候怎么知道使用和你一样的算法计算啊?难道你这个算法是和服务器约定好的吗?这样的话是怎么约定的?对于浏览器访问服务器时,输入的用户名和密码加密算法是使用浏览器内部算法实现的,这些算法也是服务器支持的,所以它们可以使用相同的算法加密比对。但是如果自己写的算法去加密数据,服务器有对应的算法吗,这样的话是没办法实现算法比对的啊?
小弟不是很懂,希望你看到我的问题能不吝赐教,谢谢了。
1 楼 407381392 2015-05-23  

写的很不错,易懂。
我有个疑问:

相关推荐

    JAVA上百实例源码以及开源项目源代码

    java模拟掷骰子2个 1个目标文件,输出演示。 java凭图游戏 一个目标文件,简单。 java求一个整数的因子 如题。 Java生成密钥的实例 1个目标文件 摘要:Java源码,算法相关,密钥  Java生成密钥、保存密钥的实例源码...

    JAVA上百实例源码以及开源项目

    java模拟掷骰子2个 1个目标文件,输出演示。 java凭图游戏 一个目标文件,简单。 java求一个整数的因子 如题。 Java生成密钥的实例 1个目标文件 摘要:Java源码,算法相关,密钥  Java生成密钥、保存密钥的实例源码...

    讨论区BBS网站

    体现了MVC(Model——View-Controller) 的思想,通过JSP技术来表现页面,通过Servlet技术来完成大量的事物处理工作,Servlet创建JSP需要的Java bean和对象,然后根据用户的请求行为, 决定将哪个JSP页面发送...

    LojoRentVideoClub:使用DAO模式和JAVA 11,MySQL,Tomcat 8.5,Json Data和Ajax和jQuery的视频租用Web应用程序

    通过模拟视频存储的操作的AJAX请求创建了应用程序客户端。 与开发 :hammer_and_wrench: 工具和技术: -IDE项目,JAVA。 -IDE前端开发(JS,CSS3,HTML5)。 数据库。 数据库连接器。 -Gson库。 -JAVA 11。 ...

    Spring.3.x企业应用开发实战(完整版).part2

    7.10.3 在Tomcat下的配置 7.10.4 在其他Web应用服务器下的配置 7.11 小结 第3篇 数据访问 第8章 Spring对DAO的支持 8.1 Spring的DAO理念 8.2 统一的异常体系 8.2.1 Spring的DAO异常体系 8.2.2 JDBC的异常转换器 ...

    Spring3.x企业应用开发实战(完整版) part1

    7.10.3 在Tomcat下的配置 7.10.4 在其他Web应用服务器下的配置 7.11 小结 第3篇 数据访问 第8章 Spring对DAO的支持 8.1 Spring的DAO理念 8.2 统一的异常体系 8.2.1 Spring的DAO异常体系 8.2.2 JDBC的异常转换器 ...

    trade-processor:CF项目挑战

    在生产模式下,JSP 渲染最好由单独的 Tomcat 实例完成。 MongoDB 作为数据库 它允许存储两个集合:“交易”和“处理” 同样为简单起见,数据库配置了单个节点。 如果 mongo 配置了多节点集群,应该会获得更好的...

    Java学习笔记-个人整理的

    {1}Java基础}{17}{chapter.1} {1.1}基本语法}{17}{section.1.1} {1.2}数字表达方式}{17}{section.1.2} {1.3}补码}{19}{section.1.3} {1.3.1}总结}{23}{subsection.1.3.1} {1.4}数据类型}{23}{section.1.4} {...

    HonestBank:具有教育意义的网络应用程序,可模拟银行付款

    如果没有,则用户可以提交开设信用帐户的请求。 管理员在考虑存款金额和有效期的情况下确认开设帐户。 ** 安装说明 检查已安装的JDK,JRE和环境变量的显式性。 已安装的MySql Server的显式性 明确安装Apache ...

    Web安全深度剖析(张柄帅)

    《Web安全深度剖析》总结了当前流行的高危漏洞的形成原因、攻击手段及解决方案,并通过大量的示例代码复现漏洞原型,制作模拟环境,更好地帮助读者深入了解Web应用程序中存在的漏洞,防患于未然。 《Web安全深度剖析...

Global site tag (gtag.js) - Google Analytics