Testando uma API segura do OAuth com o Spring MVC

Testando uma API segura do OAuth com o Spring MVC

1. Visão geral

Neste artigo, vamos mostrar como podemostest an API which is secured using OAuth with the Spring MVC test support.

2. Autorização e Servidor de Recursos

Para obter um tutorial sobre como configurar um servidor de autorização e recurso, consulte este artigo anterior:Spring REST API + OAuth2 + AngularJS.

Nosso servidor de autorização usaJdbcTokenStoree definiu um cliente com id“fooClientIdPassword”e senha“secret”, e suporta o tipo de concessãopassword.

O servidor de recursos restringe a URL/employee à função ADMIN.

A partir do Spring Boot versão 1.5.0, o adaptador de segurança tem prioridade sobre o adaptador de recursos OAuth, portanto, para reverter a ordem, temos que anotar a classeWebSecurityConfigurerAdapter com@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER).

Caso contrário, o Spring tentará acessar os URLs solicitados com base nas regras do Spring Security em vez das regras do Spring OAuth, e receberíamos um erro 403 ao usar a autenticação de token.

3. Definindo uma API de amostra

Primeiro, vamos criar um POJO simples chamadoEmployee com duas propriedades que manipularemos por meio da API:

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

    // standard constructor, getters, setters
}

A seguir, vamos definir um controlador com dois mapeamentos de solicitação, para obter e salvar um objetoEmployee em uma lista:

@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);
    }
}

Lembre-se de quein order to make this work, we need an additional JDK8 Jackson module. Caso contrário, a classeOptional não será serializada / desserializada corretamente. A versão mais recente dejackson-datatype-jdk8 pode ser baixada do Maven Central.

4. Testando a API

4.1. Configurando a classe de teste

Para testar nossa API, criaremos uma classe de teste anotada com@SpringBootTest que usa a classeAuthorizationServerApplication para ler a configuração do aplicativo.

Para testar uma API segura com suporte a teste Spring MVC, precisamos injetar os beansWebAppplicationContexteSpring Security Filter Chain. Vamos usá-los para obter uma instânciaMockMvc antes que os testes sejam executados:

@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. Obtendo um token de acesso

Simplificando, APIs protegidas comOAuth2expects to receive a the Authorization header com um valor deBearer <access_token>.

Para enviar o cabeçalhoAuthorization necessário, primeiro precisamos obter um token de acesso válido fazendo uma solicitação POST para o endpoint/oauth/token. Este endpoint requer uma autenticação HTTP Basic, com a id e o segredo do cliente OAuth, e uma lista de parâmetros especificandoclient_id,grant_type,username epassword.

Usando o suporte de teste Spring MVC, os parâmetros podem ser agrupados em umMultiValueMape a autenticação do cliente pode ser enviada usando o métodohttpBasic.

Let’s create a method that sends a POST request to obtain the token e lê o valoraccess_token da resposta 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. Teste de solicitações GET e POST

O token de acesso pode ser adicionado a uma solicitação usando o métodoheader(“Authorization”, “Bearer “+ accessToken).

Vamos tentar acessar um de nossos mapeamentos protegidos sem um cabeçalhoAuthorization e verificar se recebemos um código de statusunauthorized:

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

Especificamos que apenas usuários com a função ADMIN podem acessar o URL/employee. Vamos criar um teste em que obtemos um token de acesso para um usuário com a funçãoUSER e verificamos se recebemos um código de statusforbidden:

@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());
}

A seguir, vamos testar nossa API usando um token de acesso válido, enviando uma solicitação POST para criar um objetoEmployee e uma solicitação GET para ler o objeto criado:

@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. Conclusão

Neste tutorial rápido, demonstramos como podemos testar uma API protegida por OAuth usando o suporte de teste do Spring MVC.

O código-fonte completo dos exemplos pode ser encontrado emGitHub project.

Para executar o teste, o projeto possui um perfilmvc que pode ser executado usando o comandomvn clean install -Pmvc.