fix: TLS fingerprint lifecycle consistency and bump CLI version to 2.1.87

- Update User-Agent from claude-cli/2.1.84 to 2.1.87 in constants.go
  and identity_service.go to match latest Claude Code binary
- Replace ImpersonateChrome() in OAuth createReqClient with Node.js 24.x
  uTLS profile (tlsfingerprint.Profile) to ensure consistent JA3 hash
  across token exchange, refresh, and API calls
- Support direct/HTTP-proxy/SOCKS5 proxy modes with uTLS in OAuth client

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
win 2026-03-30 20:09:56 +08:00
parent 6620b56b5a
commit 53eaae61a3
3 changed files with 33 additions and 7 deletions

View File

@ -52,7 +52,7 @@ const APIKeyHaikuBetaHeader = BetaInterleavedThinking
var DefaultHeaders = map[string]string{
// Keep these in sync with recent Claude CLI traffic to reduce the chance
// that Claude Code-scoped OAuth credentials are rejected as "non-CLI" usage.
"User-Agent": "claude-cli/2.1.84 (external, cli)",
"User-Agent": "claude-cli/2.1.87 (external, cli)",
"X-Stainless-Lang": "js",
"X-Stainless-Package-Version": "0.74.0",
"X-Stainless-OS": "MacOS",

View File

@ -12,6 +12,7 @@ import (
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
"github.com/Wei-Shaw/sub2api/internal/pkg/oauth"
"github.com/Wei-Shaw/sub2api/internal/pkg/proxyurl"
"github.com/Wei-Shaw/sub2api/internal/pkg/tlsfingerprint"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/Wei-Shaw/sub2api/internal/util/logredact"
@ -267,18 +268,43 @@ func (s *claudeOAuthService) RefreshToken(ctx context.Context, refreshToken, pro
}
func createReqClient(proxyURL string) (*req.Client, error) {
// 禁用 CookieJar确保每次授权都是干净的会话
// Use Node.js 24.x TLS fingerprint (same as API requests) instead of Chrome
// to ensure TLS fingerprint consistency across the entire token lifecycle.
// Previously used ImpersonateChrome() which created a JA3 mismatch between
// OAuth token exchange/refresh and API calls.
profile := &tlsfingerprint.Profile{
Name: "oauth-nodejs24",
EnableGREASE: true,
}
client := req.C().
SetTimeout(60 * time.Second).
ImpersonateChrome().
SetCookieJar(nil) // 禁用 CookieJar
SetTimeout(15 * time.Second).
SetCookieJar(nil) // 禁用 CookieJar确保每次授权都是干净的会话
trimmed, _, err := proxyurl.Parse(proxyURL)
if err != nil {
return nil, err
}
if trimmed != "" {
client.SetProxyURL(trimmed)
parsedProxy, parseErr := url.Parse(trimmed)
if parseErr != nil {
return nil, fmt.Errorf("parse proxy URL: %w", parseErr)
}
scheme := strings.ToLower(parsedProxy.Scheme)
switch scheme {
case "socks5", "socks5h":
socks5Dialer := tlsfingerprint.NewSOCKS5ProxyDialer(profile, parsedProxy)
client.SetDialTLS(socks5Dialer.DialTLSContext)
case "http", "https":
httpDialer := tlsfingerprint.NewHTTPProxyDialer(profile, parsedProxy)
client.SetDialTLS(httpDialer.DialTLSContext)
default:
client.SetProxyURL(trimmed)
}
} else {
dialer := tlsfingerprint.NewDialer(profile, nil)
client.SetDialTLS(dialer.DialTLSContext)
}
return client, nil

View File

@ -26,7 +26,7 @@ var (
// 默认指纹值(当客户端未提供时使用)
var defaultFingerprint = Fingerprint{
UserAgent: "claude-cli/2.1.84 (external, cli)",
UserAgent: "claude-cli/2.1.87 (external, cli)",
StainlessLang: "js",
StainlessPackageVersion: "0.74.0",
StainlessOS: "MacOS",