Testing an OAuth Secured API with Spring MVC (using the Spring Security OAuth legacy stack) – 用Spring MVC测试OAuth安全的API(使用Spring Security OAuth遗留栈)

最后修改: 2017年 3月 17日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this article, we’re going to show how we can test an API which is secured using OAuth with the Spring MVC test support.

在这篇文章中,我们将展示如何在Spring MVC测试支持下测试使用OAuth安全的API

Note: this article is using the Spring OAuth legacy project.

注意:本文使用的是Spring OAuth遗留项目

2. Authorization and Resource Server

2.授权和资源服务器

For a tutorial on how to setup an authorization and resource server, look through this previous article: Spring REST API + OAuth2 + AngularJS.

关于如何设置授权和资源服务器的教程,请翻阅之前的这篇文章。Spring REST API + OAuth2 + AngularJS

Our authorization server uses JdbcTokenStore and defined a client with id “fooClientIdPassword” and password “secret”, and supports the password grant type.

我们的授权服务器使用JdbcTokenStore,并定义了一个客户,其ID“fooClientIdPassword”和密码“secret”,并支持password授予类型。

The resource server restricts the /employee URL to the ADMIN role.

资源服务器将/employee URL限制为ADMIN角色。

Starting with Spring Boot version 1.5.0 the security adapter takes priority over the OAuth resource adapter, so in order to reverse the order, we have to annotate the WebSecurityConfigurerAdapter class with @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER).

从Spring Boot 1.5.0版本开始,安全适配器的优先级高于OAuth资源适配器,因此为了颠倒顺序,我们必须在WebSecurityConfigurerAdapter类中注解@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)

Otherwise, Spring will attempt to access requested URLs based on the Spring Security rules instead of Spring OAuth rules, and we would receive a 403 error when using token authentication.

否则,Spring会根据Spring Security规则而不是Spring OAuth规则尝试访问请求的URL,在使用令牌认证时,我们会收到一个403错误。

3. Defining a Sample API

3.定义一个样本API

First, let’s create a simple POJO called Employee with two properties that we will manipulate through the API:

首先,让我们创建一个简单的POJO,名为Employee,有两个属性,我们将通过API进行操作。

public class Employee {
    private String email;
    private String name;
    
    // standard constructor, getters, setters
}

Next, let’s define a controller with two request mappings, for getting and saving an Employee object to a list:

接下来,让我们定义一个有两个请求映射的控制器,用于获取和保存一个Employee对象到列表中。

@Controller
public class EmployeeController {

    private List<Employee> employees = new ArrayList<>();

    @GetMapping("/employee")
    @ResponseBody
    public Optional<Employee> 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);
    }
}

Keep in mind that in order to make this work, we need an additional JDK8 Jackson module. Otherwise, the Optional class will not be serialized/deserialized properly. The latest version of jackson-datatype-jdk8 can be downloaded from Maven Central.

请记住,为了使其工作,我们需要一个额外的JDK8 Jackson模块。否则,Optional类将不能被正确序列化/反序列化。最新版本的jackson-datatype-jdk8可以从Maven中心下载。

4. Testing the API

4.测试API

4.1. Setting Up the Test Class

4.1.设置测试类

To test our API, we will create a test class annotated with @SpringBootTest that uses the AuthorizationServerApplication class to read the application configuration.

为了测试我们的API,我们将创建一个用@SpringBootTest注释的测试类,该类使用AuthorizationServerApplication类来读取应用程序配置。

For testing a secured API with Spring MVC test support, we need to inject the WebAppplicationContext and Spring Security Filter Chain beans. We’ll use these to obtain a MockMvc instance before the tests are run:

对于使用Spring MVC测试支持测试安全的API,我们需要注入WebAppplicationContextSpring Security Filter Chain Bean。我们将使用这些来在测试运行前获得一个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. Obtaining an Access Token

4.2.获得访问令牌

Simply put, an APIs secured with OAuth2 expects to receive a the Authorization header with a value of Bearer <access_token>.

简单地说,用OAuth2担保的API 希望收到一个Authorization,值为Bearer <access_token>

In order to send the required Authorization header, we first need to obtain a valid access token by making a POST request to the /oauth/token endpoint. This endpoint requires an HTTP Basic authentication, with the id and secret of the OAuth client, and a list of parameters specifying the client_id, grant_type, username, and password.

为了发送所需的Authorization头,我们首先需要向/oauth/token端点发出POST请求,从而获得一个有效的访问令牌。这个端点需要一个HTTP Basic认证,包括OAuth客户端的id和secret,以及指定client_idgrant_typeusernamepassword的参数列表。

Using Spring MVC test support, the parameters can be wrapped in a MultiValueMap and the client authentication can be sent using the httpBasic method.

使用Spring MVC测试支持,参数可以被包裹在一个MultiValueMap中,客户端认证可以使用httpBasic方法发送。

Let’s create a method that sends a POST request to obtain the token and reads the access_token value from the JSON response:

让我们创建一个方法,发送一个POST请求以获取令牌,并从JSON响应中读取access_token值。

private String obtainAccessToken(String username, String password) throws Exception {
 
    MultiValueMap<String, String> 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. Testing GET and POST Requests

4.3.测试GET和POST请求

The access token can be added to a request using the header(“Authorization”, “Bearer “+ accessToken) method.

访问令牌可以使用header(“Authorization”, “Bearer “+ accessToken)方法添加到请求中。

Let’s attempt to access one of our secured mappings without an Authorization header and verify that we receive an unauthorized status code:

让我们尝试访问一个没有Authorization头的安全映射,并验证我们收到一个unauthorized状态代码。

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

We have specified that only users with a role of ADMIN can access the /employee URL. Let’s create a test in which we obtain an access token for a user with USER role and verify that we receive a forbidden status code:

我们已经指定,只有角色为ADMIN的用户可以访问/employee URL。让我们创建一个测试,在这个测试中,我们获得一个具有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", "jim@yahoo.com"))
      .andExpect(status().isForbidden());
}

Next, let’s test our API using a valid access token, by sending a POST request to create an Employee object, then a GET request to read the object created:

接下来,让我们使用一个有效的访问令牌来测试我们的API,通过发送POST请求来创建一个Employee对象,然后发送GET请求来读取创建的对象。

@Test
public void givenToken_whenPostGetSecureRequest_thenOk() throws Exception {
    String accessToken = obtainAccessToken("admin", "nimda");

    String employeeString = "{\"email\":\"jim@yahoo.com\",\"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", "jim@yahoo.com")
      .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

5.结论

In this quick tutorial, we have demonstrated how we can test an OAuth-secured API using the Spring MVC test support.

在这个快速教程中,我们已经演示了如何使用Spring MVC测试支持来测试一个OAuth安全的API。

The full source code of the examples can be found in the GitHub project.

示例的完整源代码可以在GitHub项目中找到。

To run the test, the project has an mvc profile that can be executed using the command mvn clean install -Pmvc.

为了运行测试,该项目有一个mvc配置文件,可以使用mvn clean install -Pmvc.命令执行。