通用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
    }
}
相关推荐
恋猫de小郭几秒前
Flutter Android 改动:APK 不再允许通过 Intent 动态修改 Engine 参数
android·前端·flutter
修船大队长1 小时前
KL15 高电平请求 FullCom:AUTOSAR 项目调用流程
android·java·服务器
三少爷的鞋1 小时前
毁掉 Android 现代架构,只需要一个自定义的“FlowBus”
android
jack1868913 小时前
安卓手机忘记锁屏密码怎么办? 无需密码也可解锁
android·智能手机
应用市场16 小时前
不用一张图片的像素小游戏:小人是字符串,房间是矩形,平衡靠两千个假玩家
android·kotlin·游戏开发
stevenzqzq17 小时前
android 对渠道的理解
android
QEasycloud17 小时前
BOM接口抛转失败钉钉通知策略实战教程:MySQL异常日志到钉钉消息的端到端配置
android·mysql·钉钉
AIsoft_868917 小时前
医患交流内容如何做好记录?
android·iphone
蒸鱼Yuzheng17 小时前
Android Native 崩溃证据链:Tombstone、Build ID 与 Trace 如何联合归因
android·ndk·tombstone·调试符号·崩溃分析
事圆则缓18 小时前
Kotlin 变量与函数:把页面状态和行为写成清楚的契约
android·开发语言·kotlin