通用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 自定义 View 完全指南
android·自定义·自定义view·viewgroup
2601_949833394 小时前
flutter_for_openharmony口腔护理app实战+意见反馈实现
android·javascript·flutter
峥嵘life5 小时前
Android 16 EDLA测试STS模块
android·大数据·linux·学习
TheNextByte15 小时前
如何打印Android手机联系人?
android·智能手机
泡泡以安6 小时前
Android 逆向实战:从零突破某电商 App 登录接口全参数加密
android·爬虫·安卓逆向
2501_944525547 小时前
Flutter for OpenHarmony 个人理财管理App实战 - 预算详情页面
android·开发语言·前端·javascript·flutter·ecmascript
清蒸鳜鱼8 小时前
【Mobile Agent——Droidrun】MacOS+Android配置、使用指南
android·macos·mobileagent
2501_915918418 小时前
HTTPS 代理失效,启用双向认证(mTLS)的 iOS 应用网络怎么抓包调试
android·网络·ios·小程序·https·uni-app·iphone
峥嵘life8 小时前
Android EDLA CTS、GTS等各项测试命令汇总
android·学习·elasticsearch