将CSRF令牌添加到RestTemplate获取由表单登录保护的页面的请求

时间:2019-02-21 23:46:07

标签: java spring spring-boot spring-security csrf

以下测试失败,因为它返回的是登录页面,而不是内容。

TestRestTemplate restTemplate = new TestRestTemplate();

HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
form.set("username", testUser.getUser().getUid());
form.set("password", testUser.getUserPassword());

ResponseEntity<String> loginEntity = restTemplate.exchange(
  url("/login"),
  HttpMethod.POST, 
  new HttpEntity<>(form, headers), 
  String.class);

HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setAccept(Arrays.asList(MediaType.TEXT_HTML));
requestHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
// 
// auth csrf and form login - doesn't work
ResponseEntity<String> getLoginEntity = restTemplate.getForEntity(url("/login"), String.class);
String cookie = getLoginEntity.getHeaders().getFirst(HttpHeaders.SET_COOKIE);
// contains CSRF-TOKEN
requestHeaders.set(HttpHeaders.COOKIE, cookie);
Pattern pattern = Pattern.compile("(?s).*name=\"_csrf\".*?value=\"([^\"]+).*");
Matcher matcher = pattern.matcher(getLoginEntity.getBody());
matcher.matches();
requestHeaders.set("X-CSRF-TOKEN", matcher.group(1));
//

ResponseEntity<String> memberEntity = restTemplate.exchange(
  url("/members"),
  HttpMethod.GET,
  new HttpEntity<>(form, requestHeaders),
  String.class
);
assertEquals("Unexpected member content", "Welcome member!", memberEntity.getBody());

配置就像这样

protected void configure(HttpSecurity http) throws Exception {
  http
    .csrf().and()
    .authorizeRequests()
      .antMatchers("/**").fullyAuthenticated()
      .and()
    .formLogin()
      .permitAll();
}

返回的登录页面包括_csrf作为隐藏字段,并且该值对应于cookie中已设置的值。

请注意,通过较小的修改,我可以使以下方案起作用:

但是,我没有成功

  • 表单登录名和csrf

任何有关如何使其正常工作的指导都将受到赞赏。

顺便说一句:我采用的方法如下:https://github.com/spring-projects/spring-boot/blob/master/spring-boot-samples/spring-boot-sample-web-secure/src/test/java/sample/web/secure/SampleSecureApplicationTests.java

1 个答案:

答案 0 :(得分:0)

这不起作用,因为您需要cookie和csrf才能进行POST才能登录,因此至少需要发出两个请求。

首先,您需要设置一个暴露csrf令牌的/csrf页面,例如: {"token": "b3c7338e-95c0-4088-9fb7-d72a870bd60c", "headerName": "X-CSRF-TOKEN", "parameterName": "_csrf" }

然后流程如下:

  1. token中读取/csrf,其中还存储 通过标头Set-Cookie传递的cookie
  2. 为POST登录做准备:使用由token指定的名称在请求标头中设置/csrf,例如:X-CSRF-TOKEN: b3c7338e-95c0-4088-9fb7-d72a870bd60c
  3. 在请求标头Cookie中设置存储的cookie(这是因为cookie和csrf令牌在服务器上绑定在一起)
  4. POST到/login,存储带有响应标头Set-Cookie传递给您的cookie(登录后会更改)
  5. 对于所有后续请求,请使用从/login获取的cookie设置在请求标头Cookie
  6. 对于所有后续的POST请求,您需要从token获得新的/csrf(因为cookie更改了,令牌也更改了)