Testen einer OAuth-gesicherten API mit Spring MVC

Testen einer OAuth Secured API mit Spring MVC

1. Überblick

In diesem Artikel zeigen wir, wie wirtest an API which is secured using OAuth with the Spring MVC test support können.

2. Autorisierungs- und Ressourcenserver

Ein Tutorial zum Einrichten eines Autorisierungs- und Ressourcenservers finden Sie in diesem vorherigen Artikel:Spring REST API + OAuth2 + AngularJS.

Unser Autorisierungsserver verwendetJdbcTokenStore und definiert einen Client mit der ID“fooClientIdPassword” und dem Kennwort“secret” und unterstützt den Grant-Typpassword.

Der Ressourcenserver beschränkt die URL von/employeeauf die ADMIN-Rolle.

Ab Spring Boot Version 1.5.0 hat der Sicherheitsadapter Vorrang vor dem OAuth-Ressourcenadapter. Um die Reihenfolge umzukehren, müssen wir die KlasseWebSecurityConfigurerAdaptermit@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) versehen.

Andernfalls versucht Spring, auf die angeforderten URLs zuzugreifen, die auf den Spring-Sicherheitsregeln anstelle der Spring-OAuth-Regeln basieren. Bei Verwendung der Tokenauthentifizierung wird ein Fehler 403 angezeigt.

3. Definieren einer Beispiel-API

Zuerst erstellen wir ein einfaches POJO namensEmployee mit zwei Eigenschaften, die wir über die API bearbeiten werden:

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

    // standard constructor, getters, setters
}

Als Nächstes definieren wir einen Controller mit zwei Anforderungszuordnungen, um einEmployee-Objekt abzurufen und in einer Liste zu speichern:

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

Beachten Sie, dassin order to make this work, we need an additional JDK8 Jackson module. Andernfalls wird die KlasseOptionalnicht ordnungsgemäß serialisiert / deserialisiert. Die neueste Version vonjackson-datatype-jdk8 kann von Maven Central heruntergeladen werden.

4. Testen der API

4.1. Einrichten der Testklasse

Um unsere API zu testen, erstellen wir eine mit@SpringBootTest versehene Testklasse, die die KlasseAuthorizationServerApplicationzum Lesen der Anwendungskonfiguration verwendet.

Zum Testen einer gesicherten API mit Spring MVC-Testunterstützung müssen die BeansWebAppplicationContext undSpring Security Filter Chain injiziert werden. Wir werden diese verwenden, um eineMockMvc-Instanz zu erhalten, bevor die Tests ausgeführt werden:

@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. Erhalten eines Zugriffstokens

Einfach ausgedrückt, APIs, die mitOAuth2expects to receive a the Authorization header mit einem Wert vonBearer <access_token> gesichert sind.

Um den erforderlichenAuthorization-Header zu senden, müssen wir zuerst ein gültiges Zugriffstoken erhalten, indem wir eine POST-Anforderung an den/oauth/token-Endpunkt senden. Dieser Endpunkt erfordert eine HTTP-Basisauthentifizierung mit der ID und dem Geheimnis des OAuth-Clients sowie eine Liste von Parametern, dieclient_id,grant_type,username undpassword angeben.

Mithilfe der Spring MVC-Testunterstützung können die Parameter inMultiValueMap eingeschlossen und die Clientauthentifizierung mithilfe derhttpBasic-Methode gesendet werden.

Let’s create a method that sends a POST request to obtain the token und liest den Wert vonaccess_token aus der JSON-Antwort:

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. Testen von GET- und POST-Anfragen

Das Zugriffstoken kann mit der Methodeheader(“Authorization”, “Bearer “+ accessToken) zu einer Anforderung hinzugefügt werden.

Versuchen wir, auf eine unserer gesicherten Zuordnungen ohne den HeaderAuthorizationzuzugreifen, und überprüfen Sie, ob wir den Statuscodeunauthorizederhalten:

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

Wir haben angegeben, dass nur Benutzer mit der Rolle ADMIN auf die URL von/employeezugreifen können. Erstellen wir einen Test, in dem wir ein Zugriffstoken für einen Benutzer mit der Rolle vonUSERerhalten und überprüfen, ob wir den Statuscode vonforbiddenerhalten:

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

Testen Sie als Nächstes unsere API mithilfe eines gültigen Zugriffstokens, indem Sie eine POST-Anforderung zum Erstellen einesEmployee-Objekts und anschließend eine GET-Anforderung zum Lesen des erstellten Objekts senden:

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

In diesem kurzen Tutorial haben wir gezeigt, wie wir eine OAuth-gesicherte API mithilfe der Spring MVC-Testunterstützung testen können.

Den vollständigen Quellcode der Beispiele finden Sie inGitHub project.

Um den Test auszuführen, verfügt das Projekt über einmvc-Profil, das mit dem Befehlmvn clean install -Pmvc. ausgeführt werden kann