HTTPS代理端点
Litport的每个HTTP代理端点在同一主机和端口上也接受TLS。将代理URL中的协议从http://改为https://,客户端与节点之间的连接即被加密。凭证、端口和目标均保持不变。TLS开销计入流量(见成本),握手失败会直接关闭连接,没有可供检查的X-Proxy-Error-Code(见确认TLS确实在使用)。
| 协议 | 客户端到节点 | 示例 |
|---|---|---|
http:// |
明文 | http:// |
https:// |
TLS | https:// |
PORT是控制台中为您的令牌显示的HTTP代理端口(默认为1337和31337;见代理入口)。SOCKS5端点没有TLS变体。
选择安全级别#
与代理通信有三种方式。选择与客户端运行位置相符的一种;本页其余部分介绍如何设置每一种。
| 级别 | 代理URL与设置 | 被动监听者(Wi-Fi、ISP、共享网络) | 主动攻击者(重定向您的连接) | 客户端支持 |
|---|---|---|---|---|
| 0. 明文 | http://,无需其他设置 |
可读取您的凭证、每个CONNECT主机名以及明文HTTP的正文 |
同左 | 所有客户端 |
| 1. 加密,不验证 | https://,加上客户端的“跳过代理验证”选 |
什么也看不到 | 可冒充节点并截获您的凭证 | 大多数库;不支持浏览器;Requ |
| 2. 加密并验证(推荐) | https://,加上Litport根证书文件 |
什么也看不到 | 验证失败即中止连接:客户端会拒绝 |
所有支持https://代理的客户端 |
建议:使用级别2。它只需要一个公开文件和一个选项,并且是唯一能让可用于消费的凭证同时免受两类攻击者威胁的级别。下载一次根证书,并放在代码旁边:
curl --fail --remote-name https://litport.net/certificates/litport-cache-root.crt
对于快速手动测试,或无法给其提供文件的客户端,级别1是比明文更好的合理选择。级别0只是因为所有客户端都支持才成为默认;只要您与节点之间的网络不属于您,就应改用其他级别。
被称为“HTTPS代理”的两件不同的事#
- 代理HTTPS目标是HTTP代理一直以来的工作方式:客户端发送
CONNECT example.com:443,节点建立隧道,您的TLS会话在隧道内端到端运行。http://代理端点支持这种方式;除非您启用缓存模式,否则隧道内容对Litport不透明。 - HTTPS代理端点加密的是客户端与节点本身之间的这一跳。您发送给代理的所有内容,包括
CONNECT行和您的凭证,都在这个TLS会话内传输。
本页讲的是第二种。两者相互独立:任何目标都可以使用任一协议。
明文端点会暴露什么(级别0)#
使用http://时,客户端与节点之间路径上的任何环节都能读取到:
| 线路上暴露的内容 | 后果 |
|---|---|
Proxy-Authorization请求头 |
每次请求都会暴露您的代理用户名和 |
CONNECT host:port行 |
您连接的每个主机名,即使隧道本身 |
明文http://目标请求 |
完整的请求体和响应体。 |
“路径上”包括您所在的本地网络、您的ISP、共享云网络、企业出口代理,以及任何记录流量的环节。两种情况下,目标站点看到的都一样:所选的出口IP,绝不是您的IP。
何时使用https://#
在以下情况使用HTTPS端点:
- 客户端所在网络不受您控制。公共Wi-Fi下的笔记本电脑、共享办公室、酒店和机场网络,以及任何带有透明检测设备的环境。
- 客户端运行在共享或第三方基础设施上。CI运行器、无服务器函数、共享云VPC,以及运行在您无权管理的主机上的容器。在这些环境中,凭证被截获既不易察觉,又会长期有效。
- 目标列表本身属于敏感信息。即使内容已加密,
CONNECT行也会暴露您在研究哪些主机。 - 您发送的是明文
http://目标请求。否则这些正文在第一跳上可被完整读取。 - 普通代理流量被屏蔽或限速。有些网络会标记非标准端口上的
CONNECT;而指向主机名的TLS看起来就像普通HTTPS。
在以下情况继续使用http://:
- 客户端与节点之间已有私密的加密通路,例如接入您所控制网络的VPN,并且您希望每个连接的开销尽可能小。
- 客户端库根本无法与代理协商TLS(见下文的客户端说明)。
- 您会打开非常多的短连接,而且每个连接额外握手的代价比暴露风险更重要。
成本#
- 每个连接多一次TLS握手。支持keep-alive的客户端每个连接只需握手一次,而不是每个请求一次。
- TLS开销计入流量。记录封装和握手都是经过节点的字节,因此普通(非缓存)请求的计量会略高:小请求通常高出几个百分点,大文件传输则可忽略不计。缓存模式的计量不变。
- 您的客户端必须信任Litport根证书,才能建立到代理的连接。这是下一节的主题,也是
curl -k无济于事的原因。
节点证书,以及-k为什么无效#
级别2意味着验证节点的证书。节点出示的是为其自身主机名(例如hub-eu-1.litport.net)签发的证书,签发者为Litport缓存根证书。该根证书不在操作系统的信任库中,因此如果客户端用默认证书包验证代理证书,就会失败:
curl: (60) SSL certificate problem: unable to get local issuer certificate
curl -k不能解决这个问题。客户端有两条独立的TLS链路,设置也各自独立:
| TLS链路 | 保护对象 | curl选项 |
|---|---|---|
| 客户端到代理 | 您的凭证和CONNECT行 |
--proxy-cacert, --proxy-insecure |
| 客户端到目标(隧道内) | 您正在获取的站点 | --cacert, -k / --insecure |
-k只放宽目标链路的验证。代理链路仍会用系统证书包验证并中止。应改为让代理链路使用Litport根证书:
curl --fail --remote-name https://litport.net/certificates/litport-cache-root.crt
curl --proxy-cacert litport-cache-root.crt \
-x 'https://USER:[email protected]:PORT' \
'https://example.com/'
在更广泛地安装该根证书之前,有两点需要了解:
- 为代理这一跳信任该根证书不会启用缓存模式。缓存模式要求凭证带有
_cache-密码后缀;没有该后缀,隧道保持不透明。 - 同一个根证书也可用于签发缓存模式拦截所用的证书。如果不想把它加入系统级证书库,可以将其限定在使用代理的进程或容器范围内,下面的每个示例都是这样做的。
始终使用节点的主机名,不要使用其IP地址。证书是为主机名签发的。
级别2:已验证的客户端示例#
替换USER、PASSWORD和PORT;凭证中的保留字符需进行百分号编码。所有示例都会用litport-cache-root.crt验证代理证书,并对目标保持常规的公共CA验证。每一节末尾都给出级别1的切换方式,它只跳过代理链路的验证;使用前先阅读级别1,了解其代价。
curl#
已验证(推荐):
curl --proxy-cacert litport-cache-root.crt \
-x 'https://hub-eu-1.litport.net:PORT' --proxy-user 'USER:PASSWORD' \
'https://example.com/'
添加--verbose可以看到两次握手:第一次是与代理(hub-eu-1.litport.net)的握手,然后是其内部的CONNECT,最后是与目标的握手。
未验证,仅用于一次性测试(凭证仍然加密,但客户端无法区分真实节点和冒充者):
curl --proxy-insecure -x 'https://USER:[email protected]:PORT' 'https://example.com/'
--proxy-cacert需要curl 7.52或更高版本,当前所有发行版都已自带。
Python Requests#
Requests对两条链路使用同一个CA证书包,而verify=会替换其默认证书包。先构建一次合并后的证书包,这样目标仍保持公共CA验证:
cat "$(python -c 'import certifi; print(certifi.where())')" litport-cache-root.crt > litport-bundle.pem
import requests
proxy = 'https://USER:[email protected]:PORT'
response = requests.get(
'https://example.com/',
proxies={'http': proxy, 'https': proxy},
verify='litport-bundle.pem',
timeout=30,
)
在http和https两个键下都要列出代理;Requests按目标的协议选择对应条目。通过https://代理实现TLS-in-TLS需要urllib3 1.26或更高版本,Requests 2.26及以上版本依赖它。
Requests没有仅针对代理的开关:verify=False会同时关闭节点和目标两条链路的验证,并且每次请求都会打印警告。仅用于一次性测试。
response = requests.get('https://example.com/', proxies={'http': proxy, 'https': proxy}, verify=False, timeout=30)
Python httpx#
httpx将代理的TLS设置与目标的TLS设置分开:
import ssl
import httpx
context = ssl.create_default_context()
context.load_verify_locations(cafile='litport-cache-root.crt')
proxy = httpx.Proxy('https://USER:[email protected]:PORT', ssl_context=context)
with httpx.Client(proxy=proxy, timeout=30) as client:
response = client.get('https://example.com/')
Proxy上的ssl_context只验证节点;客户端的默认上下文仍会验证目标。要在保持目标验证的同时跳过代理链路,给Proxy传入一个关闭了验证的上下文:
insecure = ssl.create_default_context()
insecure.check_hostname = False
insecure.verify_mode = ssl.CERT_NONE
proxy = httpx.Proxy('https://USER:[email protected]:PORT', ssl_context=insecure)
Node#
https-proxy-agent接受https://代理URL,并使用Node的信任库验证节点,因此需通过NODE_EXTRA_CA_CERTS追加根证书。它不会在任何地方关闭验证。
npm install https-proxy-agent
NODE_EXTRA_CA_CERTS=./litport-cache-root.crt node fetch.mjs
import https from 'node:https'
import { HttpsProxyAgent } from 'https-proxy-agent'
const agent = new HttpsProxyAgent('https://USER:[email protected]:PORT')
https.get('https://example.com/', { agent }, response => response.resume())
对于undici(也是Node 18及更高版本中全局fetch的底层引擎),代理链路有自己的TLS选项,因此不需要环境变量:
import { readFileSync } from 'node:fs'
import { ProxyAgent, fetch } from 'undici'
const dispatcher = new ProxyAgent({
uri: 'https://hub-eu-1.litport.net:PORT',
token: 'Basic ' + Buffer.from('USER:PASSWORD').toString('base64'),
proxyTls: { ca: [readFileSync('litport-cache-root.crt')] },
})
const response = await fetch('https://example.com/', { dispatcher })
这两个库都可以只跳过代理链路。对于https-proxy-agent,第二个参数会传给到节点的TLS连接;对于undici则是proxyTls。目标验证保持开启。不要使用NODE_TLS_REJECT_UNAUTHORIZED=0,它会关闭进程内所有位置的验证。
const agent = new HttpsProxyAgent('https://USER:[email protected]:PORT', { rejectUnauthorized: false })
const dispatcher = new ProxyAgent({ uri: 'https://hub-eu-1.litport.net:PORT', token, proxyTls: { rejectUnauthorized: false } })
Go#
Go的http.Transport拨号https://代理时,使用的TLSClientConfig与访问目标时相同,因此应把根证书追加到系统证书池,而不是替换它:
pool, err := x509.SystemCertPool()
if err != nil || pool == nil { pool = x509.NewCertPool() }
pem, err := os.ReadFile("litport-cache-root.crt")
if err != nil || !pool.AppendCertsFromPEM(pem) { log.Fatal("could not add Litport root") }
proxyURL, err := url.Parse("https://USER:[email protected]:PORT")
if err != nil { log.Fatal(err) }
client := &http.Client{Transport: &http.Transport{
Proxy: http.ProxyURL(proxyURL),
TLSClientConfig: &tls.Config{RootCAs: pool, MinVersion: tls.VersionTLS12},
}}
response, err := client.Get("https://example.com/")
Go会把TLSClientConfig同时用于两条链路,因此InsecureSkipVerify: true也会停止验证目标。没有仅针对代理的开关;应改为把根证书保留在证书池中。
PHP#
curl扩展提供与命令行相同的代理链路选项:
$ch = curl_init('https://example.com/');
curl_setopt_array($ch, [
CURLOPT_PROXY => 'https://hub-eu-1.litport.net:PORT',
CURLOPT_PROXYUSERPWD => 'USER:PASSWORD',
CURLOPT_PROXY_CAINFO => __DIR__ . '/litport-cache-root.crt',
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
要只跳过代理链路,将CURLOPT_PROXY_CAINFO替换为CURLOPT_PROXY_SSL_VERIFYPEER => false和CURLOPT_PROXY_SSL_VERIFYHOST => 0;目标的CURLOPT_SSL_VERIFYPEER保持默认值。
Java#
JDK内置的java.net.http.HttpClient只支持明文代理。使用Apache HttpClient 5,它接受https代理主机,并为JVM提供一个信任库,其中除默认根证书外还包含Litport根证书:
keytool -importcert -noprompt -alias litport-cache -file litport-cache-root.crt \
-keystore ./litport-truststore.p12 -storetype PKCS12 -storepass changeit
java -Djavax.net.ssl.trustStore=./litport-truststore.p12 \
-Djavax.net.ssl.trustStoreType=PKCS12 -Djavax.net.ssl.trustStorePassword=changeit App
HttpHost proxy = new HttpHost("https", "hub-eu-1.litport.net", PORT);
BasicCredentialsProvider credentials = new BasicCredentialsProvider();
credentials.setCredentials(new AuthScope(proxy), new UsernamePasswordCredentials("USER", "PASSWORD".toCharArray()));
try (CloseableHttpClient client = HttpClients.custom()
.setProxy(proxy)
.setDefaultCredentialsProvider(credentials)
.build()) {
client.execute(new HttpGet("https://example.com/"), response -> EntityUtils.toString(response.getEntity()));
}
专用信任库会替换JVM的默认证书包,因此还需要把所需的公共根证书也导入其中,或者将Litport根证书导入JVM的cacerts副本。Java没有仅针对代理的开关:信任所有证书的TrustManager也会停止验证目标,因此应使用信任库。
浏览器与浏览器自动化#
浏览器使用操作系统证书库验证代理证书(Firefox使用自己的证书库)。在所用配置文件对应的证书库中安装根证书,然后让浏览器指向HTTPS代理:
- Chrome和Edge:启动时加上
--proxy-server="https://hub-eu-1.litport.net:PORT",或使用返回HTTPS hub-eu-1.litport.net:PORT的PAC文件。浏览器会提示输入代理凭证。 - Firefox:使用带有相同
HTTPS host:port指令的PAC文件,或在连接设置中将代理类型设为HTTPS。 - Playwright和Puppeteer:传入
server: 'https://hub-eu-1.litport.net:PORT'以及用户名和密码。浏览器的证书库中仍需有该根证书;ignoreHTTPSErrors和--ignore-certificate-errors只影响目标证书。浏览器从不会跳过对代理证书的验证,因此安装根证书是唯一的办法。
各操作系统和Firefox证书库的安装说明见证书页面。
不支持代理TLS的客户端#
Scrapy的默认下载器、JDK的HttpClient以及许多较旧的HTTP库只能与明文代理通信。对这些客户端,要么继续使用http://,要么在127.0.0.1上运行一个小型本地转发器:它接收明文,并通过TLS连接到节点;这样敏感的那一跳就留在本机上。
级别1:加密但不验证#
上面每个已验证的示例都有对应的未验证变体。跳过验证仍会保持这一跳加密,因此网络上的被动监听者依然什么也看不到。但它去掉了“您正在与Litport节点通信”这一保证:能重定向您连接的主动攻击者可以出示任意证书并截获您的凭证。这与级别0面对主动攻击者时的暴露程度相同,因此应把级别1视为比http://更好的一步,而不是等同于级别2。
| 客户端 | 仅代理链路 | 两条链路 |
|---|---|---|
| curl | --proxy-insecure |
--proxy-insecure -k |
| Python Requests | 不可用 | verify=False |
| Python httpx | Proxy(..., ssl |
在客户端上添加verify=False |
Node https-proxy-agent |
agent选项{ reject |
NODE(避免使用) |
| Node undici | proxyTls: { reject |
添加requestTls: { reject |
| Go | 不可用 | Insecure |
| PHP curl | CURLOPT / CURLOPT |
添加CURLOPT |
| Java | 不可用 | 信任所有证书的TrustManager(避免使用) |
| 浏览器 | 不可用 | 不可用;安装根证书 |
无人值守运行的场景应优先使用级别2。验证只需要一个文件和一个选项,而它保护的凭证可用于消费。
确认TLS确实在使用#
curl --verbose会在CONNECT行之前打印代理握手,包括证书主题CN=hub-eu-1.litport.net。- 通过
https://使用错误密码时,仍会返回407和X-Proxy-Error-Code: 4,与通过http://时完全一样;错误响应头在TLS内部传输。 - 如果连接立即关闭且没有HTTP响应,说明节点无法完成TLS握手。检查:使用的是主机名而不是IP地址;
https://用在HTTP端口而不是SOCKS5端口上;客户端在代理链路上信任Litport根证书。参见代理错误参考。
相关页面#
- 令牌、代理产品和节点,了解端口和凭证。
- 缓存证书设置,了解根证书的标识、指纹以及各证书库的安装方法。
- 静态响应缓存,如果您还希望免费提供重复的静态响应。