1. 概述

本文将演示如何使用Spring MVC测试框架对OAuth安全保护的API进行测试

⚠️ 注意:本文使用的是Spring OAuth旧版项目

2. 授权服务器与资源服务器

关于如何设置授权服务器和资源服务器,可参考之前的文章:Spring REST API + OAuth2 + AngularJS

我们的授权服务器配置要点:

  • 使用 JdbcTokenStore 存储令牌
  • 定义了一个客户端ID为 fooClientIdPassword,密码为 secret
  • 支持 password 授权模式

资源服务器限制 /employee 接口仅允许 ADMIN 角色访问。

⚠️ 踩坑提醒:从Spring Boot 1.5.0开始,安全适配器优先级高于OAuth资源适配器。需要手动调整顺序,在 WebSecurityConfigurerAdapter 类上添加 @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) 注解。否则Spring会优先使用普通安全规则而非OAuth规则,导致令牌认证时返回403错误。

3. 定义示例API

首先创建一个简单的 Employee POJO类:

public class Employee {
    private String email;
    private String name;
    
    // 标准构造函数、getter和setter
}

然后定义控制器,包含两个接口:

  • 通过邮箱查询员工
  • 保存员工信息
@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);
    }
}

关键依赖:需要额外添加 jackson-datatype-jdk8 模块(JDK8 Jackson模块),否则 Optional 类无法正确序列化/反序列化。

4. 测试API

4.1. 测试类配置

创建测试类时需要:

  • 使用 @SpringBootTest 注解
  • 注入 WebApplicationContextSpring 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. 获取访问令牌

OAuth2安全API要求请求头包含:

Authorization: Bearer <access_token>

获取令牌步骤:

  1. /oauth/token 接口发送POST请求
  2. 使用HTTP Basic认证(客户端ID和密码)
  3. 请求参数包含:
    • grant_type
    • client_id
    • username
    • password

实现令牌获取方法:

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. 测试GET和POST请求

令牌通过请求头添加:

.header("Authorization", "Bearer " + accessToken)

测试场景列表:

  1. 无令牌访问(预期401未授权)

    @Test
    public void givenNoToken_whenGetSecureRequest_thenUnauthorized() throws Exception {
     mockMvc.perform(get("/employee")
       .param("email", "test@example.com"))
       .andExpect(status().isUnauthorized());
    }
    
  2. 无效角色访问(USER角色访问ADMIN接口,预期403禁止)

    @Test
    public void givenInvalidRole_whenGetSecureRequest_thenForbidden() throws Exception {
     String accessToken = obtainAccessToken("user1", "pass");
     mockMvc.perform(get("/employee")
       .header("Authorization", "Bearer " + accessToken)
       .param("email", "test@example.com"))
       .andExpect(status().isForbidden());
    }
    
  3. 有效令牌完整流程(创建员工后查询)

    @Test
    public void givenToken_whenPostGetSecureRequest_thenOk() throws Exception {
     String accessToken = obtainAccessToken("admin", "nimda");
    
     String employeeString = "{\"email\":\"jim@example.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@example.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. 总结

本文简单粗暴地演示了如何使用Spring MVC测试框架测试OAuth安全API。完整代码可在GitHub项目获取。

运行测试命令:

mvn clean install -Pmvc

原始标题:Testing an OAuth Secured API with Spring MVC

« 上一篇: Ratpack 介绍