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&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值,请注意红色部分,如图所示:
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。好了,祝你成功!
相关推荐
【网络购物系统(jsp+java+mdb数据库)】是一个基于Web的应用程序,旨在模拟实际的在线购物体验。这个系统利用了多种技术,包括JavaServer Pages (JSP)、Java后端编程以及Access数据库来存储和管理数据。让我们深入...
- 用于连接Java应用和关系型数据库的接口,这里可能是MySQL或其他数据库,用于存储旅游信息、用户数据等。 4. **框架应用**: - **Spring框架**:提供依赖注入、事务管理、AOP(面向切面编程)等功能,简化开发...
本项目是基于Java技术栈构建的一个B2C网上拍卖与秒杀系统,旨在模拟实际的电子商务环境,提供用户参与竞拍和秒杀商品的功能。在系统设计与实现过程中,涉及了多个关键知识点,包括Web应用开发、数据库设计、并发处理...
Java网络爬虫、数据库、JSP以及搜索引擎是四个紧密关联的IT技术领域,它们共同构建了一个数据抓取、存储、展示和检索的完整系统。在这个系统中,Java网络爬虫负责从互联网上自动抓取信息,数据库用于存储这些信息,...
这篇项目是基于Android客户端、Tomcat服务器和MySQL数据库构建的一款模仿QQ聊天软件的实现,旨在为学生提供一个一万行代码的编程实践作业。这个综合性的项目涵盖了多个IT领域的核心技术,让我们逐一深入探讨。 首先...
本项目是一个基于JSP(JavaServer Pages)技术开发的产品售后服务管理系统,主要面向即将毕业的学生,提供了完整的毕业设计素材,包括项目报告、答辩PPT、源代码、数据库、系统截图以及部署教程视频。这个系统旨在...
该系统旨在模拟实际医院的挂号流程,为用户提供方便快捷的在线预约服务。设计时,考虑到系统的易用性、安全性和稳定性,采用分层架构,将业务逻辑、数据访问和用户界面进行了分离,提高了代码的可维护性和可扩展性。...
而Tomcat、Jetty等应用服务器用于部署运行这些JSP和JAVA应用。 10. **单元测试与集成测试**:JUnit和Mockito等工具可以用于编写单元测试,验证代码的正确性;而Selenium或TestNG可能用于模拟用户行为的集成测试。 ...
在这个“使用MYSQL+JAVA编写的图书管理系统,数据库课程设计作业.zip”项目中,我们可以深入探讨几个关键的IT知识点,特别是关于数据库管理和Java编程在实际应用中的整合。 首先,MySQL是世界上最受欢迎的关系型...
Java编写的山寨QQ,多人聊天+用户在线 21个目标文件 摘要:JAVA源码,媒体网络,山寨QQ,Java聊天程序 Java编写的山寨QQ,多人聊天+用户在线,程序分服务端和客户端,典型C/S结构, 当用户发送第一次请求的时候,验证...
在这个Java Web模拟项目中,我们将会探讨如何使用SSM(Spring、SpringMVC、MyBatis)框架来构建一个购物平台。SSM是Java企业级应用开发中的常见组合,它提供了强大的模型-视图-控制器架构支持以及持久层操作的能力。...
Struts2负责处理用户请求和控制流程,Spring提供了容器管理和依赖注入,简化了组件的配置和协作,而Hibernate则将Java对象与关系数据库进行映射,简化了数据操作。 5. **Tomcat**: Tomcat是一款开源的Servlet容器,...
JUnit是常用的Java单元测试框架,而Servlet容器提供的模拟环境(如Tomcat的Catalina Embed)可以用来进行集成测试。 10. **版本控制**: 开发过程中,版本控制工具如Git可以帮助团队协作,跟踪代码更改,并方便回滚...
总的来说,这个Java图书馆系统设计项目涵盖了Web开发中的多个重要知识点,包括数据库设计、前后端交互、用户认证、事务管理、单元测试等。通过研究源代码,开发者不仅可以学习到具体的技术实现,还能了解到一个完整...
而集成测试可能会使用像Struts2提供的MockMVC工具或者Servlet容器如Tomcat来模拟整个请求处理流程。 总的来说,Java当当网代码是一个全面展示Java Web开发实践的项目,涵盖了Struts2框架的应用、MVC设计模式的实现...
Tomcat,是Apache软件基金会下的一个开源项目,是Java Servlet和JavaServer Pages(JSP)的容器,广泛用于部署Java Web应用。在集群环境中,多台Tomcat服务器可以协同工作,通过Nginx进行负载均衡,增强系统的稳定性...
3. **数据库接入**:在模拟订单生成的场景下,数据库用于存储订单信息。可能使用了MySQL、Oracle、SQL Server等关系型数据库,或者MongoDB、Cassandra等NoSQL数据库。数据库操作通常通过JDBC(Java Database ...
`Tomcat连接池`是Java应用服务器中用于管理数据库连接的一种机制,它的目的是提高数据库连接的复用性,减少创建和销毁连接的开销,从而提升应用的性能和响应速度。Tomcat内置了多种连接池实现,如Apache的Commons ...
8. **部署与运行**:项目可能需要在Tomcat、Jetty等Servlet容器上运行,部署时需要将编译后的Java类和JSP文件放入容器的相应目录,配置好数据库连接参数。 9. **版本控制**:开发过程中,代码可能使用Git等版本控制...