首页 > 解决方案 > 在进行 HTTP 调用之前使用 RestTemplate 清除 Cookie

问题描述

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

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

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

首先,我正在考虑进行身份验证调用并从 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(之前的原因描述)。有没有可能?我没有找到任何有效的方法。


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

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

标签: javacookiessession-cookiesresttemplate

解决方案


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

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

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

public ResponseEntity getContent(String url, Map<String, String> headers, Map<String, String> 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);

…………

从响应中,如果您想阅读,我们可以通过以下方式阅读 cookie

列出 responseCookies = responseEntity.getHeaders().get(HttpHeaders.SET_COOKIE); Map<String, String> 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;

推荐阅读