HTTP digest RFC2671规范 加密实现(JAVA)
程序员文章站
2022-06-15 17:57:17
...
HTTP diest认证
一、介绍:
可用于访问资源时的身份认证。当客户端未通过服务器的认证,服务器返回401,并传递WWW-Authenticate信息,客户端可通过WWW-Authenticate,生成response返回给服务器来实现认证。
二、实例:
1)客户端发起一次请求,报文如下:
GET / HTTP/1.1
Host: 192.168.7.202:7547
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.96 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8
2)服务器响应401,报文如下:
HTTP/1.1 401 Unauthorized
Content-Length: 0
Connection: close
WWW-Authenticate: Digest realm="realm@easycwmp",qop="auth",nonce="c10c9897f05a9aee2e2c5fdebf03bb5b0001b1ef",
opaque="328458fab28345ae87ab3210a8513b14eff452a2"
3)客户端根据WWW-Authenticate生成response,报文如下:
GET / HTTP/1.1
Host: 192.168.7.202:7547
Connection: keep-alive
Authorization: Digest username="povodo", realm="realm@easycwmp", qop=auth, nonce="c10c9897f05a9aee2e2c5fdebf03bb5b0001b1ef", opaque="328458fab28345ae87ab3210a8513b14eff452a2", uri="/", response="77d8c3e7ad338e86b8d039867651b65f", nc=00000001, cnonce="d5324153548c43d8"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.96 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8
4)服务器响应:报文如下:
HTTP/1.1 200 OK
Content-Length: 0
一次完整的认证完成了。
三、认证方法实现(JAVA):
加密算法:
四、客户端请求(GET方式示例):
相关代码已上传附件。
如有错误,欢迎指正!
5、参考:
详解HTTP中的摘要认证机制
RFC 2017文档说明
一、介绍:
可用于访问资源时的身份认证。当客户端未通过服务器的认证,服务器返回401,并传递WWW-Authenticate信息,客户端可通过WWW-Authenticate,生成response返回给服务器来实现认证。
二、实例:
1)客户端发起一次请求,报文如下:
引用
GET / HTTP/1.1
Host: 192.168.7.202:7547
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.96 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8
2)服务器响应401,报文如下:
引用
HTTP/1.1 401 Unauthorized
Content-Length: 0
Connection: close
WWW-Authenticate: Digest realm="realm@easycwmp",qop="auth",nonce="c10c9897f05a9aee2e2c5fdebf03bb5b0001b1ef",
opaque="328458fab28345ae87ab3210a8513b14eff452a2"
3)客户端根据WWW-Authenticate生成response,报文如下:
引用
GET / HTTP/1.1
Host: 192.168.7.202:7547
Connection: keep-alive
Authorization: Digest username="povodo", realm="realm@easycwmp", qop=auth, nonce="c10c9897f05a9aee2e2c5fdebf03bb5b0001b1ef", opaque="328458fab28345ae87ab3210a8513b14eff452a2", uri="/", response="77d8c3e7ad338e86b8d039867651b65f", nc=00000001, cnonce="d5324153548c43d8"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.96 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8
4)服务器响应:报文如下:
引用
HTTP/1.1 200 OK
Content-Length: 0
一次完整的认证完成了。
三、认证方法实现(JAVA):
加密算法:
//我用的是commons-codec-1.9.jar包 import org.apache.commons.codec.binary.Hex; import java.io.IOException; import java.io.InputStream; import java.security.GeneralSecurityException; import java.security.MessageDigest; import java.security.SecureRandom; public class Digests { /** * 加密遵循RFC2671规范 * 将相关参数加密生成一个MD5字符串,并返回 */ public static String http_da_calc_HA1(String username, String realm, String password, String nonce, String nc, String cnonce, String qop, String method, String uri, String algorithm){ String HA1,HA2; if ("MD5-sess".equals(algorithm)){ HA1 = HA1_MD5_sess(username,realm,password,nonce,cnonce); } else{ HA1 = HA1_MD5(username,realm,password); } byte[] md5Byte = md5(HA1.getBytes()); HA1 = new String(Hex.encodeHex(md5Byte)); md5Byte = md5(HA2(method, uri).getBytes()); HA2 = new String(Hex.encodeHex(md5Byte)); String original = HA1 + ":" + (nonce +":"+ nc +":"+ cnonce +":"+ qop) + ":" + HA2; md5Byte = md5(original.getBytes()); return new String(Hex.encodeHex(md5Byte)); } /** * algorithm值为MD5时规则 */ private static String HA1_MD5(String username, String realm, String password){ return username+":"+realm+":"+password; } /** * algorithm值为MD5-sess时规则 */ private static String HA1_MD5_sess(String username, String realm, String password, String nonce, String cnonce){ // MD5(username:realm:password):nonce:cnonce String s = HA1_MD5(username, realm, password); byte[] md5Byte = md5(s.getBytes()); String smd5 = new String(Hex.encodeHex(md5Byte)); return smd5+":"+nonce+":"+cnonce; } private static String HA2(String method, String uri){ return method+":"+uri; } /** * 对输入字符串进行md5散列. */ public static byte[] md5(byte[] input) { return digest(input, MD5, null, 1); } /** * 对字符串进行散列, 支持md5与sha1算法. */ private static byte[] digest(byte[] input, String algorithm, byte[] salt, int iterations) { try { MessageDigest digest = MessageDigest.getInstance(algorithm); if (salt != null) { digest.update(salt); } byte[] result = digest.digest(input); for (int i = 1; i < iterations; i++) { digest.reset(); result = digest.digest(result); } return result; } catch (GeneralSecurityException e) { throw new RuntimeException(e); } } //测试 public static void main(String[] args) { String s = http_da_calc_HA1("povodo", "realm@easycwmp", "povodo", "c10c9897f05a9aee2e2c5fdebf03bb5b0001b1ef", "00000001", "d5324153548c43d8", "auth", "GET", "/", "MD5"); System.out.println("加密后response为:"+s); }
四、客户端请求(GET方式示例):
//我这里用的fastjson-1.1.41.jar import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.povodo.map.servlet.StringUtils; import org.apache.commons.codec.binary.Hex; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.util.List; import java.util.Map; public class HttpRequestUtils { static int nc = 0; //调用次数 /** * 向指定URL发送GET方法的请求 * @param url 发送请求的URL * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 * @param username 验证所需的用户名 * @param password 验证所需的密码 * @return URL 所代表远程资源的响应结果 */ public static String sendGet(String url, String param, String username, String password) { StringBuilder result = new StringBuilder(); BufferedReader in = null; try { String wwwAuth = sendGet(url, param); //发起一次授权请求 if (wwwAuth.startsWith("WWW-Authenticate:")) { wwwAuth = wwwAuth.replaceFirst("WWW-Authenticate:", ""); } else { return wwwAuth; } nc ++; String urlNameString = url + (StringUtils.isNotEmpty(param) ? "?" + param : ""); URL realUrl = new URL(urlNameString); // 打开和URL之间的连接 URLConnection connection = realUrl.openConnection(); // 设置通用的请求属性 connection.setRequestProperty("accept", "*/*"); connection.setRequestProperty("connection", "Keep-Alive"); connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); //授权信息 String authentication = getAuthorization(wwwAuth, realUrl.getPath(), username, password); connection.setRequestProperty("Authorization", authentication); // 建立实际的连接 connection.connect(); in = new BufferedReader(new InputStreamReader(connection.getInputStream())); String line; while ((line = in.readLine()) != null) { result.append(line); } nc = 0; } catch (Exception e) { nc = 0; throw new RuntimeException(e); } finally { try { if (in != null) in.close(); } catch (Exception e2) { e2.printStackTrace(); } } return result.toString(); } /** * 生成授权信息 * @param authorization 上一次调用返回401的WWW-Authenticate数据 * @param username 用户名 * @param password 密码 * @return 授权后的数据, 应放在http头的Authorization里 * @throws IOException 异常 */ private static String getAuthorization(String authorization, String uri, String username, String password) throws IOException { uri = StringUtils.isEmpty(uri) ? "/" : uri; String temp = authorization.replaceFirst("Digest", "").trim(); String json = "{\"" + temp.replaceAll("=", "\":").replaceAll(",", ",\"") + "}"; JSONObject jsonObject = JSON.parseObject(json); String cnonce = new String(Hex.encodeHex(Digests.generateSalt(8))); //客户端随机数 String ncstr = ("00000000" + nc).substring(Integer.toString(nc).length()); //认证的次数,第一次是1,第二次是2... String algorithm = jsonObject.getString("algorithm"); String response = Digests.http_da_calc_HA1(username, jsonObject.getString("realm"), password, jsonObject.getString("nonce"), ncstr, cnonce, jsonObject.getString("qop"), "GET", uri, algorithm); //组成响应authorization authorization = "Digest username=\"" + username + "\"," + temp; authorization += ",uri=\"" + uri + "\",nc=\"" + ncstr + "\",cnonce=\"" + cnonce + "\",response=\"" + response+"\""; return authorization; } /** * 向指定URL发送GET方法的请求 * * @param url 发送请求的URL * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 * @return URL 所代表远程资源的响应结果 */ public static String sendGet(String url, String param) { StringBuilder result = new StringBuilder(); BufferedReader in = null; try { String urlNameString = url + (StringUtils.isNotEmpty(param) ? "?" + param : ""); URL realUrl = new URL(urlNameString); // 打开和URL之间的连接 URLConnection connection = realUrl.openConnection(); // 设置通用的请求属性 connection.setRequestProperty("accept", "*/*"); connection.setRequestProperty("connection", "Keep-Alive"); connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); connection.connect(); //返回401时需再次用用户名和密码请求 //此情况返回服务器的 WWW-Authenticate 信息 if (((HttpURLConnection) connection).getResponseCode() == 401) { Map<String, List<String>> map = connection.getHeaderFields(); return "WWW-Authenticate:" + map.get("WWW-Authenticate").get(0); } in = new BufferedReader(new InputStreamReader(connection.getInputStream())); String line; while ((line = in.readLine()) != null) { result.append(line); } } catch (Exception e) { throw new RuntimeException("get请求发送失败",e); } // 使用finally块来关闭输入流 finally { try { if (in != null) in.close(); } catch (Exception e2) { e2.printStackTrace(); } } return result.toString(); } }
相关代码已上传附件。
如有错误,欢迎指正!
5、参考:
详解HTTP中的摘要认证机制
RFC 2017文档说明