tomcat下摘要认证(数据库配置用户角色)+java代码模拟请求
发布日期:2022-02-09 20:39:03 浏览次数:5 分类:技术文章

本文共 11540 字,大约阅读时间需要 38 分钟。

1. 如果你不明白摘要认证,可以看看这个网站:【摘要认证】

 

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会启动时会加载这个文件,记得这个节点大小写敏感):
  •  在web.xml中配置以下信息:
TN Auth
Protected Area
/ps/*
/health/*
/door/*
/consume/*
/app/*
/poll/*
DELETE
GET
POST
PUT
ADMIN
USER
ADMIN
USER
DIGEST
www.think-net.cn
  •  如果按照上面你去启动tomcat这时会出错,报缺少ClassNotFoundException:com.mysql.jdbc.Driver;所以你得把mysql的驱动包(mysql-connector-java-5.1.7-bin.jar)放到{tomcat}/lib目录下
  •  至此就已经完成了HTTP 摘要认证配置。

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

 

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

 

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

 

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

 浏览器中request信息

                                                                                                                  1.2

 

 7. 上图header-Authorization中有一个“response”这个值你可以理解成一个复杂的密码,它是由很多信息组装得来的,并且还有先后顺序,它是认证基准;整个校验过程是这样的(用自己与维基百科中得出的总结,这里以浏览器为客户端):在浏览器中输入,回车之后浏览器开始发送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中配置的一个域地址(这个好像可以任意设置);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
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
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
getMapByKeyArray(String[] resourceStr) { Map
maps = new HashMap
(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。好了,祝你成功!

转载地址:https://blog.csdn.net/iteye_12884/article/details/82580737 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Spring+CXF+IBatis详细介绍及下载
下一篇:后台JS校验框架

发表评论

最新留言

不错!
[***.144.177.141]2024年04月09日 09时16分59秒