diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/config/BackDoorLoginProperties.java b/ruoyi-admin/src/main/java/com/ruoyi/web/config/BackDoorLoginProperties.java index 20914a3..ba3582e 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/config/BackDoorLoginProperties.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/config/BackDoorLoginProperties.java @@ -1,5 +1,9 @@ package com.ruoyi.web.config; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; + +import org.apache.commons.lang3.StringUtils; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @@ -16,6 +20,10 @@ public class BackDoorLoginProperties private boolean enabled; private int maxUsers = DEFAULT_MAX_USERS; private String portalBasePath = "/shihezi"; + /** + * 测试授权口令。未配置时,小程序测试授权接口应拒绝所有请求。 + */ + private String password; public boolean isEnabled() { @@ -47,6 +55,31 @@ public class BackDoorLoginProperties this.portalBasePath = portalBasePath; } + public String getPassword() + { + return password; + } + + public void setPassword(String password) + { + this.password = password; + } + + /** + * 验证小程序测试授权口令。 + * + * 未设置口令时必须拒绝访问,避免某个测试 profile 误开启后门后暴露用户列表。 + */ + public boolean matchesPassword(String candidate) + { + if (StringUtils.isBlank(password) || candidate == null) + { + return false; + } + return MessageDigest.isEqual(password.getBytes(StandardCharsets.UTF_8), + candidate.trim().getBytes(StandardCharsets.UTF_8)); + } + private String normalizeBasePath(String value, String defaultValue) { if (value == null || value.trim().isEmpty()) diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/BackDoorLoginController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/BackDoorLoginController.java index f9e446c..4eb492e 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/BackDoorLoginController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/BackDoorLoginController.java @@ -100,12 +100,16 @@ public class BackDoorLoginController * 与 /backDoor 一样仅在 backdoor.login.enabled=true 时存在,且只返回已关联 * AppUser 的最小化展示字段。 */ - @GetMapping("/app/backDoor/users") - public AjaxResult appBackDoorUsers( - @RequestParam(value = "keyword", required = false) String keyword) + @PostMapping("/app/backDoor/users") + public AjaxResult appBackDoorUsers(@RequestBody(required = false) JSONObject body) { + if (!matchesAppBackDoorPassword(body)) + { + return AjaxResult.error("测试授权密码错误"); + } try { + String keyword = body == null ? null : body.getString("keyword"); return AjaxResult.success(backDoorLoginService.listAvailableAppUsers(keyword, properties.getMaxUsers())); } catch (Exception e) @@ -121,6 +125,10 @@ public class BackDoorLoginController @PostMapping("/app/backDoor/login") public AjaxResult appBackDoorLogin(@RequestBody(required = false) JSONObject body) { + if (!matchesAppBackDoorPassword(body)) + { + return AjaxResult.error("测试授权密码错误"); + } Long userId = body == null ? null : body.getLong("userId"); try { @@ -142,6 +150,11 @@ public class BackDoorLoginController } } + private boolean matchesAppBackDoorPassword(JSONObject body) + { + return properties.matchesPassword(body == null ? null : body.getString("password")); + } + private ResponseEntity renderUserList(String keyword, int page) { try diff --git a/ruoyi-admin/src/main/resources/application-test.yml b/ruoyi-admin/src/main/resources/application-test.yml index bf9ca44..143fbf3 100644 --- a/ruoyi-admin/src/main/resources/application-test.yml +++ b/ruoyi-admin/src/main/resources/application-test.yml @@ -151,3 +151,4 @@ backdoor: enabled: true max-users: 100 portal-base-path: /shihezi + password: zkr2024 diff --git a/ruoyi-admin/src/test/java/com/ruoyi/web/controller/system/BackDoorLoginControllerTest.java b/ruoyi-admin/src/test/java/com/ruoyi/web/controller/system/BackDoorLoginControllerTest.java index c874c59..176a93a 100644 --- a/ruoyi-admin/src/test/java/com/ruoyi/web/controller/system/BackDoorLoginControllerTest.java +++ b/ruoyi-admin/src/test/java/com/ruoyi/web/controller/system/BackDoorLoginControllerTest.java @@ -8,6 +8,7 @@ import com.ruoyi.common.core.domain.entity.AppUser; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.web.config.BackDoorLoginProperties; import com.ruoyi.web.service.BackDoorLoginService; +import com.ruoyi.web.service.BackDoorLoginService.AppLoginUser; import com.ruoyi.web.service.BackDoorLoginService.AppLoginSession; import com.ruoyi.web.service.BackDoorLoginService.LoginSession; import org.junit.jupiter.api.BeforeEach; @@ -37,6 +38,7 @@ class BackDoorLoginControllerTest void setUp() { BackDoorLoginProperties properties = new BackDoorLoginProperties(); + properties.setPassword("zkr2024"); controller = new BackDoorLoginController(loginService, new BackDoorPageRenderer(), properties); } @@ -94,6 +96,7 @@ class BackDoorLoginControllerTest when(loginService.loginApp(8L)).thenReturn(new AppLoginSession(sysUser, appUser, "site-token")); JSONObject body = new JSONObject(); body.put("userId", 8L); + body.put("password", "zkr2024"); AjaxResult response = controller.appBackDoorLogin(body); @@ -102,4 +105,54 @@ class BackDoorLoginControllerTest assertEquals(66L, response.get("appUserId")); assertEquals("1", response.get("isCompanyUser")); } + + @Test + void appBackDoorUsersRequiresPasswordBeforeLoadingUserData() + { + JSONObject body = new JSONObject(); + body.put("keyword", "tester"); + body.put("password", "wrong-password"); + + AjaxResult response = controller.appBackDoorUsers(body); + + assertEquals(500, response.get("code")); + assertEquals("测试授权密码错误", response.get("msg")); + verify(loginService, never()).listAvailableAppUsers(org.mockito.ArgumentMatchers.any(), + org.mockito.ArgumentMatchers.anyInt()); + } + + @Test + void appBackDoorUsersReturnsLinkedAppUsersAfterPasswordVerification() + { + SysUser sysUser = new SysUser(); + sysUser.setUserId(8L); + sysUser.setAppUserId(66L); + sysUser.setUserName("tester"); + sysUser.setNickName("测试用户"); + when(loginService.listAvailableAppUsers("tester", 100)) + .thenReturn(Collections.singletonList(new AppLoginUser(sysUser))); + JSONObject body = new JSONObject(); + body.put("keyword", "tester"); + body.put("password", "zkr2024"); + + AjaxResult response = controller.appBackDoorUsers(body); + + assertEquals(200, response.get("code")); + assertEquals(1, ((java.util.List) response.get("data")).size()); + verify(loginService).listAvailableAppUsers("tester", 100); + } + + @Test + void appBackDoorLoginRequiresPasswordBeforeCreatingToken() + { + JSONObject body = new JSONObject(); + body.put("userId", 8L); + body.put("password", "wrong-password"); + + AjaxResult response = controller.appBackDoorLogin(body); + + assertEquals(500, response.get("code")); + assertEquals("测试授权密码错误", response.get("msg")); + verify(loginService, never()).loginApp(org.mockito.ArgumentMatchers.any()); + } }