`

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  

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

相关推荐

    网络购物系统(jsp+java+mdb数据库)

    【网络购物系统(jsp+java+mdb数据库)】是一个基于Web的应用程序,旨在模拟实际的在线购物体验。这个系统利用了多种技术,包括JavaServer Pages (JSP)、Java后端编程以及Access数据库来存储和管理数据。让我们深入...

    基于Java的旅游网站系统设计与实现(项目报告+答辩PPT+源代码+数据库+截图+部署视频).zip

    - 用于连接Java应用和关系型数据库的接口,这里可能是MySQL或其他数据库,用于存储旅游信息、用户数据等。 4. **框架应用**: - **Spring框架**:提供依赖注入、事务管理、AOP(面向切面编程)等功能,简化开发...

    基于Java的B2C网上拍卖秒杀与竞价系统设计与实现(项目报告+答辩PPT+源代码+数据库+截图+部署视频).zip

    本项目是基于Java技术栈构建的一个B2C网上拍卖与秒杀系统,旨在模拟实际的电子商务环境,提供用户参与竞拍和秒杀商品的功能。在系统设计与实现过程中,涉及了多个关键知识点,包括Web应用开发、数据库设计、并发处理...

    java网络爬虫+数据库+jsp+搜索引擎.rar.rar

    Java网络爬虫、数据库、JSP以及搜索引擎是四个紧密关联的IT技术领域,它们共同构建了一个数据抓取、存储、展示和检索的完整系统。在这个系统中,Java网络爬虫负责从互联网上自动抓取信息,数据库用于存储这些信息,...

    Android+Tomcat+MySql一万行代码模仿QQ聊天软件

    这篇项目是基于Android客户端、Tomcat服务器和MySQL数据库构建的一款模仿QQ聊天软件的实现,旨在为学生提供一个一万行代码的编程实践作业。这个综合性的项目涵盖了多个IT领域的核心技术,让我们逐一深入探讨。 首先...

    基于jsp的产品售后服务管理系统毕业设计(项目报告+答辩PPT+源代码+数据库+截图+部署视频).zip

    本项目是一个基于JSP(JavaServer Pages)技术开发的产品售后服务管理系统,主要面向即将毕业的学生,提供了完整的毕业设计素材,包括项目报告、答辩PPT、源代码、数据库、系统截图以及部署教程视频。这个系统旨在...

    基于java+ssh的医院在线挂号系统毕业设计(项目报告+答辩PPT+源代码+数据库+部署视频).zip

    该系统旨在模拟实际医院的挂号流程,为用户提供方便快捷的在线预约服务。设计时,考虑到系统的易用性、安全性和稳定性,采用分层架构,将业务逻辑、数据访问和用户界面进行了分离,提高了代码的可维护性和可扩展性。...

    5款新闻发布系统(JSP+JAVA源代码)

    而Tomcat、Jetty等应用服务器用于部署运行这些JSP和JAVA应用。 10. **单元测试与集成测试**:JUnit和Mockito等工具可以用于编写单元测试,验证代码的正确性;而Selenium或TestNG可能用于模拟用户行为的集成测试。 ...

    使用MYSQL+JAVA编写的图书管理系统,数据库课程设计作业.zip

    在这个“使用MYSQL+JAVA编写的图书管理系统,数据库课程设计作业.zip”项目中,我们可以深入探讨几个关键的IT知识点,特别是关于数据库管理和Java编程在实际应用中的整合。 首先,MySQL是世界上最受欢迎的关系型...

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

    Java编写的山寨QQ,多人聊天+用户在线 21个目标文件 摘要:JAVA源码,媒体网络,山寨QQ,Java聊天程序 Java编写的山寨QQ,多人聊天+用户在线,程序分服务端和客户端,典型C/S结构, 当用户发送第一次请求的时候,验证...

    Java Web模拟项目 ssm框架开发 购物平台 mysql数据库 前后端

    在这个Java Web模拟项目中,我们将会探讨如何使用SSM(Spring、SpringMVC、MyBatis)框架来构建一个购物平台。SSM是Java企业级应用开发中的常见组合,它提供了强大的模型-视图-控制器架构支持以及持久层操作的能力。...

    java模拟酷狗官网源码(附数据库文件)

    Struts2负责处理用户请求和控制流程,Spring提供了容器管理和依赖注入,简化了组件的配置和协作,而Hibernate则将Java对象与关系数据库进行映射,简化了数据操作。 5. **Tomcat**: Tomcat是一款开源的Servlet容器,...

    Java课设+Java Web 基于JSP+Servlet的论坛系统.zip

    JUnit是常用的Java单元测试框架,而Servlet容器提供的模拟环境(如Tomcat的Catalina Embed)可以用来进行集成测试。 10. **版本控制**: 开发过程中,版本控制工具如Git可以帮助团队协作,跟踪代码更改,并方便回滚...

    java图书馆系统设计有源代码

    总的来说,这个Java图书馆系统设计项目涵盖了Web开发中的多个重要知识点,包括数据库设计、前后端交互、用户认证、事务管理、单元测试等。通过研究源代码,开发者不仅可以学习到具体的技术实现,还能了解到一个完整...

    Java当当网代码

    而集成测试可能会使用像Struts2提供的MockMVC工具或者Servlet容器如Tomcat来模拟整个请求处理流程。 总的来说,Java当当网代码是一个全面展示Java Web开发实践的项目,涵盖了Struts2框架的应用、MVC设计模式的实现...

    Nginx+Redis+Tomcat 集群部署

    Tomcat,是Apache软件基金会下的一个开源项目,是Java Servlet和JavaServer Pages(JSP)的容器,广泛用于部署Java Web应用。在集群环境中,多台Tomcat服务器可以协同工作,通过Nginx进行负载均衡,增强系统的稳定性...

    javaweb接入语音朗读功能项目源码+数据库,模拟订单生成的语音播放

    3. **数据库接入**:在模拟订单生成的场景下,数据库用于存储订单信息。可能使用了MySQL、Oracle、SQL Server等关系型数据库,或者MongoDB、Cassandra等NoSQL数据库。数据库操作通常通过JDBC(Java Database ...

    tomcat连接池的配置与性能测试

    `Tomcat连接池`是Java应用服务器中用于管理数据库连接的一种机制,它的目的是提高数据库连接的复用性,减少创建和销毁连接的开销,从而提升应用的性能和响应速度。Tomcat内置了多种连接池实现,如Apache的Commons ...

    基于Java的源码-JSP 学生管理系统(全部代码+数据库).zip

    8. **部署与运行**:项目可能需要在Tomcat、Jetty等Servlet容器上运行,部署时需要将编译后的Java类和JSP文件放入容器的相应目录,配置好数据库连接参数。 9. **版本控制**:开发过程中,代码可能使用Git等版本控制...

Global site tag (gtag.js) - Google Analytics