TestRestTemplate с авторизацией

Здесь рассмотрим случай, когда тестируемый /url защищен, и с первого раза запрос с TestRestTemplate сделать не получится.

В этом случае отправляем два запроса — один для получения заголовка авторизации, а второй уже с целью тестирования.
У нас есть два приложения с различными вариантами защиты, применим TestRestTemplate в каждом из них.

Form-Based аутентификация + Сессии

Приложение отсюда.

Тут форма логина находится по адресу /login.

Форма логина

Пользователь вводит в нее имя и пароль, а в ответ получает (в числе прочих) заголовок Set-Cookie (в нем несколько куки, нас интересует первый JSESSIONID):

Set-Cookie: JSESSIONID=4B83B639528C73B31627A8F9890F34D9; Path=/; HttpOnly

Можно отследить запрос в Chrome DevToools:

Post-запрос с данными формы на /login
Post-запрос с данными формы на /login

Теперь если в запросе отправлять заголовок (а браузер так и делает):

Cookie: JSESSIONID=4B83B639528C73B31627A8F9890F34D9

то запрос авторизован будет, то есть допущен в метод контроллера, который нужно протестировать.

То есть браузер включает заголовок Cookie в каждый запрос:

Запрос на защищенный адрес /user. Содержит заголовок Cookie c JSESSIONID=,,,
Запрос на защищенный адрес /user. Содержит заголовок Cookie c JSESSIONID=…

И нам надо повторить поведение браузера.

Так что весь тест будет состоять из двух запросов:

  • получение заголовка Set-Cookie по адресу /login
  • собственно тестирование (например, защищенного адреса /user). Здесь мы сделаем запрос так же, как браузер, то есть включим в него заголовок Cookie с JSESSIONID

В нашем приложении логин делается с формы — и хотя это не REST-endpoint, тем не менее TestRestTemplate может делать и такие запросы.

Получение заголовка

Напишем в тестовом классе вспомогательный метод getCookieForUser():

@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class HelloControllerTests {

    @Autowired
    private TestRestTemplate testRestTemplate;


    @Test
    public void whenGetUser_thenCorrect() {
      ...
    }


    private String getCookieForUser(String username, String password, String loginUrl)   {

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

        ResponseEntity<String> loginResponse = testRestTemplate.postForEntity(
                loginUrl,
                new HttpEntity<>(form, new HttpHeaders()),
                String.class);
        String cookie = loginResponse.getHeaders().get("Set-Cookie").get(0);

        return cookie;
    }
}

В нем TestRestTemplate делает запрос на /login, передает в теле запроса имя и пароль, а в ответ получает нужный нам заголовок Set-Cookie. Из него извлекаем первую пару имя_куки — значение:

JSESSIONID=4B83B639528C73B31627A8F9890F34D9

Эту пару и будем использовать для тестирования защищенных адресов.

Тестирование

Для этого просто будем добавлять ее в заголовок Cookie при каждом запросе. Spring Boot и контейнер сервлетов парсит этот заголовок, опознает сессию и пропускает запрос в контроллер.

Проверим, что все работает:

@Test
public void whenGetUser_thenCorrect() {
    String securedUrl = "/user";

    String cookie = getCookieForUser("user", "password", "/login");


    HttpHeaders headers = new HttpHeaders();
    headers.add("Cookie", cookie);
    ResponseEntity<String> responseFromSecuredEndPoint = testRestTemplate.exchange(securedUrl, HttpMethod.GET, new HttpEntity<>(headers), String.class);

    assertEquals(responseFromSecuredEndPoint.getStatusCode(), HttpStatus.OK);
    assertTrue(responseFromSecuredEndPoint.getBody().equals("User"));
}

По адресу /user находится REST-endpoint с единственной строкой «User» — это мы и проверяем.

Как видите, хотя в TestRestTemplate входит слово Rest, запросы он может делать не только на REST-endpoint. Пример — запрос на адрес /login, по которому находится форма.

TestRestTemplate не может отправлять CSRF-токен (так как он отправляется только с формы), поэтому для тестов CSRF надо отключить. Это можно сделать разными способами, у нас для простоты он отключен в основной конфигурации — http.csrf().disable(). 

JWT-токен

Рассмотрим второе приложение. Тут мы делали специальный REST-endpoint для аутентификации /authencate. Первый запрос будет сюда.

Получение токена

Запрос отправляется в таком виде:

public class AuthRequest {
    private String name;
    private String password;
}

В ответ приходит JWT-токен:

public class AuthResponse {
    private String jwtToken;

}

Или так:

Запрос из PostMan
Запрос из Postman

Так что теперь первый запрос для получения токена будет такой:

private AuthResponse getAuthHeaderForUser(String name, String password) {

    AuthRequest authRequest = new AuthRequest();
    authRequest.setName(name);
    authRequest.setPassword(password);
    AuthResponse authResponse = restTemplate.postForObject("/authenticate", authRequest, AuthResponse.class);

    return authResponse;
}

Тестирование

А полученный токен авторизации теперь надо отправлять в таком заголовке:

Authorization: Bearer <полученный токен>

Так что в этот раз добавляем теперь заголовок Authorization при тестировании адреса /user:

@Test
public void whenGetUser_thenCorrect() {

    AuthResponse authResponse = getAuthHeaderForUser("user", "password");

    HttpHeaders headers = new HttpHeaders();
    headers.add("Authorization", "Bearer " + authResponse.getJwtToken());

    ResponseEntity<String> response = restTemplate.exchange("/user", HttpMethod.GET, new HttpEntity<>(headers), String.class);

    assertTrue(response.getBody().equals("User"));

}

Базовая аутентификация

Она поддерживается TestRestTemplate, так что если в нашем приложении используется базовая аутентификация, никаких дополнительных запросов делать не нужно. Просто передаем имя и пароль:

@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class HelloControllerTest {
    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    public void whenGetAdmin_thenCorrect() {
    	
        ResponseEntity<String> response = restTemplate
                .withBasicAuth("admin", "password")
                .getForEntity("/admin", String.class);
        
        assertTrue(response.getBody().equals("Admin"));
    }

}

Этот пример находится тут на GitHub.

 

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *