Тестирование API с защитой OAuth с помощью Spring MVC
1. обзор
В этой статье мы покажем, как мы можемtest an API which is secured using OAuth with the Spring MVC test support.
2. Сервер авторизации и ресурсов
Чтобы узнать, как настроить сервер авторизации и ресурсов, просмотрите эту предыдущую статью:Spring REST API + OAuth2 + AngularJS.
Наш сервер авторизации используетJdbcTokenStore и определил клиента с идентификатором“fooClientIdPassword” и паролем“secret”, а также поддерживает тип предоставленияpassword.
Сервер ресурсов ограничивает 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 employees = 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);
}
}
Имейте в виду, чтоin order to make this work, we need an additional JDK8 Jackson module. В противном случае классOptional не будет правильно сериализован / десериализован. Последнюю версию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, защищенныеOAuth2expects to receive a the Authorization header со значениемBearer <access_token>.
Чтобы отправить требуемый заголовокAuthorization, нам сначала нужно получить действительный токен доступа, сделав запрос POST к конечной точке/oauth/token. Эта конечная точка требует проверки подлинности HTTP Basic с идентификатором и секретом клиента OAuth, а также списком параметров, определяющихclient_id,grant_type,username иpassword.
Используя поддержку тестирования Spring MVC, параметры могут быть заключены вMultiValueMap, а проверка подлинности клиента может быть отправлена с использованием методаhttpBasic.
Let’s create a method that sends a POST request to obtain the token и считывает значениеaccess_token из ответа JSON:
private String obtainAccessToken(String username, String password) throws Exception {
MultiValueMap 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).
Давайте попытаемся получить доступ к одному из наших защищенных сопоставлений без заголовкаAuthorization и убедимся, что мы получили код состоянияunauthorized:
@Test
public void givenNoToken_whenGetSecureRequest_thenUnauthorized() throws Exception {
mockMvc.perform(get("/employee")
.param("email", EMAIL))
.andExpect(status().isUnauthorized());
}
Мы указали, что только пользователи с ролью ADMIN могут получить доступ к 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 project.
Для запуска теста в проекте есть профильmvc, который можно запустить с помощью командыmvn clean install -Pmvc.