在进行 HTTP 调用之前使用 RestTemplate 清除 Cookie

Posted

技术标签:

【中文标题】在进行 HTTP 调用之前使用 RestTemplate 清除 Cookie【英文标题】:clear Cookies with RestTemplate before make HTTP-call 【发布时间】:2018-11-13 17:56:10 【问题描述】:

我使用 Spring-Boot 2.0.2 RestTemplate 制作以下场景:

我是一名休息消费者(客户),其中:

    首先需要登录 Spring-Security-Check 然后进行第二次调用以获取数据。

首先,我正在考虑进行身份验证调用并从 SET-COOKIE 手动读取 JSESSIONID Cookie 并将其设置在标题中的第二次调用中。这是我的第一次尝试:

RestClient :

@Service
public class RestClient 
    private static final String ENDPOINT_DATA = "/data";
    private static final String ENDPOINT_SECURITY_CHECK = "/j_spring_security_check";

    private static final String HTTP_HEADER_KEY_SET_COOKIE = "Set-Cookie";
    private static final String HTTP_HEADER_KEY_COOKIE = "Cookie";

    private static final String PROPERTY_SPRING_SECURITY_USER = "j_username";
    private static final String PROPERTY_SPRING_SECURITY_PASS = "j_password";

    private final RestTemplate restTemplate;
    private final RestConfig restConfig;

    @Autowired
    public RestClient(RestTemplateBuilder restTemplateBuilder, final RestConfig restConfig) 

        notNull(restTemplateBuilder, "restTemplateBuilder must not be null!");
        this.restTemplate = restTemplateBuilder
                .additionalCustomizers(new NoRedirectionHandlingRestTemplateCostumizer())
                .build();

        notNull(restConfig, "openIdConfig must not be null!");
        this.restConfig = restConfig;
    

    public String getData() 
        final String jSessionCockie = jSpringSecurityLogin();
        return getAuthorizeCode(jSessionCockie);
    

    private String jSpringSecurityLogin() 
        // read config
        final String fullLoginUri = restConfig.getUrl() + ENDPOINT_SECURITY_CHECK;
        final String user = restConfig.getUser();
        final String password = restConfig.getPassword();

        // Build entity that is send
        final HttpHeaders headers = new HttpHeaders();
        final String body = PROPERTY_SPRING_SECURITY_USER + "=" + user + "&" + PROPERTY_SPRING_SECURITY_PASS + "=" + password;
        final HttpEntity<String> toSend = new HttpEntity<>(body, headers);

        final String jSessionIdCockie;

        final ResponseEntity<String> response = restTemplate.postForEntity(fullLoginUri, toSend, String.class);

        // Get String "JSESSIONID=XXXX". If there are other Cookies, propably will fail.
        if (HttpStatus.FOUND.equals(response.getStatusCode()) && response.getHeaders().containsKey(HTTP_HEADER_KEY_SET_COOKIE)) 
            jSessionIdCockie = response.getHeaders().get(HTTP_HEADER_KEY_SET_COOKIE).get(0);
         else 
            throw new Error();
        

        return jSessionIdCockie;
    

    private String getAuthorizeCode(final String jSessionCockie) 
        // read config
        final String fullDataUri = restConfig.getUrl() + ENDPOINT_DATA;

        // Build entity that is send
        final HttpHeaders headers = new HttpHeaders();
        headers.add(HTTP_HEADER_KEY_COOKIE, jSessionCockie);
        final HttpEntity<Void> toSend = new HttpEntity<>(headers);

        final ResponseEntity<String> response = restTemplate.exchange(fullDataUri, HttpMethod.GET, toSend, String.class);

        if (HttpStatus.OK.equals(response.getStatusCode()) && response.hasBody()) 
            return response.getBody();
         else 
            return "";
        
    

为了完整起见,这里是构造函数中使用的 Costumizer:

class NoRedirectionHandlingRestTemplateCostumizer implements RestTemplateCustomizer 
    @Override
    public void customize(RestTemplate restTemplate) 
        final HttpClient httpClient = HttpClientBuilder.create()
                .disableRedirectHandling()
                .build();
        restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient));
    

现在,当我使用 Wiremock 进行一些功能测试时,我在数据调用的 wiremock-reqeust 历史记录中看到以下内容:


  ...
  "headers":
  
    "Cookie": [
      "JSESSIONID=axcvueiornxniuwherwuieoiun,asdpfoiu",
      "JSESSIONID=axcvueiornxniuwherwuieoiun,asdpfoiu"
    ],
    ...
  ,
  "cookies":
  
    "JSESSIONID": [
      "axcvueiornxniuwherwuieoiun,asdpfoiu",
      "axcvueiornxniuwherwuieoiun,asdpfoiu"
    ]
  
  ...

等待 - JSESSIONID 设置了 2 次。 Cool REST 为我处理!

第二次尝试:我可以删除 Cookie 处理。它有效。

但第一次和第二次尝试有一个问题:在第一次调用RestClient.getData() 之后,对登录端点的所有后续调用也设置了 JSESSIONID。我不知道 Rest-Producer 如何处理这个(会话超时等)。

现在我的问题/疑问:

我想强制RestTemplate 在进行登录调用之前忘记/清除所有 Cookie(之前的原因描述)。有没有可能?我没有找到任何有效的方法。


我现在的解决方法是禁用RestTemplate 的 Cookie 管理,并像第一次尝试一样手动执行所有操作。可以通过RestTemplateCustomizer 禁用 CookieManagment:

class NoRedirectionHandlingRestTemplateCostumizer implements RestTemplateCustomizer 
    @Override
    public void customize(RestTemplate restTemplate) 
        final HttpClient httpClient = HttpClientBuilder.create()
                .disableRedirectHandling()
                .disableCookieManagement()
                .build();
        restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient));
    

【问题讨论】:

你能找到解决方案吗?我面临同样的问题,即使添加解决方法也不起作用。 您好 user964819 。我没有找到其他解决方案,然后是我写的解决方法。如果您想使用标准 OAuth 2,那么 Spring 中有一个实现:请参阅 ***.com/questions/27864295/… 在我的情况下,它不是标准的,这就是重新配置 HttpClient 的黑客攻击 【参考方案1】:

正如问题中提到的,首先我们需要通过以下方式禁用cookie管理

CloseableHttpClient httpClient = HttpClients.custom() .setConnectionManager(厘米) .setRetryHandler(retryHandler) .setKeepAliveStrategy(connectionKeepAliveStrategy) .disableCookieManagement() .evictExpiredConnections().evictIdleConnections(10, TimeUnit.MINUTES) .build();

并像设置任何其他标头一样设置“Cookie”标头。

public ResponseEntity getContent(String url, Map headers, Map cookies) HttpHeaders requestHeaders = new HttpHeaders();

    // add headers
    for(String key : headers.keySet())
        requestHeaders.set(key, headers.get(key));
    

    // add cookies
    List<String> allCookieValues = new ArrayList<>();
    for(String key : cookies.keySet())
        String value = cookies.get(key);
        String cookieString = MessageFormat.format("0=1", key,value);
        allCookieValues.add(cookieString);
    
    requestHeaders.addAll(HttpHeaders.COOKIE, allCookieValues);

    HttpEntity<String> httpEntity = new HttpEntity<String>(requestHeaders);
    UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(url);
    URI uri = builder.build(true).toUri();
    ResponseEntity responseEntity = null;
    try 
        responseEntity = restTemplate.exchange(uri, HttpMethod.GET, httpEntity, byte[].class);

…… ......

从响应中,如果你想读取我们可以通过以下方式读取cookies

列出 responseCookies = responseEntity.getHeaders().get(HttpHeaders.SET_COOKIE); Map cookieNameValue = new HashMap();

    if(requestCookies != null)
        cookieNameValue.putAll(requestCookies);
    

    for(String cookie : responseCookies)
        String[] cookieParts = cookie.split(";")[0].split("=");
        cookieNameValue.put(cookieParts[0], cookieParts[1]);
    
    return cookieNameValue;

【讨论】:

以上是关于在进行 HTTP 调用之前使用 RestTemplate 清除 Cookie的主要内容,如果未能解决你的问题,请参考以下文章

springcloud

springcloud

springcloud

Angular 8 在身份验证完成之前抑制 HTTP 调用

服务器在渲染之前不会等到 http 调用完成 - 角度 4 服务器端渲染

如何使用简单的ajax jQuery对DialogFlow V2进行http调用?