使用Java 11 HttpClient设置会话cookie

8

我正在使用Java 11 HttpClient尝试获取页面内容。由于该页面使用OAuth,因此我希望使用我的会话cookie进行身份验证。我目前使用以下代码,灵感来自这个问题

public static void main(String[] args) throws IOException, InterruptedException {
  String year = args[0];
  String day = args[1];
  String session = System.getenv("AOCSESSION");
  System.out.println(session);
  
  HttpRequest req = HttpRequest.newBuilder()
      .uri(URI.create("https://adventofcode.com/"+year+"/day/"+day+"/input")).GET().build();
  CookieManager cookieManager = new CookieManager();
  HttpCookie cookie = new HttpCookie("session", session);
  cookieManager.getCookieStore().add(URI.create("https://adventofcode.com"), cookie);
  HttpClient client = HttpClient.newBuilder().cookieHandler(cookieManager).build();

  String body = client.send(req, BodyHandlers.ofString()).body();
  System.out.println(body);
}

不过,请求未经过身份验证 (响应: 谜题输入因用户而异,请登录以获取您的谜题输入。)。 我已经尝试向cookie添加其他字段:

cookie.setDomain(".adventofcode.com");
cookie.setMaxAge(Instant.parse("2031-11-01T18:26:23.293Z").getEpochSecond());
cookie.setPath("/");
cookie.setHttpOnly(true);
cookie.setSecure(true);

但这并没有解决问题。

我该如何向请求中添加一个会话cookie?

2个回答

9
以下是使用正确的CookieManager而不是硬编码标头的解决方案:
public static void main(String[] args) throws Exception {

        CookieHandler.setDefault(new CookieManager());

        HttpCookie sessionCookie = new HttpCookie("session", "53616c7465645f5f9d467d3ae831ec1b1e7289ef45d256224786e1ed13");
        sessionCookie.setPath("/");
        sessionCookie.setVersion(0);

        ((CookieManager) CookieHandler.getDefault()).getCookieStore().add(new URI("https://adventofcode.com"),
                sessionCookie);

        HttpClient client = HttpClient.newBuilder()
                .cookieHandler(CookieHandler.getDefault())
                .connectTimeout(Duration.ofSeconds(10))
                .build();

        HttpRequest req = HttpRequest.newBuilder()
                .uri(URI.create("https://adventofcode.com/2020/day/1/input"))
                .GET().build();


        System.out.println(client.send(req, HttpResponse.BodyHandlers.ofString()).body());

    }

sessionCookie.setPath("/"); 重要提示: Java有一个CookieFilter,它实际上匹配请求URI路径,而不是Cookie路径。

sessionCookie.setVersion(0); 重要提示: 该站点不支持版本1(HttpCookie中的默认版本,符合RFC 2965/2109)

版本0遵循原始的Netscape cookie标准,并且AdventOfCode喜欢它。

版本0和版本1之间的区别在于Cookie的外观:

版本0:

INFO: HEADERS: HEADERS FRAME (stream=1)
    :authority: adventofcode.com
    :method: GET
    :path: /2020/day/1/input
    :scheme: https
    Cookie: session=53616c7465645f5f9d467....
    User-Agent: Java-http-client/11.0.7

版本 1:

INFO: HEADERS: HEADERS FRAME (stream=1)
    :authority: adventofcode.com
    :method: GET
    :path: /2020/day/1/input
    :scheme: https
    Cookie: $Version="1"
    Cookie: session="53616c7465645f5f9d467.....13";$Path="/"
    User-Agent: Java-http-client/11.0.7

版本1会从网站上抛出500服务器错误。

为了帮助你调试HttpClient日志,将以下内容添加到你的程序参数中:

-Djdk.httpclient.HttpClient.log=errors,requests,headers,frames[:control:data:window:all],content,ssl,trace,channel,all

日志:

jdk.internal.net.http.HttpClientImpl$SelectorManager run
INFO: CHANNEL: HttpClient-1-SelectorManager: starting
nov. 04, 2021 3:35:39 EM jdk.internal.net.http.MultiExchange requestFilters
INFO: MISC: Applying request filters
nov. 04, 2021 3:35:39 EM jdk.internal.net.http.MultiExchange requestFilters
INFO: MISC: Applying jdk.internal.net.http.AuthenticationFilter@49ec71f8
nov. 04, 2021 3:35:39 EM jdk.internal.net.http.MultiExchange requestFilters
INFO: MISC: Applying jdk.internal.net.http.RedirectFilter@8f4ea7c
nov. 04, 2021 3:35:39 EM jdk.internal.net.http.MultiExchange requestFilters
INFO: MISC: Applying jdk.internal.net.http.CookieFilter@436813f3
nov. 04, 2021 3:35:39 EM jdk.internal.net.http.CookieFilter request
INFO: MISC: Request: adding cookies for https://adventofcode.com/2020/day/1/input
nov. 04, 2021 3:35:39 EM jdk.internal.net.http.MultiExchange requestFilters
INFO: MISC: All filters applied
nov. 04, 2021 3:35:39 EM jdk.internal.net.http.AbstractAsyncSSLConnection createSSLParameters
INFO: SSL: AbstractAsyncSSLConnection: Setting application protocols: [h2, http/1.1]
nov. 04, 2021 3:35:39 EM jdk.internal.net.http.AbstractAsyncSSLConnection <init>
INFO: SSL: SSLParameters:
    cipher: TLS_AES_128_GCM_SHA256
    cipher: TLS_AES_256_GCM_SHA384
    cipher: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
    cipher: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
    cipher: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
    cipher: TLS_RSA_WITH_AES_256_GCM_SHA384
    cipher: TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384
    cipher: TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384
    cipher: TLS_DHE_RSA_WITH_AES_256_GCM_SHA384
    cipher: TLS_DHE_DSS_WITH_AES_256_GCM_SHA384
    cipher: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
    cipher: TLS_RSA_WITH_AES_128_GCM_SHA256
    cipher: TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256
    cipher: TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256
    cipher: TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
    cipher: TLS_DHE_DSS_WITH_AES_128_GCM_SHA256
    cipher: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
    cipher: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
    cipher: TLS_RSA_WITH_AES_256_CBC_SHA256
    cipher: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384
    cipher: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384
    cipher: TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
    cipher: TLS_DHE_DSS_WITH_AES_256_CBC_SHA256
    cipher: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
    cipher: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
    cipher: TLS_RSA_WITH_AES_256_CBC_SHA
    cipher: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA
    cipher: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA
    cipher: TLS_DHE_RSA_WITH_AES_256_CBC_SHA
    cipher: TLS_DHE_DSS_WITH_AES_256_CBC_SHA
    cipher: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
    cipher: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
    cipher: TLS_RSA_WITH_AES_128_CBC_SHA256
    cipher: TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256
    cipher: TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256
    cipher: TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
    cipher: TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
    cipher: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
    cipher: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
    cipher: TLS_RSA_WITH_AES_128_CBC_SHA
    cipher: TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA
    cipher: TLS_ECDH_RSA_WITH_AES_128_CBC_SHA
    cipher: TLS_DHE_RSA_WITH_AES_128_CBC_SHA
    cipher: TLS_DHE_DSS_WITH_AES_128_CBC_SHA
    cipher: TLS_EMPTY_RENEGOTIATION_INFO_SCSV
    application protocol: h2
    application protocol: http/1.1
    protocol: TLSv1.3
    protocol: TLSv1.2
    endpointIdAlg: HTTPS
    server name: type=host_name (0), value=adventofcode.com

nov. 04, 2021 3:35:39 EM jdk.internal.net.http.HttpClientImpl registerTimer
INFO: MISC: Registering timer ConnectTimerEvent, TimeoutEvent[id=1, duration=PT10S, deadline=2021-11-04T14:35:49.817109800Z]
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.SocketTube$InternalReadPublisher$InternalReadSubscription handleSubscribeEvent
INFO: CHANNEL: Start reading from java.nio.channels.SocketChannel[connected local=/100.120.31.175:57725 remote=adventofcode.com/54.166.48.177:443]
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.SocketTube$InternalWriteSubscriber startSubscription
INFO: CHANNEL: Start requesting bytes for writing to channel: java.nio.channels.SocketChannel[connected local=/100.120.31.175:57725 remote=adventofcode.com/54.166.48.177:443]
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.HttpClientImpl cancelTimer
INFO: MISC: Canceling timer ConnectTimerEvent, TimeoutEvent[id=1, duration=PT10S, deadline=2021-11-04T14:35:49.817109800Z]
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Http2Connection <init>
INFO: MISC: Connection send window size 65 535 
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Http2Connection sendConnectionPreface
INFO: MISC: /100.120.31.175:57725: start sending connection preface to adventofcode.com/54.166.48.177:443
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Http2Connection sendConnectionPreface
INFO: FRAME: OUT: SETTINGS: length=30, streamid=0, flags=0  Settings: HEADER_TABLE_SIZE=16384 ENABLE_PUSH=1 MAX_CONCURRENT_STREAMS=100 INITIAL_WINDOW_SIZE=16777216 MAX_FRAME_SIZE=16384 
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Http2Connection sendConnectionPreface
INFO: MISC: PREFACE_BYTES sent
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Http2Connection sendConnectionPreface
INFO: MISC: Settings Frame sent
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Http2Connection sendConnectionPreface
INFO: CHANNEL: Sending initial connection window update frame: 33 488 897 (33 554 432 - 65 535)
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Http2Connection encodeFrame
INFO: FRAME: OUT: WINDOW_UPDATE: length=4, streamid=0, flags=0  WindowUpdate: 33488897
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Http2Connection sendConnectionPreface
INFO: MISC: finished sending connection preface
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Stream sendHeadersAsync
INFO: REQUEST: https://adventofcode.com/2020/day/1/input GET
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Http2Connection encodeHeaders
INFO: HEADERS: HEADERS FRAME (stream=1)
    :authority: adventofcode.com
    :method: GET
    :path: /2020/day/1/input
    :scheme: https
    Cookie: session=53616c7465645f5f9d467d3ae831
    User-Agent: Java-http-client/11.0.7


nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Http2Connection lambda$encodeFrames$9
INFO: FRAME: OUT: HEADERS: length=126, streamid=1, flags=END_STREAM END_HEADERS 
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Stream getResponseAsync
INFO: MISC: Response future (stream=1) is: jdk.internal.net.http.common.MinimalFuture@374f597e[Not completed] (id=43)
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Http2Connection processFrame
INFO: FRAME: IN: SETTINGS: length=18, streamid=0, flags=0  Settings: MAX_CONCURRENT_STREAMS=128 INITIAL_WINDOW_SIZE=65536 MAX_FRAME_SIZE=16777215 
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Http2Connection encodeFrame
INFO: FRAME: OUT: SETTINGS: length=0, streamid=0, flags=ACK  Settings: 
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Http2Connection processFrame
INFO: FRAME: IN: WINDOW_UPDATE: length=4, streamid=0, flags=0  WindowUpdate: 2147418112
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Http2Connection processFrame
INFO: FRAME: IN: SETTINGS: length=0, streamid=0, flags=ACK  Settings: 
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Http2Connection processFrame
INFO: FRAME: IN: HEADERS: length=118, streamid=1, flags=END_HEADERS 
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Stream$HeadersConsumer onDecoded
INFO: MISC: RECEIVED HEADER (streamid=1): :status: 200
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Stream$HeadersConsumer onDecoded
INFO: MISC: RECEIVED HEADER (streamid=1): date: Thu, 04 Nov 2021 14:35:40 GMT
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Stream$HeadersConsumer onDecoded
INFO: MISC: RECEIVED HEADER (streamid=1): content-type: text/plain
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Stream$HeadersConsumer onDecoded
INFO: MISC: RECEIVED HEADER (streamid=1): content-length: 990
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Stream$HeadersConsumer onDecoded
INFO: MISC: RECEIVED HEADER (streamid=1): server: Apache
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Stream$HeadersConsumer onDecoded
INFO: MISC: RECEIVED HEADER (streamid=1): server-ip: 172.31.16.87
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Stream$HeadersConsumer onDecoded
INFO: MISC: RECEIVED HEADER (streamid=1): vary: Accept-Encoding
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Stream$HeadersConsumer onDecoded
INFO: MISC: RECEIVED HEADER (streamid=1): strict-transport-security: max-age=300
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Stream incoming
INFO: MISC: handling response (streamid=1)
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Stream handleResponse
INFO: HEADERS: RESPONSE HEADERS:
    :status: 200
    content-length: 990
    content-type: text/plain
    date: Thu, 04 Nov 2021 14:35:40 GMT
    server: Apache
    server-ip: 172.31.16.87
    strict-transport-security: max-age=300
    vary: Accept-Encoding

nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Stream completeResponse
INFO: MISC: Completing response (streamid=1): jdk.internal.net.http.common.MinimalFuture@113d2103[Not completed, 1 dependents] (id=42)
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Exchange lambda$wrapForLog$11
INFO: RESPONSE: (GET https://adventofcode.com/2020/day/1/input) 200 HTTP_2 Local port:  57725
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.MultiExchange responseFilters
INFO: MISC: Applying response filters
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.MultiExchange responseFilters
INFO: MISC: Applying jdk.internal.net.http.CookieFilter@436813f3
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.CookieFilter response
INFO: MISC: Response: processing cookies for https://adventofcode.com/2020/day/1/input
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.CookieFilter response
INFO: MISC: Response: parsing cookies from {:status=[200], content-length=[990], content-type=[text/plain], date=[Thu, 04 Nov 2021 14:35:40 GMT], server=[Apache], server-ip=[172.31.16.87], strict-transport-security=[max-age=300], vary=[Accept-Encoding]}
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.MultiExchange responseFilters
INFO: MISC: Applying jdk.internal.net.http.RedirectFilter@8f4ea7c
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.MultiExchange responseFilters
INFO: MISC: Applying jdk.internal.net.http.AuthenticationFilter@49ec71f8
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.MultiExchange responseFilters
INFO: MISC: All filters applied
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Stream readBodyAsync
INFO: MISC: Reading body on stream 1
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Http2Connection processFrame
INFO: FRAME: IN: DATA: length=990, streamid=1, flags=0 
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Stream schedule
INFO: MISC: responseSubscriber.onNext 990
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Http2Connection processFrame
INFO: FRAME: IN: DATA: length=0, streamid=1, flags=END_STREAM 
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Stream schedule
INFO: MISC: responseSubscriber.onComplete
1337
1906
2007
1939
818
1556
2005
1722
1484
1381
1682
1253
1967
1718
2002
1398
1439
1689
1746
1979
1985
1387
1509
1566
1276
1625
1853
882
1750
1390
1731
1555
1860
1675
1457
1554
1506
1639
1543
1849
1062
1869
1769
1858
1916
1504
1747
1925
1275
1273
1383
1816
1814
1481
1649
1993
1759
1949
1499
1374
1613
1424
783
1765
1576
1933
1270
1844
1856
1634
1261
1293
1741
668
1573
1599
1877
1474
1918
476
1515
1029
202
1589
1867
1503
1582
1605
1557
587
1462
1955
1806
1834
1739
1343
1594
1622
1972
1527
1798
1719
1866
134
2000
1992
1966
1909
1340
1621
1921
1256
1365
1314
1748
1963
1379
1627
1848
1977
1917
1826
1716
1631
1404
1936
1677
1661
1986
1997
1603
1932
1780
1902
2009
1257
1871
1362
1662
1507
1255
1539
1962
1886
1513
1264
1873
1700
807
1426
1697
1698
1519
1791
1240
1542
1497
1761
1640
1502
1770
1437
1333
1805
1591
1644
1420
1809
1587
1421
1540
1942
470
1940
1831
1247
1632
1975
1774
1919
1829
1944
1553
1361
1483
1995
1868
1601
1552
1854
1490
1855
1987
1538
1389
1454
1427
1686
1456
1974

nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Stream close
INFO: MISC: Closing stream 1
nov. 04, 2021 3:35:40 EM jdk.internal.net.http.Stream close
INFO: MISC: Stream 1 closed

Process finished with exit code 0

提示:这真是一件有趣的事情 :D


对我来说,这仍然不能让我在网站上进行身份验证。当我尝试访问https://adventofcode.com/2020/day/1/input时,仍会出现“Puzzle inputs differ by user. Please log in to get your puzzle input.”的提示,即使我使用自己有效的会话密钥。或者我是否缺少某些需要进行身份验证的东西? - Simon Baars
1
@SimonBaars 我已经把所有的硬编码 Header 都改成 CookieManager。现在请试试看。我本地测试过了,它可以针对 day/1/input 工作。 - JCompetence
2
做到了!非常感谢! - Simon Baars
2
真高兴它能帮助到你 @SimonBaars - JCompetence

4

Java 11 HttpClient 内置了对 Session-Cookie 的支持。如果您像这样创建一个客户端:

var client = HttpClient.newBuilder()
                .cookieHandler(new CookieManager())
                .version(HttpClient.Version.HTTP_2)
                .build();

客户端将自动保存在第一次授权请求中得到的所有cookie。此客户端发出的任何后续请求都将已经设置好了cookie。

或者,如果您想在发送初始请求之前为cookie设置某些值,请尝试:

var cookieManager = new cookieManager();
cookieManager.getCookieStore().add(someHostURI, someSessionCookie);

//Now this cookie can be used to create the client:
var client = HttpClient.newBuilder()
                .cookieHandler(cookieManager)
                .version(HttpClient.Version.HTTP_2)
                .build();

若需要更详细的解释,请查看此文章


如果您需要在发出初始请求之前设置Cookie,则这不是一个合适的解决方案。 - BugHunterUK
1
你能解释一下为什么吗?我的意思是,CookieManager 可以通过 var cookie = new CookieManager() 进行初始化,然后向此 cookie 添加所需的值,最后在使用上述代码时使用此 cookie。如果有更好的想法,我很乐意了解。 - Prasannjeet Singh
似乎至少php会话cookie没有保存。你知道为什么吗? - mazend

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接