1. 概述
在本文中,我們將演示如何使用 Spring MVC 測試支持 測試使用 OAuth 進行安全保護的 API。
注意:本文使用了 Spring OAuth legacy 項目。
2. 授權和資源服務器
對於如何設置授權和資源服務器的教程,請參考之前的文章:Spring REST API + OAuth2 + AngularJS。
我們的授權服務器使用 JdbcTokenStore,並定義了一個客户端,其id為 “fooClientIdPassword”,密碼為 “secret”,並支持 密碼 grant 類型。
資源服務器限制了 /employee URL 僅供 ADMIN 角色訪問。
從 Spring Boot 1.5.0 版本開始,安全適配器優先於 OAuth 資源適配器,因此為了反轉順序,我們需要使用 @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) 註解標記 WebSecurityConfigurerAdapter 類。
否則,Spring 將嘗試根據 Spring Security 規則訪問請求的 URL,而不是 Spring OAuth 規則,並且在使用令牌認證時,我們將會收到 403 錯誤。
3. 定義一個示例API
首先,讓我們創建一個簡單的POJO,名為 Employee,其中包含兩個我們將通過API進行操作的屬性:
public class Employee {
private String email;
private String name;
// standard constructor, getters, setters
}接下來,我們定義一個帶有兩個請求映射的控制器,用於獲取和將 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);
}
}
請注意,為了確保其正常運行,我們需要一個額外的 JDK8 Jackson 模塊。 否則,Optional 類將無法正確序列化/反序列化。 最新版本的 jackson-datatype-jdk8 可從 Maven Central 下載。
4. 測試 API
This section covers the testing of the API. It outlines the different types of tests that should be performed to ensure the API is functioning correctly and reliably.
4.1 Types of API Tests
There are several types of API tests that should be conducted:
- Unit Tests: These tests verify the functionality of individual components or modules within the API.
- Integration Tests: These tests ensure that different components of the API work together correctly.
- Functional Tests: These tests verify that the API meets the specified functional requirements.
- Performance Tests: These tests evaluate the API's performance under various load conditions.
- Security Tests: These tests identify and address potential security vulnerabilities in the API.
4.2 Testing Tools
Several tools can be used to test the API, including:
- Postman
- Swagger Inspector
- REST-assured
- curl
4.3 Example Test Case
// This function retrieves user data from the database.
function getUser(userId) {
// Perform database query here
// Return user data if found, otherwise return null
return null;
}
4.1. 設置測試類
為了測試我們的API,我們將創建一個使用<em @SpringBootTest>註解並使用類來讀取應用程序配置的測試類。
對於使用Spring MVC測試支持測試受保護的API,我們需要注入和 Bean。 我們將使用它們在測試執行前獲取實例:
@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 期望收到帶有值為 Bearer <access_token> 的 Authorization 標頭。
為了發送所需的 Authorization 標頭,我們首先需要通過向 /oauth/token 端點發出 POST 請求來獲取一個有效的訪問令牌。 此端點需要 HTTP Basic 身份驗證,使用 OAuth 客户端的 id 和 secret,以及指定 client_id、grant_type、username 和 password 的參數列表。
使用 Spring MVC 測試支持,參數可以封裝在 MultiValueMap 中,客户端身份驗證可以使用 httpBasic 方法。
讓我們創建一個方法來發送 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. 測試 GET 和 POST 請求
可以使用 header(“Authorization”, “Bearer “+ accessToken) 方法將訪問令牌添加到請求中。
讓我們嘗試在沒有 Authorization 標頭的情況下訪問一個受保護的映射,並驗證我們是否收到 未授權 狀態碼:
@Test
public void givenNoToken_whenGetSecureRequest_thenUnauthorized() throws Exception {
mockMvc.perform(get("/employee")
.param("email", EMAIL))
.andExpect(status().isUnauthorized());
}我們已經指定只有具有 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", "[email protected]"))
.andExpect(status().isForbidden());
}接下來,讓我們使用有效的訪問令牌測試我們的 API,通過向創建 Employee 對象發送 POST 請求,然後發送 GET 請求讀取創建的對象:
@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. 結論
在本快速教程中,我們演示瞭如何使用 Spring MVC 測試支持測試 OAuth 保護的 API。
要運行測試,項目包含一個 mvc 配置文件,可以使用命令 mvn clean install -Pmvc 運行。