Browse Source

fix: enforce strict admin bootstrap idempotency

refactor/r01-structure-performance
Zhang Shun 1 month ago
parent
commit
443a305e91
  1. 16
      mydreamjob-server/src/main/java/com/mydreamjob/auth/AdminBootstrapRunner.java
  2. 33
      mydreamjob-server/src/test/java/com/mydreamjob/auth/AdminBootstrapRunnerTest.java

16
mydreamjob-server/src/main/java/com/mydreamjob/auth/AdminBootstrapRunner.java

@ -66,13 +66,9 @@ public class AdminBootstrapRunner implements ApplicationRunner {
} }
phone = phone.trim(); phone = phone.trim();
// 已有活动 ADMIN:无副作用跳过,绝不创建第二个管理员。 // 配置手机号已被任何用户占用(含 inactive、STUDENT、COMPANY、角色不一致)?
if (userRepository.countByRoleAndActiveTrue(User.Role.ADMIN) > 0) { // 必须先验证该账号,不能被“已有活动 ADMIN”短路:否则一个主角色为 ADMIN、
log.info("admin bootstrap skipped: an active administrator already exists"); // 但角色表缺失/含附加角色/仍有密码的账号会绕过严格幂等判据。
return;
}
// 手机号已被任何用户占用(含 inactive、STUDENT、COMPANY、角色不一致)?
Optional<AdminBootstrapCreator.BootstrapUser> existing = creator.findByPhone(phone); Optional<AdminBootstrapCreator.BootstrapUser> existing = creator.findByPhone(phone);
if (existing.isPresent()) { if (existing.isPresent()) {
// 方案 A:仅当现存账号正是本次初始化配置要求的合法 ADMIN 时幂等成功; // 方案 A:仅当现存账号正是本次初始化配置要求的合法 ADMIN 时幂等成功;
@ -84,6 +80,12 @@ public class AdminBootstrapRunner implements ApplicationRunner {
throw new IllegalStateException(ERROR_PHONE_IN_USE); throw new IllegalStateException(ERROR_PHONE_IN_USE);
} }
// 配置手机号不存在且已有其他活动 ADMIN:无副作用跳过,绝不创建第二个管理员。
if (userRepository.countByRoleAndActiveTrue(User.Role.ADMIN) > 0) {
log.info("admin bootstrap skipped: an active administrator already exists");
return;
}
AdminBootstrapCreator.CreateResult result = creator.createAdmin(phone, properties.getNickname()); AdminBootstrapCreator.CreateResult result = creator.createAdmin(phone, properties.getNickname());
if (result == AdminBootstrapCreator.CreateResult.CONFLICT) { if (result == AdminBootstrapCreator.CreateResult.CONFLICT) {
// 并发初始化:另一个实例刚创建了同一手机号。按方案 A 重新判定: // 并发初始化:另一个实例刚创建了同一手机号。按方案 A 重新判定:

33
mydreamjob-server/src/test/java/com/mydreamjob/auth/AdminBootstrapRunnerTest.java

@ -140,7 +140,6 @@ class AdminBootstrapRunnerTest {
@Test @Test
void phoneBelongingToStudentFails() { void phoneBelongingToStudentFails() {
enableWith(PHONE); enableWith(PHONE);
when(userRepository.countByRoleAndActiveTrue(User.Role.ADMIN)).thenReturn(0L);
when(creator.findByPhone(PHONE)).thenReturn(Optional.of( when(creator.findByPhone(PHONE)).thenReturn(Optional.of(
new AdminBootstrapCreator.BootstrapUser(9L, "STUDENT", true, true, true, List.of("STUDENT")))); new AdminBootstrapCreator.BootstrapUser(9L, "STUDENT", true, true, true, List.of("STUDENT"))));
@ -152,7 +151,6 @@ class AdminBootstrapRunnerTest {
@Test @Test
void phoneBelongingToCompanyFails() { void phoneBelongingToCompanyFails() {
enableWith(PHONE); enableWith(PHONE);
when(userRepository.countByRoleAndActiveTrue(User.Role.ADMIN)).thenReturn(0L);
when(creator.findByPhone(PHONE)).thenReturn(Optional.of( when(creator.findByPhone(PHONE)).thenReturn(Optional.of(
new AdminBootstrapCreator.BootstrapUser(9L, "COMPANY", true, true, true, List.of("COMPANY")))); new AdminBootstrapCreator.BootstrapUser(9L, "COMPANY", true, true, true, List.of("COMPANY"))));
@ -163,7 +161,6 @@ class AdminBootstrapRunnerTest {
@Test @Test
void phoneBelongingToInactiveUserFailsAndIsNotRevived() { void phoneBelongingToInactiveUserFailsAndIsNotRevived() {
enableWith(PHONE); enableWith(PHONE);
when(userRepository.countByRoleAndActiveTrue(User.Role.ADMIN)).thenReturn(0L);
when(creator.findByPhone(PHONE)).thenReturn(Optional.of( when(creator.findByPhone(PHONE)).thenReturn(Optional.of(
new AdminBootstrapCreator.BootstrapUser(9L, "ADMIN", false, true, true, List.of("ADMIN")))); new AdminBootstrapCreator.BootstrapUser(9L, "ADMIN", false, true, true, List.of("ADMIN"))));
@ -171,6 +168,25 @@ class AdminBootstrapRunnerTest {
verify(creator, never()).createAdmin(any(), any()); verify(creator, never()).createAdmin(any(), any());
} }
@Test
void activeAdminMissingAdminRoleCannotBypassStrictIdempotencyCheck() {
assertInvalidExistingAdminRejected(
new AdminBootstrapCreator.BootstrapUser(9L, "ADMIN", true, true, true, List.of()));
}
@Test
void activeAdminWithAdditionalRoleCannotBypassStrictIdempotencyCheck() {
assertInvalidExistingAdminRejected(
new AdminBootstrapCreator.BootstrapUser(
9L, "ADMIN", true, true, true, List.of("ADMIN", "STUDENT")));
}
@Test
void activeAdminWithPasswordCannotBypassStrictIdempotencyCheck() {
assertInvalidExistingAdminRejected(
new AdminBootstrapCreator.BootstrapUser(9L, "ADMIN", true, true, false, List.of("ADMIN")));
}
// ── 正常创建 ─────────────────────────────────────────────── // ── 正常创建 ───────────────────────────────────────────────
@Test @Test
@ -222,7 +238,6 @@ class AdminBootstrapRunnerTest {
@Test @Test
void failureMessagesNeverContainPhone() { void failureMessagesNeverContainPhone() {
enableWith(PHONE); enableWith(PHONE);
when(userRepository.countByRoleAndActiveTrue(User.Role.ADMIN)).thenReturn(0L);
when(creator.findByPhone(PHONE)).thenReturn(Optional.of( when(creator.findByPhone(PHONE)).thenReturn(Optional.of(
new AdminBootstrapCreator.BootstrapUser(9L, "STUDENT", true, true, true, List.of("STUDENT")))); new AdminBootstrapCreator.BootstrapUser(9L, "STUDENT", true, true, true, List.of("STUDENT"))));
@ -234,4 +249,14 @@ class AdminBootstrapRunnerTest {
assertFalse(haystack != null && haystack.contains(needle), assertFalse(haystack != null && haystack.contains(needle),
"输出不得包含手机号"); "输出不得包含手机号");
} }
private void assertInvalidExistingAdminRejected(AdminBootstrapCreator.BootstrapUser existing) {
enableWith(PHONE);
when(creator.findByPhone(PHONE)).thenReturn(Optional.of(existing));
IllegalStateException ex = assertThrows(IllegalStateException.class, () -> runner.run(null));
assertEquals(AdminBootstrapRunner.ERROR_PHONE_IN_USE, ex.getMessage());
verify(creator, never()).createAdmin(any(), any());
}
} }

Loading…
Cancel
Save