1. 概述
在本文中,我們將演示如何使用 OAuth 與 Spring MVC 測試支持測試受 OAuth 保護的 API。
注意:本文使用了 Spring OAuth 遺留項目。
2. 授權和資源服務器
為了瞭解如何設置授權和資源服務器,請參考之前的文章: Spring REST API + OAuth2 + AngularJS。
我們的授權服務器使用 JdbcTokenStore,並定義了一個客户端,其 id 為 “fooClientIdPassword”,密碼為 “secret”,並支持 password 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;
// 標準構造函數,getter,setter
}
接下來,讓我們定義一個帶有兩個請求映射的控制器,用於獲取和將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類將無法正確序列化/反序列化。 您可以從 Maven Central 下載最新版本的 jackson-datatype-jdk8。
4. Testing the API
4.1. Setting Up the Test Class
為了測試我們的API,我們將創建一個帶有@SpringBootTest註解的測試類,該類使用AuthorizationServerApplication類來讀取應用程序配置。
對於使用Spring MVC測試支持測試受保護的API,我們需要注入WebAppplicationContext和Spring 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
簡單來説,使用OAuth2的API期望收到帶有Authorization頭的值Bearer <access_token>。
為了發送所需的Authorization頭,我們首先需要通過向/oauth/token端點發出POST請求來獲取有效的訪問令牌。 此端點需要HTTP Basic身份驗證,包括id和secret的OAuth客户端,以及指定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. Testing GET and POST Requests
訪問令牌可以添加到請求中使用header(“Authorization”, “Bearer “+ accessToken)方法。
讓我們嘗試在沒有Authorization頭的情況下訪問我們的受保護映射,並驗證我們是否收到unauthorized狀態碼:
@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.