Перейти к основному содержимому

Тестирование защищенного API OAuth с помощью Spring MVC (с использованием устаревшего стека Spring Security OAuth)

· 4 мин. чтения

1. Обзор

В этой статье мы собираемся показать, как мы можем протестировать API, защищенный с помощью OAuth с поддержкой тестирования Spring MVC .

Примечание . В этой статье используется устаревший проект Spring OAuth .

2. Авторизация и сервер ресурсов

Руководство по настройке сервера авторизации и ресурсов см. в этой предыдущей статье: Spring REST API + OAuth2 + AngularJS .

Наш сервер авторизации использует JdbcTokenStore и определил клиента с идентификатором «fooClientIdPassword» и паролем «secret» и поддерживает тип предоставления пароля .

Сервер ресурсов ограничивает URL-адрес /employee ролью ADMIN.

Начиная с Spring Boot версии 1.5.0 адаптер безопасности имеет приоритет над адаптером ресурсов OAuth, поэтому, чтобы изменить порядок, мы должны аннотировать класс WebSecurityConfigurerAdapter с помощью @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) .

В противном случае Spring попытается получить доступ к запрошенным URL-адресам на основе правил Spring Security вместо правил Spring OAuth, и мы получим ошибку 403 при использовании проверки подлинности токена.

3. Определение примера API

Во-первых, давайте создадим простой POJO с именем Employee с двумя свойствами, которыми мы будем управлять через API:

public class Employee {
private String email;
private String name;

// standard constructor, getters, setters
}

Далее давайте определим контроллер с двумя сопоставлениями запросов для получения и сохранения объекта Employee в список:

@Controller
public class EmployeeController {

private List<Employee> employees = new ArrayList<>();

@GetMapping("/employee")
@ResponseBody
public Optional<Employee> getEmployee(@RequestParam String email) {
return employees.stream()
.filter(x -> x.getEmail().equals(email)).findAny();
}

@PostMapping("/employee")
@ResponseStatus(HttpStatus.CREATED)
public void postMessage(@RequestBody Employee employee) {
employees.add(employee);
}
}

Имейте в виду, что для того, чтобы это заработало, нам нужен дополнительный модуль JDK8 Jackson . В противном случае дополнительный класс не будет правильно сериализован/десериализован. Последнюю версию jackson-datatype-jdk8 можно загрузить с Maven Central.

4. Тестирование API

4.1. Настройка тестового класса

Чтобы протестировать наш API, мы создадим тестовый класс с аннотацией @SpringBootTest , который использует класс AuthorizationServerApplication для чтения конфигурации приложения.

Для тестирования защищенного API с поддержкой тестирования Spring MVC нам необходимо внедрить bean- компоненты WebAppplicationContext и Spring Security Filter Chain . Мы будем использовать их для получения экземпляра MockMvc перед запуском тестов:

@RunWith(SpringRunner.class)
@WebAppConfiguration
@SpringBootTest(classes = AuthorizationServerApplication.class)
public class OAuthMvcTest {

@Autowired
private WebApplicationContext wac;

@Autowired
private FilterChainProxy springSecurityFilterChain;

private MockMvc mockMvc;

@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
.addFilter(springSecurityFilterChain).build();
}
}

4.2. Получение токена доступа

Проще говоря, API, защищенные с помощью OAuth2, ожидают получить заголовок Authorization со значением Bearer <access_token> .

Чтобы отправить требуемый заголовок авторизации , нам сначала нужно получить действительный токен доступа, выполнив запрос POST к конечной точке /oauth/token . Для этой конечной точки требуется базовая проверка подлинности HTTP с идентификатором и секретом клиента OAuth, а также список параметров, указывающих client_id , grant_type , имя пользователя и пароль .

Используя поддержку тестирования Spring MVC, параметры могут быть заключены в MultiValueMap , а аутентификация клиента может быть отправлена с использованием метода httpBasic .

Давайте создадим метод, который отправляет запрос POST для получения токена и считывает значение access_token из ответа JSON:

private String obtainAccessToken(String username, String password) throws Exception {

MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "password");
params.add("client_id", "fooClientIdPassword");
params.add("username", username);
params.add("password", password);

ResultActions result
= mockMvc.perform(post("/oauth/token")
.params(params)
.with(httpBasic("fooClientIdPassword","secret"))
.accept("application/json;charset=UTF-8"))
.andExpect(status().isOk())
.andExpect(content().contentType("application/json;charset=UTF-8"));

String resultString = result.andReturn().getResponse().getContentAsString();

JacksonJsonParser jsonParser = new JacksonJsonParser();
return jsonParser.parseMap(resultString).get("access_token").toString();
}

4.3. Тестирование запросов GET и POST

Токен доступа можно добавить в запрос с помощью метода header("Authorization", "Bearer" + accessToken) .

Давайте попробуем получить доступ к одному из наших защищенных сопоставлений без заголовка авторизации и убедимся, что мы получили неавторизованный код состояния:

@Test
public void givenNoToken_whenGetSecureRequest_thenUnauthorized() throws Exception {
mockMvc.perform(get("/employee")
.param("email", EMAIL))
.andExpect(status().isUnauthorized());
}

Мы указали, что только пользователи с ролью ADMIN могут получить доступ к URL-адресу /employee . Давайте создадим тест, в котором мы получим токен доступа для пользователя с ролью USER и проверим, что мы получаем запрещенный код состояния:

@Test
public void givenInvalidRole_whenGetSecureRequest_thenForbidden() throws Exception {
String accessToken = obtainAccessToken("user1", "pass");
mockMvc.perform(get("/employee")
.header("Authorization", "Bearer " + accessToken)
.param("email", "jim@yahoo.com"))
.andExpect(status().isForbidden());
}

Затем давайте протестируем наш API, используя действительный токен доступа, отправив запрос POST для создания объекта Employee , а затем запрос GET для чтения созданного объекта:

@Test
public void givenToken_whenPostGetSecureRequest_thenOk() throws Exception {
String accessToken = obtainAccessToken("admin", "nimda");

String employeeString = "{\"email\":\"jim@yahoo.com\",\"name\":\"Jim\"}";

mockMvc.perform(post("/employee")
.header("Authorization", "Bearer " + accessToken)
.contentType(application/json;charset=UTF-8)
.content(employeeString)
.accept(application/json;charset=UTF-8))
.andExpect(status().isCreated());

mockMvc.perform(get("/employee")
.param("email", "jim@yahoo.com")
.header("Authorization", "Bearer " + accessToken)
.accept("application/json;charset=UTF-8"))
.andExpect(status().isOk())
.andExpect(content().contentType(application/json;charset=UTF-8))
.andExpect(jsonPath("$.name", is("Jim")));
}

5. Вывод

В этом кратком руководстве мы продемонстрировали, как мы можем протестировать API, защищенный OAuth, с помощью поддержки тестирования Spring MVC.

Полный исходный код примеров можно найти в проекте GitHub .

Для запуска теста в проекте есть профиль mvc , который можно запустить с помощью команды mvn clean install -Pmvc.