通用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
    }
}
相关推荐
一只特立独行的Yang7 小时前
Android graphics - 框架摘要
android
AC赳赳老秦9 小时前
DeepSeek优化多智能体指令:避免协同冲突,提升自动化流程稳定性
android·大数据·运维·人工智能·自然语言处理·自动化·deepseek
峥嵘life12 小时前
Android16 【CTS】CtsWindowManagerDeviceAnimations存在fail项
android·linux·学习
阿拉斯攀登13 小时前
第 7 篇 安卓驱动开发的灵魂:字符设备驱动框架,从原理到最简实战
android·驱动开发·rk3568·嵌入式驱动·安卓驱动
阿拉斯攀登13 小时前
第 1 篇 入坑不亏!瑞芯微 RK 平台 + 安卓驱动开发,小白全维度扫盲
android·驱动开发·rk3568·嵌入式驱动
Android系统攻城狮13 小时前
Android tinyalsa深度解析之pcm_params_get调用流程与实战(一百六十二)
android·pcm·tinyalsa·android hal·audio hal
zh路西法14 小时前
【C语言简明教程提纲】(四):结构体与文件定义和操作
android·c语言·redis
常利兵14 小时前
Jetpack Compose 1.8 新特性来袭,打造丝滑开发体验
android
牢七14 小时前
百家cms 审计 未完成
android·ide·android studio
hjxu201614 小时前
【 MySQL 速记5】插入
android·数据库·mysql