实现登录权限底座
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
package cn.nianxx.thhotel.platform.identity.control;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.matchesPattern;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import cn.nianxx.thhotel.ThHotelApplication;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
@SpringBootTest(
|
||||
classes = ThHotelApplication.class,
|
||||
properties = {
|
||||
"auth.bootstrap.admin.username=m003-admin",
|
||||
"auth.bootstrap.admin.password=Admin@123456",
|
||||
"auth.bootstrap.admin.display-name=系统管理员",
|
||||
"auth.bootstrap.default-hotel-id=HOTEL-TEST",
|
||||
"auth.bootstrap.default-hotel-name=测试酒店",
|
||||
"auth.bootstrap.default-hotel-time-zone=Asia/Bangkok",
|
||||
"auth.session.ttl-minutes=720"
|
||||
})
|
||||
@AutoConfigureMockMvc
|
||||
@ActiveProfiles("test")
|
||||
class AuthControllerTest {
|
||||
|
||||
private static final String UTC_INSTANT_PATTERN = "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?Z$";
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Test
|
||||
void shouldLoginBootstrapAdminAndReturnCurrentUserContext() throws Exception {
|
||||
MvcResult loginResult = mockMvc.perform(post("/api/auth/login")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"username": "m003-admin",
|
||||
"password": "Admin@123456"
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.access_token").value(notNullValue()))
|
||||
.andExpect(jsonPath("$.token_type").value("Bearer"))
|
||||
.andExpect(jsonPath("$.expires_at").value(matchesPattern(UTC_INSTANT_PATTERN)))
|
||||
.andExpect(jsonPath("$.user.username").value("m003-admin"))
|
||||
.andExpect(jsonPath("$.user.display_name").value("系统管理员"))
|
||||
.andExpect(jsonPath("$.user.super_admin").value(true))
|
||||
.andExpect(jsonPath("$.default_hotel_id").value("HOTEL-TEST"))
|
||||
.andExpect(jsonPath("$.hotels[0].hotel_id").value("HOTEL-TEST"))
|
||||
.andExpect(jsonPath("$.hotels[0].hotel_name").value("测试酒店"))
|
||||
.andExpect(jsonPath("$.hotels[0].time_zone").value("Asia/Bangkok"))
|
||||
.andExpect(jsonPath("$.permissions").value(contains(
|
||||
"SOURCE_MESSAGE_READ",
|
||||
"SOURCE_MESSAGE_ORIGINAL_READ",
|
||||
"RESERVATION_ORDER_READ",
|
||||
"RESERVATION_TASK_READ",
|
||||
"RESERVATION_TASK_EDIT",
|
||||
"RESERVATION_TASK_CONFIRM",
|
||||
"RESERVATION_OPERA_SIM_EXECUTE",
|
||||
"RESERVATION_AUDIT_READ",
|
||||
"HOTEL_SWITCH",
|
||||
"SYSTEM_AUTH_READ",
|
||||
"SYSTEM_USER_MANAGE",
|
||||
"SYSTEM_ROLE_MANAGE",
|
||||
"SYSTEM_MENU_MANAGE",
|
||||
"HOTEL_MANAGE",
|
||||
"SYSTEM_DEBUG_EML_RUN")))
|
||||
.andExpect(jsonPath("$.menus[0].menu_code").value("RESERVATION_ORDERS"))
|
||||
.andExpect(jsonPath("$.menus[0].route_path").value("/reservation/orders"))
|
||||
.andExpect(jsonPath("$.menus[0].icon_key").value("pi pi-list"))
|
||||
.andExpect(jsonPath("$.menus[1].menu_code").value("RESERVATION_TASKS"))
|
||||
.andExpect(jsonPath("$.menus[1].route_path").value("/reservation/tasks"))
|
||||
.andExpect(jsonPath("$.menus[1].icon_key").value("pi pi-check-square"))
|
||||
.andExpect(jsonPath("$.menus[2].menu_code").value("DEBUG_EML_SUPERAGENT"))
|
||||
.andExpect(jsonPath("$.menus[2].route_path").value("/debug/eml-superagent"))
|
||||
.andExpect(jsonPath("$.menus[2].icon_key").value("pi pi-upload"))
|
||||
.andReturn();
|
||||
|
||||
String token = tokenFrom(loginResult);
|
||||
|
||||
mockMvc.perform(get("/api/auth/me")
|
||||
.header("Authorization", "Bearer " + token))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.user.username").value("m003-admin"))
|
||||
.andExpect(jsonPath("$.default_hotel_id").value("HOTEL-TEST"))
|
||||
.andExpect(jsonPath("$.menus[2].menu_code").value("DEBUG_EML_SUPERAGENT"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectInvalidLoginWithoutLeakingAccountState() throws Exception {
|
||||
mockMvc.perform(post("/api/auth/login")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"username": "m003-admin",
|
||||
"password": "wrong-password"
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isUnauthorized())
|
||||
.andExpect(jsonPath("$.error_code").value("AUTH_INVALID_CREDENTIALS"))
|
||||
.andExpect(jsonPath("$.message").value("用户名或密码错误。"))
|
||||
.andExpect(content().string(not("DISABLED")))
|
||||
.andExpect(content().string(not("not found")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRequireTokenForAuthMeAndRevokeSessionOnLogout() throws Exception {
|
||||
mockMvc.perform(get("/api/auth/me"))
|
||||
.andExpect(status().isUnauthorized())
|
||||
.andExpect(jsonPath("$.error_code").value("AUTH_TOKEN_REQUIRED"));
|
||||
|
||||
String token = tokenFrom(login());
|
||||
|
||||
mockMvc.perform(post("/api/auth/logout")
|
||||
.header("Authorization", "Bearer " + token))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.success").value(true));
|
||||
|
||||
mockMvc.perform(post("/api/auth/logout")
|
||||
.header("Authorization", "Bearer " + token))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.success").value(true));
|
||||
|
||||
mockMvc.perform(get("/api/auth/me")
|
||||
.header("Authorization", "Bearer " + token))
|
||||
.andExpect(status().isUnauthorized())
|
||||
.andExpect(jsonPath("$.error_code").value("AUTH_SESSION_INVALID"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldKeepExistingBusinessEndpointsCompatibleWhenTokenIsMissingOrInvalid() throws Exception {
|
||||
mockMvc.perform(get("/api/reservation/orders")
|
||||
.param("hotel_id", "HOTEL-TEST")
|
||||
.param("page_num", "1")
|
||||
.param("page_size", "1"))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
mockMvc.perform(get("/api/reservation/orders")
|
||||
.header("Authorization", "Bearer invalid-token")
|
||||
.param("hotel_id", "HOTEL-TEST")
|
||||
.param("page_num", "1")
|
||||
.param("page_size", "1"))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
private MvcResult login() throws Exception {
|
||||
return mockMvc.perform(post("/api/auth/login")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"username": "m003-admin",
|
||||
"password": "Admin@123456"
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
private String tokenFrom(MvcResult result) throws Exception {
|
||||
JsonNode json = objectMapper.readTree(result.getResponse().getContentAsString());
|
||||
return json.path("access_token").asText();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user