Test d’une API sécurisée OAuth avec Spring MVC

Test d'une API sécurisée OAuth avec Spring MVC

1. Vue d'ensemble

Dans cet article, nous allons montrer comment nous pouvonstest an API which is secured using OAuth with the Spring MVC test support.

2. Serveur d'autorisation et de ressources

Pour un didacticiel sur la configuration d'un serveur d'autorisation et de ressources, parcourez cet article précédent:Spring REST API + OAuth2 + AngularJS.

Notre serveur d'autorisation utiliseJdbcTokenStore et a défini un client avec l'ID“fooClientIdPassword” et le mot de passe“secret”, et prend en charge le type d'octroipassword.

Le serveur de ressources restreint l'URL de/employee au rôle ADMIN.

À partir de Spring Boot version 1.5.0, l'adaptateur de sécurité est prioritaire sur l'adaptateur de ressources OAuth.Par conséquent, pour inverser l'ordre, nous devons annoter la classeWebSecurityConfigurerAdapter avec@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER).

Sinon, Spring tentera d'accéder aux URL demandées en fonction des règles de sécurité Spring au lieu des règles Spring OAuth. Une erreur 403 s'afficherait lors de l'utilisation de l'authentification par jeton.

3. Définition d'un exemple d'API

Tout d'abord, créons un POJO simple appeléEmployee avec deux propriétés que nous manipulerons via l'API:

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

    // standard constructor, getters, setters
}

Ensuite, définissons un contrôleur avec deux mappages de requêtes, pour obtenir et enregistrer un objetEmployee dans une liste:

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

Gardez à l'esprit quein order to make this work, we need an additional JDK8 Jackson module. Sinon, la classeOptional ne sera pas sérialisée / désérialisée correctement. La dernière version dejackson-datatype-jdk8 peut être téléchargée depuis Maven Central.

4. Tester l'API

4.1. Configuration de la classe de test

Pour tester notre API, nous allons créer une classe de test annotée avec@SpringBootTest qui utilise la classeAuthorizationServerApplication pour lire la configuration de l'application.

Pour tester une API sécurisée avec la prise en charge des tests Spring MVC, nous devons injecter les beansWebAppplicationContext etSpring Security Filter Chain. Nous les utiliserons pour obtenir une instance deMockMvc avant l'exécution des tests:

@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. Obtention d'un jeton d'accès

En termes simples, une API sécurisée avecOAuth2expects to receive a the Authorization header avec une valeur deBearer <access_token>.

Afin d'envoyer l'en-têteAuthorization requis, nous devons d'abord obtenir un jeton d'accès valide en faisant une requête POST au point de terminaison/oauth/token. Ce point de terminaison nécessite une authentification HTTP Basic, avec l'ID et le secret du client OAuth, et une liste de paramètres spécifiant lesclient_id,grant_type,username etpassword.

À l'aide de la prise en charge des tests Spring MVC, les paramètres peuvent être enveloppés dans unMultiValueMap et l'authentification du client peut être envoyée à l'aide de la méthodehttpBasic.

Let’s create a method that sends a POST request to obtain the token et lit la valeuraccess_token à partir de la réponse 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. Test des requêtes GET et POST

Le jeton d'accès peut être ajouté à une requête à l'aide de la méthodeheader(“Authorization”, “Bearer “+ accessToken).

Essayons d'accéder à l'un de nos mappages sécurisés sans en-têteAuthorization et vérifions que nous recevons un code d'étatunauthorized:

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

Nous avons précisé que seuls les utilisateurs ayant un rôle ADMIN peuvent accéder à l'URL de/employee. Créons un test dans lequel nous obtenons un jeton d'accès pour un utilisateur avec le rôleUSER et vérifions que nous recevons un code d'étatforbidden:

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

Ensuite, testons notre API à l'aide d'un jeton d'accès valide, en envoyant une requête POST pour créer un objetEmployee, puis une requête GET pour lire l'objet créé:

@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. Conclusion

Dans ce didacticiel rapide, nous avons montré comment tester une API sécurisée par OAuth à l'aide du support de test Spring MVC.

Le code source complet des exemples peut être trouvé dans lesGitHub project.

Pour exécuter le test, le projet a un profilmvc qui peut être exécuté à l'aide de la commandemvn clean install -Pmvc.