通用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
    }
}
相关推荐
数智工坊3 小时前
机器人运动控制:采样、优化与学习三大流派深度对比与实战
android·学习·机器人
故渊at5 小时前
第二板块:Android 四大组件标准化学理 | 第八篇:Service 后台执行实体与优先级
android·gitee·service·前台服务·后台服务
会Tk矩阵群控的小木5 小时前
安卓群控系统对于游戏工作室实战教程
android·运维·游戏·adb·开源软件·个人开发
qeen876 小时前
【C++】类与对象之类的默认成员函数(二)
android·c语言·开发语言·c++·笔记·学习
故渊at6 小时前
第二板块:Android 四大组件标准化学理 | 第九篇:BroadcastReceiver 事件分发与有序广播
android·gitee·broadcast·广播·动态注册·静态注册
JohnnyDeng946 小时前
【Android】Room 数据库高级用法与性能调优:从查询瓶颈到毫秒级响应
android·性能优化·kotlin·room
zeqinjie7 小时前
Flutter 折叠屏 iPad / 宽屏适配实践
android·前端·flutter
ab_dg_dp7 小时前
Android 17+ 提取 AIDL 生成 Java 文件的实用脚本
android·java·python
Arrom7 小时前
DLNA 渲染端排障实战:从 20s 卡顿到 stale subscriber 的两周追凶之旅
android·java