一开始写的时候没有从okhttp库中找到对应的方法,但是在梳理文章时发现了
io.github.rburgst:okhttp-digest,大家如果不想手撸,可以直接用该库,以下是手撸代码。
简介
Digest认证采用**挑战-响应(Challenge-Response)**机制,具体流程如下:
- 服务器质询 :当客户端请求受保护的资源时,服务器会返回一个
401 Unauthorized状态码,并在WWW-Authenticate响应头中附带质询信息。这些信息通常包括:realm:认证域,提示用户使用哪个区域的凭证。nonce:一个服务器生成的、每次质询都不同的随机数,是防重放攻击的关键。qop(质量保护):可选参数,指定保护质量,如auth(认证)或auth-int(认证带报文完整性)。algorithm:指定摘要算法,如MD5、SHA-256等。
- 客户端响应 :客户端收到质询后,会提示用户输入用户名和密码。然后,客户端利用这些信息以及服务器质询中的参数,按照特定算法计算一个响应摘要(response digest) 。这个摘要通常会用到用户名、密码、realm、HTTP方法、请求的URI以及nonce等参数。计算完毕后,客户端会在后续请求的
Authorization头中带上这个摘要和其他相关参数发给服务器。 - 服务器验证 :服务器收到客户端的响应后,会根据存储的对应用户密码,使用相同的算法和参数计算期望的摘要值。若计算出的摘要与客户端传来的
response值匹配,则认证通过。
实现逻辑
- 首先请求一次该接口
kotlin
/**
* Digest认证的请求方法
* @param urlStr 请求URL
* @param username 用户名
* @param password 密码
* @param requestBody 请求体内容
* @return HttpURLConnection连接对象,调用者需负责关闭连接
*/
private fun urlWithDigestAuth(method: String, urlStr: String, username: String, password: String, requestBody: String? = null): HttpURLConnection? {
val url = URL(urlStr)
var connection: HttpURLConnection? = null
try {
// 第一次请求,获取401响应和认证信息
connection = url.openConnection() as HttpURLConnection
connection.requestMethod = method
connection.setRequestProperty("Host", url.host ?: "")
connection.setRequestProperty("Connection", "Keep-Alive")
// 发送初始请求
val responseCode = connection.responseCode
Log.d(TAG, "Initial response code: $responseCode")
if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
// 获取WWW-Authenticate头
val authHeader = connection.getHeaderField("WWW-Authenticate")
Log.d(TAG, "WWW-Authenticate header: $authHeader")
if (authHeader != null) {
// 解析认证信息
val authInfo = parseDigestAuthHeader(authHeader)
if (authInfo.isNotEmpty()) {
// 关闭当前连接
connection.disconnect()
// 第二次请求,使用Digest认证
connection = url.openConnection() as HttpURLConnection
connection.requestMethod = method
connection.setRequestProperty("Host", url.host)
connection.setRequestProperty("Connection", "keep-alive")
// 设置请求体
if (requestBody != null) {
connection.doOutput = true
connection.setRequestProperty("Content-Type", "application/json")
connection.setRequestProperty("Content-Length", requestBody.length.toString())
} else {
connection.doInput = true
}
// 生成Authorization头
val authorizationHeader = generateAuthorizationHeader(
method,
urlStr,
username,
password,
authInfo
)
connection.setRequestProperty("Authorization", authorizationHeader)
Log.d(TAG, "Authorization header: $authorizationHeader")
// 发送请求
if (requestBody != null) {
val outputStream: OutputStream = connection.outputStream
outputStream.write(requestBody.toByteArray())
outputStream.flush()
outputStream.close()
// 只有在发送了请求体后才获取响应码
val finalResponseCode = connection.responseCode
Log.d(TAG, "Final response code: $finalResponseCode")
} else {
// 如果没有请求体,不获取响应码,保留连接用于后续写入
Log.d(TAG, "No request body, keeping connection for later use")
}
// 返回连接对象,调用者负责关闭
return connection
}
}
}
// 返回连接对象,调用者负责关闭
return connection
} catch (e: Exception) {
Log.e(TAG, "Error in $method request: ${e.message}", e)
// 发生异常时关闭连接
connection?.disconnect()
return null
}
}