hkws提供的demo中的HTTPClientUtil发起的GET/POST/PUT请求都是只发送一次请求,面对Digest Auth永远只能收到401权限不足的反馈,所以需要修改GET/POST/PUT请求的发送方式,以doPut为例:
```
/**
* 执行带 Digest 认证的 PUT 请求
*/
public static String doPut(DeviceInfoDTO deviceInfo, String url, String xmlBody,String ContentType) {
String fullUrl = "http://" + deviceInfo.getDevIp() + url;
String response = "";
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
// 第一次尝试:发送不带认证的请求以获取挑战信息
HttpPut httpPut = new HttpPut(fullUrl);
setCommonHeaders(httpPut, deviceInfo,ContentType);
httpPut.setEntity(new StringEntity(xmlBody, StandardCharsets.UTF_8));
try (CloseableHttpResponse firstResponse = httpClient.execute(httpPut)) {
int statusCode = firstResponse.getStatusLine().getStatusCode();
if (statusCode == 401) {
// 提取 WWW-Authenticate 头中的 realm 和 nonce
String wwwAuth = firstResponse.getFirstHeader("WWW-Authenticate").getValue();
DigestChallenge challenge = parseDigestChallenge(wwwAuth);
if (challenge != null) {
// 第二次尝试:使用 Digest 认证重新发送请求
HttpPut authPut = new HttpPut(fullUrl);
setCommonHeaders(authPut, deviceInfo,ContentType);
authPut.setEntity(new StringEntity(xmlBody, StandardCharsets.UTF_8));
// 计算并设置 Digest 认证头
String authHeader = calculateDigestAuthHeader(
deviceInfo.getUsername(),
deviceInfo.getPassword(),
"PUT",
url,
challenge
);
authPut.setHeader("Authorization", authHeader);
try (CloseableHttpResponse authResponse = httpClient.execute(authPut)) {
if (authResponse.getEntity() != null) {
response = EntityUtils.toString(authResponse.getEntity());
System.out.println("认证后响应状态: " + authResponse.getStatusLine());
System.out.println("响应内容: " + response);
}
}
}
} else if (statusCode == 200) {
// 直接成功(不需要认证)
if (firstResponse.getEntity() != null) {
response = EntityUtils.toString(firstResponse.getEntity());
}
} else {
System.out.println("Unexpected status: " + statusCode);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return response;
}
```
另附
```
```
/**
* 设置通用的请求头
*/
private static void setCommonHeaders(HttpPut httpPut, DeviceInfoDTO deviceInfo,String ContentType) {
httpPut.setHeader("Content-Type", ContentType);
httpPut.setHeader("Accept", "*/*");
httpPut.setHeader("Host", deviceInfo.getDevIp());
httpPut.setHeader("Connection", "keep-alive");
httpPut.setHeader("User-Agent", "HttpClient/4.5.2");
}
/**
* 设置通用的请求头
*/
private static void setCommonHeaders(HttpRequestBase request, DeviceInfoDTO deviceInfo) {
request.setHeader("Accept", "*/*");
request.setHeader("Host", deviceInfo.getDevIp());
request.setHeader("Connection", "keep-alive");
request.setHeader("User-Agent", "HttpClient/4.5.2");
// 根据请求类型设置 Content-Type
if (request instanceof HttpPut || request instanceof HttpPost) {
request.setHeader("Content-Type", "application/xml");
}
}
/**
* 解析 Digest 认证挑战
*/
private static DigestChallenge parseDigestChallenge(String wwwAuthenticate) {
Pattern realmPattern = Pattern.compile("realm="([^"]+)"");
Pattern noncePattern = Pattern.compile("nonce="([^"]+)"");
Pattern algorithmPattern = Pattern.compile("algorithm=([^,]+)");
Pattern qopPattern = Pattern.compile("qop="([^"]+)"");
Matcher realmMatcher = realmPattern.matcher(wwwAuthenticate);
Matcher nonceMatcher = noncePattern.matcher(wwwAuthenticate);
Matcher algorithmMatcher = algorithmPattern.matcher(wwwAuthenticate);
Matcher qopMatcher = qopPattern.matcher(wwwAuthenticate);
if (realmMatcher.find() && nonceMatcher.find()) {
DigestChallenge challenge = new DigestChallenge();
challenge.realm = realmMatcher.group(1);
challenge.nonce = nonceMatcher.group(1);
challenge.algorithm = algorithmMatcher.find() ? algorithmMatcher.group(1) : "MD5";
challenge.qop = qopMatcher.find() ? qopMatcher.group(1) : "auth";
return challenge;
}
return null;
}
/**
* 计算 Digest 认证头
*/
private static String calculateDigestAuthHeader(String username, String password,
String method, String uri,
DigestChallenge challenge) {
// 生成客户端随机数和计数器
String cnonce = generateCnonce();
String nc = "00000001"; // 每次请求递增
try {
// HA1 = MD5(username:realm:password)
String ha1 = md5(username + ":" + challenge.realm + ":" + password);
// HA2 = MD5(method:uri)
String ha2 = md5(method + ":" + uri);
// response = MD5(HA1:nonce:nc:cnonce:qop:HA2)
String response = md5(ha1 + ":" + challenge.nonce + ":" + nc + ":" + cnonce + ":" + challenge.qop + ":" + ha2);
// 构建 Authorization 头
return String.format("Digest username="%s", realm="%s", nonce="%s", " +
"uri="%s", algorithm="%s", qop="%s", nc=%s, " +
"cnonce="%s", response="%s"",
username, challenge.realm, challenge.nonce, uri,
challenge.algorithm, challenge.qop, nc, cnonce, response);
} catch (Exception e) {
throw new RuntimeException("Failed to calculate digest auth", e);
}
}
/**
* 生成客户端随机数
*/
private static String generateCnonce() {
// 使用当前时间戳和随机数生成更安全的 cnonce
long timestamp = System.currentTimeMillis();
int random = (int) (Math.random() * 1000000);
return md5(timestamp + ":" + random).substring(0, 16);
}
/**
* MD5 计算
*/
private static String md5(String input) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8));
return HexFormat.of().formatHex(digest);
} catch (Exception e) {
throw new RuntimeException("MD5 calculation failed", e);
}
}
/**
* Digest 挑战信息容器类
*/
static class DigestChallenge {
String realm;
String nonce;
String algorithm;
String qop;
}
```
```