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.
153 lines
6.8 KiB
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);
|
|
}
|
|
}
|