You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
digital-rmb-backend/src/test/java/com/yau/digitalrmb/security/UserControllerTest.java

153 lines
6.8 KiB
Java

package com.yau.digitalrmb.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jayway.jsonpath.JsonPath;
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.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
class UserControllerTest {
private static final String SECRET = "local-token-sso-test-secret-key-123456";
@Autowired
private MockMvc mvc;
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
void tokenLoginSynchronizesCrossPlatformUserAndReturnsLocalToken() throws Exception {
String response = mvc.perform(post("/api/user/login")
.param("TOKEN", crossPlatformToken()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data.accessToken").isNotEmpty())
.andExpect(jsonPath("$.data.tokenType").value("Bearer"))
.andExpect(jsonPath("$.data.username").value("cross001"))
.andExpect(jsonPath("$.data.studentId").value("cross001"))
.andExpect(jsonPath("$.data.name").value("Cross Student"))
.andExpect(jsonPath("$.data.roleId").value(4))
.andExpect(jsonPath("$.data.schoolId").value("610000"))
.andExpect(jsonPath("$.data.schoolName").value("Yan'an University"))
.andExpect(jsonPath("$.data.classId").value("202401"))
.andExpect(jsonPath("$.data.className").value("Class 1"))
.andExpect(jsonPath("$.data.collegeId").value("100"))
.andExpect(jsonPath("$.data.majorId").value("101"))
.andReturn().getResponse().getContentAsString();
String localUserId = JsonPath.read(response, "$.data.userId");
assertThat(UUID.fromString(localUserId).toString()).isEqualTo(localUserId);
assertThat(jdbcTemplate.queryForObject(
"SELECT zy_user_id FROM sys_user WHERE user_id = ?", String.class, localUserId))
.isEqualTo("9001");
}
@Test
void parameterLoginMatchesCrossTrainingPlatformEndpointShape() throws Exception {
mvc.perform(post("/api/user/login")
.param("username", "tzs001")
.param("passwordEncode", "123qwe"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data.userId")
.value("00000000-0000-0000-0000-000000000487"))
.andExpect(jsonPath("$.data.username").value("tzs001"))
.andExpect(jsonPath("$.data.accessToken").isNotEmpty());
}
@Test
void legacyPlaintextPasswordLoginUpgradesPasswordToBcrypt() throws Exception {
String userId = "00000000-0000-0000-0000-000000000221";
jdbcTemplate.update("INSERT INTO sys_user "
+ "(user_id, student_id, password, user_name, role_id, is_deleted) "
+ "VALUES (?, ?, ?, ?, ?, ?)",
userId, "legacy001", "123qwe", "Legacy Student", 4, 0);
mvc.perform(post("/api/user/login")
.param("username", "legacy001")
.param("passwordEncode", "123qwe"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data.userId").value(userId))
.andExpect(jsonPath("$.data.accessToken").isNotEmpty());
String upgradedPassword = jdbcTemplate.queryForObject(
"SELECT password FROM sys_user WHERE user_id = ?", String.class, userId);
assertThat(upgradedPassword).startsWith("$2").isNotEqualTo("123qwe");
}
@Test
void tokenLoginAcceptsLegacyTokenWithoutExpirationOrPassword() throws Exception {
Map<String, Object> claims = baseClaims();
claims.remove("password");
claims.remove("exp");
mvc.perform(post("/api/user/login").param("TOKEN", signedToken(claims)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data.username").value("cross001"))
.andExpect(jsonPath("$.data.accessToken").isNotEmpty());
}
private String crossPlatformToken() throws Exception {
return signedToken(baseClaims());
}
private Map<String, Object> baseClaims() {
Map<String, Object> claims = new HashMap<String, Object>();
claims.put("userId", 9001);
claims.put("username", "cross001");
claims.put("password", "token-password");
claims.put("name", "Cross Student");
claims.put("roleid", 4);
claims.put("schoolId", 610000);
claims.put("school", "Yan'an University");
claims.put("collegeId", 100);
claims.put("collegeName", "Computer College");
claims.put("majorId", 101);
claims.put("major", "Software Engineering");
claims.put("classId", 202401);
claims.put("class", "Class 1");
claims.put("studentNo", "cross001");
claims.put("exp", Instant.now().plusSeconds(300).getEpochSecond());
return claims;
}
private String signedToken(Map<String, Object> claims) throws Exception {
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> header = new HashMap<String, Object>();
header.put("alg", "HS256");
header.put("typ", "JWT");
String unsigned = encode(mapper.writeValueAsBytes(header))
+ "." + encode(mapper.writeValueAsBytes(claims));
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(SECRET.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
return unsigned + "." + encode(mac.doFinal(unsigned.getBytes(StandardCharsets.US_ASCII)));
}
private String encode(byte[] value) {
return Base64.getUrlEncoder().withoutPadding().encodeToString(value);
}
}