1. Обзор
В этой статье мы покажем, как мы можем протестировать API, защищенный с помощью OAuth с поддержкой тестирования Spring MVC .
Примечание : в этой статье используется устаревший проект Spring OAuth .
2. Сервер авторизации и ресурсов
Руководство по настройке сервера авторизации и ресурсов см. в предыдущей статье: Spring REST API + OAuth2 + AngularJS .
Наш сервер авторизации использует JdbcTokenStore и определяет клиента с идентификатором “foo ClientID Password” и паролем “secret” , а также поддерживает тип password grant.
Сервер ресурсов ограничивает URL-адрес /employee ролью АДМИНИСТРАТОРА.
Начиная с версии Spring Boot 1.5.0 адаптер безопасности имеет приоритет над адаптером ресурсов OAuth, поэтому, чтобы изменить порядок, мы должны аннотировать класс WebSecurityConfigurerAdapter с помощью @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) .
В противном случае Spring попытается получить доступ к запрошенным URL-адресам на основе правил безопасности Spring вместо правил 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 Listemployees = new ArrayList<>(); @GetMapping("/employee") @ResponseBody public Optional 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 нам необходимо ввести Webapplicationcontext и Цепочку фильтров безопасности Spring beans. Мы будем использовать их для получения экземпляра 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 .
Чтобы отправить требуемый заголовок Authorization , нам сначала нужно получить действительный токен доступа, отправив запрос POST в конечную точку /oauth/token|/. Эта конечная точка требует базовой аутентификации HTTP с идентификатором и секретом клиента OAuth, а также списком параметров, указывающих client_id , grant_type , имя пользователя и пароль .
Используя поддержку тестов Spring MVC, параметры могут быть завернуты в многозначную карту , а аутентификация клиента может быть отправлена с помощью метода httpBasic .
Давайте создадим метод, который отправляет запрос POST для получения токена и считывает значение access_token из ответа JSON:
private String obtainAccessToken(String username, String password) throws Exception { MultiValueMapparams = 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. Тестирование запросов на ПОЛУЧЕНИЕ и отправку
Маркер доступа может быть добавлен в запрос с помощью метода header(“Authorization”, “Bearer “+ accessToken) .
Давайте попытаемся получить доступ к одному из наших защищенных сопоставлений без заголовка Authorization и убедимся, что мы получили несанкционированный код состояния:
@Test public void givenNoToken_whenGetSecureRequest_thenUnauthorized() throws Exception { mockMvc.perform(get("/employee") .param("email", EMAIL)) .andExpect(status().isUnauthorized()); }
Мы указали, что только пользователи с ролью АДМИНИСТРАТОРА могут получить доступ к URL-адресу /employee . Давайте создадим тест, в котором мы получим маркер доступа для пользователя с ролью USER и проверим, что мы получили код состояния forbidden :
@Test public void givenInvalidRole_whenGetSecureRequest_thenForbidden() throws Exception { String accessToken = obtainAccessToken("user1", "pass"); mockMvc.perform(get("/employee") .header("Authorization", "Bearer " + accessToken) .param("email", "[email protected]")) .andExpect(status().isForbidden()); }
Затем давайте протестируем наш API с использованием допустимого маркера доступа, отправив запрос POST для создания объекта Employee , а затем запрос GET для чтения созданного объекта:
@Test public void givenToken_whenPostGetSecureRequest_thenOk() throws Exception { String accessToken = obtainAccessToken("admin", "nimda"); String employeeString = "{\"email\":\"[email protected]\",\"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", "[email protected]") .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 -Pvc.