HTTP Digest接入方式鉴权认证流程
一、摘要认证原理
摘要认证与基础认证的工作原理很相似,用户先发出一个没有认证证书的请求,Web服务器回复一个带有WWW-Authenticate头的响应,指明访问所请求的资源需要证书。但是和基础认证发送以Base 64编码的用户名和密码不同,在摘要认证中服务器让客户选一个随机数(称作”nonce“),然后浏览器使用一个单向的加密函数生成一个消息摘要(message digest),该摘要是关于用户名、密码、给定的nonce值、HTTP方法,以及所请求的URL。
二、摘要认证流程
客户端首次向服务器发送HTTP请求,服务器返回401(未授权)响应进行挑战。401消息的头里带有WWW-Authenticate消息头,其中包含挑战摘要的随机参数nonce。客户端收到401后,将用户名密码和挑战信息用MD5加密形成认证鉴权头,重新发送给服务器,服务器对认证鉴权头进行验证,如果认证成功则返回200 OK,并在响应的消息中返回下次认证的随机数nextnonce,客户端下次请求时,根据nextnonce生成鉴权头进行HTTP请求。
服务器的401未授权挑战WWW-Authenticate消息头语法:
challenge =“Digest” digest-challenge
digest-challenge=1#( realm | [ domain ] | nonce |
[ opaque ] |[ stale ] | [ algorithm ] |
[ qop-options ] | [auth-param] )
domain =“domain" “=” <“> URI ( 1*SP URI ) <”>
URI =absoluteURI | abs_path
nonce =“nonce" “=” nonce-value
nonce-value =quoted-string
opaque =“opaque" “=” quoted-string
stale =“stale" “=” ( “true” | “false” )
algorithm =“algorithm" “=” ( “MD5” | “MD5-sess” |
token )
qop-options =“qop" “=” <“> 1#qop-value <”>
qop-value =“auth" | “auth-int” | token
参数说明:
- realm:用户域。由服务端告知
- qop:保护质量。auth:鉴权,不对消息体做完整性验证。auth-int:鉴权并需要对消息体做摘要,保证消息完整性。
- nonce:摘要质询参数。401响应中唯一生成的字符串数据。采用十六进制数据。
- opaque:会话标识。由服务器指定,客户端须在请求二中返回该数据。采用十六进制数据。
客户端的挑战响应消息Authorization头参数一下为String类型:
- username:用户名。必填。
- realm:用户域。由服务方告知
- nonce:摘要质询参数。返回请求一响应中的参数nonce
- uri:访问路径。请求的URI
- qop:保护质量。auth:鉴权,不对消息体做完整性验证。auth-int:鉴权并需要对消息体做摘要,保证消息完整性。注册过程使用auth
- nc:nonce计数参数。客户端请求的十六进制计数,以00000001开始,每次请求加1,目的是防止重放攻击。
- cnonce:客户端nonce值。客户端用来鉴定服务器的摘要质询参数
- response:响应值。对请求一中401响应的参数采用MD5算法做摘要计算的结果
- opaque:会话标识。返回服务器原值
response参数的算法:
response=<“> < KD ( H(A1), unq(nonce-value) “:” nc-value “:”
unq(cnonce-value) “:” unq(qop-value) “:” H(A2) ) <”>
A1=unq(username-value) “:” unq(realm-value) “:” passwd
如果qop等于auth,A2=Method “:” digest-uri-value 如果qop等于auth-int,A2=Method
“:” digest-uri-value “:” H(entity-body) 其中: H(data)=MD5(data)。
KD(secret, data)=H(concat(secret, “:”, data))。 unq(X)代表去掉X前后的引号。
Method=GET或者POST。 entity-body代表HTTP请求的消息体。 passwd=key。
三、代码实现HTTP Digest接入方式鉴权认证
以下是初次接触摘要认证时的简要请求方法。
摘要认证过程也可用Springorg.springframework.web.client.RestTemplate实现,修改配置类中的RestTemplate生成方法。具体方法自行百度。
public static String sendPostRequest(String username,String password,String uri, String jsonParams){
CloseableHttpClient httpClient = null;
String result =null;
String requestUrl=url+uri;
try {
CredentialsProvider credsProvider = new BasicCredentialsProvider();
httpClient = HttpClients.custom().setDefaultCredentialsProvider(credsProvider).build();
//第一次请求
HttpPost post = new HttpPost(requestUrl);
// 构造消息头
post.addHeader("Content-type","application/json; charset=utf-8");
post.setHeader("Accept", "application/json");
HttpResponse firstResponse = httpClient.execute(post);
org.apache.http.HttpEntity firstEntity = null;
firstEntity = firstResponse.getEntity();
result = EntityUtils.toString(firstEntity, "UTF-8");
Map<String,String> map =new HashMap<String,String>();
StringEntity stringEntity = new StringEntity(jsonParams);
//解析头部
Header[] h=firstResponse.getHeaders("WWW-Authenticate");
StringBuffer auth=new StringBuffer(h[0].toString());
String realm=auth.substring(auth.indexOf("realm=")+6,auth.indexOf("qop=")-1);
String qop=auth.substring(auth.indexOf("qop=")+4,auth.indexOf("nonce=")-1);
String nonce=auth.substring(auth.indexOf("nonce=")+6,auth.indexOf("opaque=")-1);
String opaque=auth.substring(auth.indexOf("opaque=")+7);
String cnonce="00000001";
String nc="00000001";
String algorithm="MD5";
String responseString;
String params1=username+":"+realm+":"+password;
String params2="POST:"+uri;
String params3= SecureUtil.md5(params1)+":"+nonce+":"+nc+":"+cnonce+":"+qop+":"+SecureUtil.md5(params2);
responseString=SecureUtil.md5(params3);
StringBuilder sb=new StringBuilder();
String Authorization ="Digest username=" + username +",realm="+ realm +",nonce=" + nonce +",uri=" + uri +
",response=" + responseString + ",cnonce=" +cnonce + ",qop=" +qop+ ",nc="+nc;
//第二次请求
// 构造消息头
post.setHeader("Authorization",Authorization);
post.setEntity(stringEntity);
HttpResponse secondResponse = httpClient.execute(post);
org.apache.http.HttpEntity secondEntity = null;
secondEntity = secondResponse.getEntity();
result = EntityUtils.toString(secondEntity, "UTF-8");
httpClient.close();
} catch (Exception e) {
log.error(e.getMessage());
} finally {
if (httpClient != null) {
try {
httpClient.close();
} catch (IOException e) {
log.error(e.getMessage());
}
}
}
return result;
}