通用Digest认证

一开始写的时候没有从okhttp库中找到对应的方法,但是在梳理文章时发现了io.github.rburgst:okhttp-digest,大家如果不想手撸,可以直接用该库,以下是手撸代码。

简介

Digest认证采用**挑战-响应(Challenge-Response)**机制,具体流程如下:

  1. 服务器质询 :当客户端请求受保护的资源时,服务器会返回一个401 Unauthorized状态码,并在WWW-Authenticate响应头中附带质询信息。这些信息通常包括:
    • realm:认证域,提示用户使用哪个区域的凭证。
    • nonce:一个服务器生成的、每次质询都不同的随机数,是防重放攻击的关键。
    • qop(质量保护):可选参数,指定保护质量,如auth(认证)或auth-int(认证带报文完整性)。
    • algorithm:指定摘要算法,如MD5、SHA-256等。
  2. 客户端响应 :客户端收到质询后,会提示用户输入用户名和密码。然后,客户端利用这些信息以及服务器质询中的参数,按照特定算法计算一个响应摘要(response digest) 。这个摘要通常会用到用户名、密码、realm、HTTP方法、请求的URI以及nonce等参数。计算完毕后,客户端会在后续请求的Authorization头中带上这个摘要和其他相关参数发给服务器。
  3. 服务器验证 :服务器收到客户端的响应后,会根据存储的对应用户密码,使用相同的算法和参数计算期望的摘要值。若计算出的摘要与客户端传来的response值匹配,则认证通过。

实现逻辑

  1. 首先请求一次该接口
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
    }
}
相关推荐
城东米粉儿38 分钟前
Kotlin @JvmOverLoads 笔记
android
alexhilton1 小时前
把离线AI代理装进口袋里
android·kotlin·android jetpack
哈哈浩丶1 小时前
ATF (ARM Trusted Firmware) -2:完整启动流程(冷启动)
android·linux·arm开发·驱动开发
哈哈浩丶1 小时前
ATF (ARM Trusted Firmware) -3:完整启动流程(热启动)
android·linux·arm开发
哈哈浩丶1 小时前
OP-TEE-OS:综述
android·linux·驱动开发
恋猫de小郭11 小时前
你是不是觉得 R8 很讨厌,但 Android 为什么选择 R8 ?也许你对 R8 还不够了解
android·前端·flutter
城东米粉儿13 小时前
Android Glide 笔记
android
城东米粉儿13 小时前
Android TheRouter 笔记
android
城东米粉儿19 小时前
Android AIDL 笔记
android
城东米粉儿19 小时前
Android 进程间传递大数据 笔记
android