bigywater.tech
Java · 364

Authenticator:集中处理网络认证挑战

遇到 401 或 407 时 Java 会怎么处理?谁来提供口令、会重试几次、凭证被缓存多久、全局设置会影响谁?这篇用本机的真实 HTTP 服务逐项验证。

20 vs 4
错误口令时 Authenticator 被调用的次数:HttpURLConnection(之后返回 401)与 HttpClient(之后抛 IOException,限制 3 次重试)
MD5 被拒绝
JDK 21 默认拒绝 algorithm=MD5 的 Digest 认证(Authenticator 仍被调用,但不发送凭证)
HttpClient 不用
Authenticator.setDefault 对 java.net.http.HttpClient 无效,需要 HttpClient.Builder.authenticator

HttpURLConnection + Authenticator

实测 · 本机执行输出
  [urlconn]
    没有设置 Authenticator:401
          服务端记录:GET /secret → 无凭证 → 401
    Authenticator 返回正确口令:200;getPasswordAuthentication 被调用 1 次
        请求者信息:type=SERVER,scheme=basic,prompt(realm)=realm-b,host=127.0.0.1,port>0=true,protocol=http,site=<addr>,url=/secret,thread=main
          服务端记录:GET /secret → 无凭证 → 401
          服务端记录:GET /secret → Basic 凭证正确 → 200
    同一个 URL 再请求一次:200;累计调用 1 次
          服务端记录:GET /secret → Basic 凭证正确 → 200
    同一主机另一个路径 /other/x:200;累计调用 1 次
          服务端记录:GET /other/x → Basic 凭证正确 → 200
    (新服务)Authenticator 一直返回错误口令:最终状态 401;getPasswordAuthentication 被调用 20 次
          服务端记录:GET /secret → 无凭证 → 401
          服务端记录:GET /secret → Basic 凭证错误 → 401
          服务端记录:GET /secret → Basic 凭证错误 → 401
          服务端记录:GET /secret → Basic 凭证错误 → 401
          服务端记录:GET /secret → Basic 凭证错误 → 401
          服务端记录:GET /secret → Basic 凭证错误 → 401
          服务端记录:GET /secret → Basic 凭证错误 → 401
          服务端记录:GET /secret → Basic 凭证错误 → 401
          服务端记录:GET /secret → Basic 凭证错误 → 401
          服务端记录:GET /secret → Basic 凭证错误 → 401
          服务端记录:GET /secret → Basic 凭证错误 → 401
          服务端记录:GET /secret → Basic 凭证错误 → 401
          服务端记录:GET /secret → Basic 凭证错误 → 401
          服务端记录:GET /secret → Basic 凭证错误 → 401
          服务端记录:GET /secret → Basic 凭证错误 → 401
          服务端记录:GET /secret → Basic 凭证错误 → 401
          服务端记录:GET /secret → Basic 凭证错误 → 401
          服务端记录:GET /secret → Basic 凭证错误 → 401
          服务端记录:GET /secret → Basic 凭证错误 → 401
          服务端记录:GET /secret → Basic 凭证错误 → 401
    (新服务)Authenticator 返回 null:最终状态 401;调用 1 次
          服务端记录:GET /secret → 无凭证 → 401
    (新服务)URL 里带 userinfo(amy:s3cret@):401
          服务端记录:GET /userinfo → 无凭证 → 401
    手工设置 Authorization 头:200
          服务端记录:GET /secret4 → Basic 凭证正确 → 200
    (新服务)先用正确口令认证成功,再换成返回错误口令的 Authenticator,请求 /b:200
          服务端记录:GET /a → 无凭证 → 401
          服务端记录:GET /a → Basic 凭证正确 → 200
          服务端记录:GET /b → Basic 凭证正确 → 200

HttpClient + authenticator

实测 · 本机执行输出
  [client]
    HttpClient(authenticator):200;调用 1 次;请求者:type=SERVER,scheme=basic,prompt(realm)=realm-b,host=127.0.0.1,port>0=true,protocol=http,site=null,url=/secret,thread=HttpClient-N-Worker-N
          服务端记录:GET /secret → 无凭证 → 401
          服务端记录:GET /secret → Basic 凭证正确 → 200
    同一个 HttpClient 同一个 URL 再请求:200;累计调用 1 次
          服务端记录:GET /secret → Basic 凭证正确 → 200
    同一个 HttpClient 更深的路径 /secret/sub/deeper:200;累计调用 1 次
          服务端记录:GET /secret/sub/deeper → Basic 凭证正确 → 200
    同一个 HttpClient 兄弟路径 /elsewhere:200;累计调用 1 次
          服务端记录:GET /elsewhere → Basic 凭证正确 → 200
    没有 authenticator 的 HttpClient:401
          服务端记录:GET /secret → 无凭证 → 401
    只有 Authenticator.setDefault、HttpClient 没设 authenticator:401(HttpClient 不使用全局默认 Authenticator)
          服务端记录:GET /secret → 无凭证 → 401
    手工加 Authorization 头(预先认证):200
          服务端记录:GET /secret → Basic 凭证正确 → 200
    sendAsync 时 getPasswordAuthentication 所在线程:HttpClient-N-Worker-N
    (新服务)authenticator 一直返回错误口令:IOException: too many authentication attempts. Limit: 3;调用 4 次;耗时 4 ms
          服务端记录:GET /x → 无凭证 → 401
          服务端记录:GET /x → Basic 凭证错误 → 401
          服务端记录:GET /x → Basic 凭证错误 → 401
          服务端记录:GET /x → Basic 凭证错误 → 401
          服务端记录:GET /x → Basic 凭证错误 → 401
    (新服务)authenticator 返回 null:IOException: No credentials provided;调用 1 次
          服务端记录:GET /x → 无凭证 → 401

Digest 认证

实测 · 本机执行输出
  [digest]
    HttpURLConnection + Digest(服务端 algorithm=MD5):401;Authenticator 调用 1 次;请求者:type=SERVER,scheme=digest,prompt(realm)=realm-d,host=127.0.0.1,port>0=true,protocol=http,site=<addr>,url=/d1,thread=main
          服务端记录:GET /d1 → 无凭证 → 401
        HttpClient + 同一个 Digest 服务:401;authenticator 调用 0 次
          服务端记录:GET /hc → 无凭证 → 401
    HttpURLConnection + Digest(服务端 algorithm=SHA-256):200;Authenticator 调用 1 次;请求者:type=SERVER,scheme=digest,prompt(realm)=realm-d,host=127.0.0.1,port>0=true,protocol=http,site=<addr>,url=/d1,thread=main
          服务端记录:GET /d1 → 无凭证 → 401
          服务端记录:GET /d1 → Digest 凭证(algorithm=SHA-256)正确(nc=00000001) → 200
        同一服务再请求 /d2:200;累计调用 1 次
          服务端记录:GET /d2 → 无凭证 → 401
          服务端记录:GET /d2 → Digest 凭证(algorithm=SHA-256)正确(nc=00000002) → 200
        再请求 /d3:200
          服务端记录:GET /d3 → 无凭证 → 401
          服务端记录:GET /d3 → Digest 凭证(algorithm=SHA-256)正确(nc=00000003) → 200
        HttpClient + 同一个 Digest 服务:401;authenticator 调用 0 次
          服务端记录:GET /hc → 无凭证 → 401
  Oct 04, 2026 3:50:08 AM sun.net.www.protocol.http.DigestAuthentication log
  INFO: Rejecting digest authentication with insecure algorithm: MD5 This constraint may be relaxed by setting the "http.auth.digest.reEnabledAlgorithms" system property.
  [digest](JVM 参数 -Dhttp.auth.digest.reEnabledAlgorithms=MD5)
    HttpURLConnection + Digest(服务端 algorithm=MD5):200;Authenticator 调用 1 次;请求者:type=SERVER,scheme=digest,prompt(realm)=realm-d,host=127.0.0.1,port>0=true,protocol=http,site=<addr>,url=/d1,thread=main
          服务端记录:GET /d1 → 无凭证 → 401
          服务端记录:GET /d1 → Digest 凭证(algorithm=MD5)正确(nc=00000001) → 200
        HttpClient + 同一个 Digest 服务:401;authenticator 调用 0 次
          服务端记录:GET /hc → 无凭证 → 401
    HttpURLConnection + Digest(服务端 algorithm=SHA-256):200;Authenticator 调用 1 次;请求者:type=SERVER,scheme=digest,prompt(realm)=realm-d,host=127.0.0.1,port>0=true,protocol=http,site=<addr>,url=/d1,thread=main
          服务端记录:GET /d1 → 无凭证 → 401
          服务端记录:GET /d1 → Digest 凭证(algorithm=SHA-256)正确(nc=00000001) → 200
        同一服务再请求 /d2:200;累计调用 1 次
          服务端记录:GET /d2 → 无凭证 → 401
          服务端记录:GET /d2 → Digest 凭证(algorithm=SHA-256)正确(nc=00000002) → 200
        再请求 /d3:200
          服务端记录:GET /d3 → 无凭证 → 401
          服务端记录:GET /d3 → Digest 凭证(algorithm=SHA-256)正确(nc=00000003) → 200
        HttpClient + 同一个 Digest 服务:401;authenticator 调用 0 次
          服务端记录:GET /hc → 无凭证 → 401

代理认证

实测 · 本机执行输出
  [proxy]
    经代理、没有 Authenticator:407
          服务端记录:代理收到 GET http://127.0.0.1:62395/via-proxy,Proxy-Authorization = 无 → 407
    经代理、Authenticator 提供代理口令:200;调用 1 次;请求者:type=PROXY,scheme=basic,prompt(realm)=proxy-realm,host=127.0.0.1,port>0=true,protocol=http,site=<addr>,url=/via-proxy2,thread=main
          服务端记录:代理收到 GET http://127.0.0.1:62395/via-proxy2,Proxy-Authorization = 无 → 407
          服务端记录:代理收到 GET http://127.0.0.1:62395/via-proxy2,Proxy-Authorization = 正确 → 转发
          服务端记录:目标服务器收到 GET /via-proxy2,Proxy-Authorization = null
    按 RequestorType 区分(PROXY 返回代理口令,SERVER 返回别的)请求 /via-proxy3:200
          服务端记录:代理收到 GET http://127.0.0.1:62395/via-proxy3,Proxy-Authorization = 正确 → 转发
          服务端记录:目标服务器收到 GET /via-proxy3,Proxy-Authorization = null
    HttpClient 经代理 + authenticator:200;调用 1 次;请求者:type=PROXY,scheme=basic,prompt(realm)=proxy-realm,host=127.0.0.1,port>0=true,protocol=http,site=null,url=/via-proxy4,thread=HttpClient-N-Worker-N
          服务端记录:代理收到 GET http://127.0.0.1:62395/via-proxy4,Proxy-Authorization = 无 → 407
          服务端记录:代理收到 GET http://127.0.0.1:62395/via-proxy4,Proxy-Authorization = 正确 → 转发
          服务端记录:目标服务器收到 GET /via-proxy4,Proxy-Authorization = null
    HttpClient 第二次经代理请求:200;累计调用 1 次
          服务端记录:代理收到 GET http://127.0.0.1:62395/via-proxy5,Proxy-Authorization = 正确 → 转发
          服务端记录:目标服务器收到 GET /via-proxy5,Proxy-Authorization = null

作用范围

实测 · 本机执行输出
  [scope]
    全局 Authenticator 按端口返回不同口令:服务 1 → 200;服务 2 → 200
          服务端记录:GET /a → 无凭证 → 401
          服务端记录:GET /a → Basic 凭证正确 → 200
          服务端记录:GET /b → 无凭证 → 401
          服务端记录:GET /b → Basic 凭证正确 → 200
    两个线程并发访问两个服务(路径 /t 与之前同主机,可能已有缓存):200 / 200(全局 Authenticator 对所有线程生效)
    两个 HttpClient 各带自己的 authenticator:200 / 200
    用 c1 的 authenticator(amy)访问服务 6(要 bob):IOException: too many authentication attempts. Limit: 3
          服务端记录:GET /d → 无凭证 → 401
          服务端记录:GET /d → Basic 凭证错误 → 401
          服务端记录:GET /d → Basic 凭证错误 → 401
          服务端记录:GET /d → Basic 凭证错误 → 401
          服务端记录:GET /d → Basic 凭证错误 → 401
    静态方法 Authenticator.requestPasswordAuthentication(JDK 9 起 deprecated):x
    Authenticator.getDefault()(JDK 14+)= null

分步演示

Authenticator
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12;服务端与代理是本实验用 com.sun.net.httpserver 写的,Digest 的服务端按 RFC 2617(qop=auth)自行实现,不是真实的生产服务器
    • 没有验证:HTTPS 隧道(CONNECT)时的 Basic 认证(jdk.http.auth.tunneling.disabledSchemes 默认禁用 Basic)、NTLM 与 Negotiate(需要域环境)、jdk.http.auth.proxying.disabledSchemes、HttpClient 的 jdk.httpclient.auth.retrylimit 属性调整、Authenticator 在 HTTP/2 下的行为、凭证缓存的失效时间(只观察到进程内持续有效)
    京ICP备2026067502号-1京公网安备11010502063487号