feat: Implement pagination and effective account validation for backdoor user login

This commit is contained in:
2026-07-20 20:49:23 +08:00
parent de42645cae
commit 0c1c41772e
14 changed files with 268 additions and 93 deletions

View File

@@ -16,7 +16,6 @@ public class BackDoorLoginProperties
private boolean enabled;
private int maxUsers = DEFAULT_MAX_USERS;
private String portalBasePath = "/shihezi";
private String apiBasePath = "/api/shihezi";
public boolean isEnabled()
{
@@ -48,16 +47,6 @@ public class BackDoorLoginProperties
this.portalBasePath = portalBasePath;
}
public String getApiBasePath()
{
return normalizeBasePath(apiBasePath, "/api/shihezi");
}
public void setApiBasePath(String apiBasePath)
{
this.apiBasePath = apiBasePath;
}
private String normalizeBasePath(String value, String defaultValue)
{
if (value == null || value.trim().isEmpty())

View File

@@ -32,6 +32,7 @@ public class BackDoorLoginController
{
private static final Logger log = LoggerFactory.getLogger(BackDoorLoginController.class);
private static final MediaType HTML_UTF8 = new MediaType("text", "html", StandardCharsets.UTF_8);
private static final int PAGE_SIZE = 8;
private final BackDoorLoginService backDoorLoginService;
private final BackDoorPageRenderer pageRenderer;
@@ -49,11 +50,12 @@ public class BackDoorLoginController
@GetMapping(value = "/backDoor", produces = "text/html;charset=UTF-8")
public ResponseEntity<String> backDoor(
@RequestParam(value = "id", required = false) String id,
@RequestParam(value = "keyword", required = false) String keyword)
@RequestParam(value = "keyword", required = false) String keyword,
@RequestParam(value = "page", required = false) String page)
{
if (StringUtils.isBlank(id))
{
return renderUserList(keyword);
return renderUserList(keyword, parsePage(page));
}
Long userId;
@@ -86,13 +88,14 @@ public class BackDoorLoginController
}
}
private ResponseEntity<String> renderUserList(String keyword)
private ResponseEntity<String> renderUserList(String keyword, int page)
{
try
{
List<SysUser> users = backDoorLoginService.listAvailableUsers(keyword, properties.getMaxUsers());
return html(HttpStatus.OK,
pageRenderer.renderUserList(users, StringUtils.trimToEmpty(keyword), properties.getMaxUsers()));
pageRenderer.renderUserList(users, StringUtils.trimToEmpty(keyword), properties.getMaxUsers(),
page, PAGE_SIZE));
}
catch (Exception e)
{
@@ -101,6 +104,22 @@ public class BackDoorLoginController
}
}
private int parsePage(String page)
{
if (StringUtils.isBlank(page))
{
return 1;
}
try
{
return Math.max(1, Integer.parseInt(page.trim()));
}
catch (NumberFormatException e)
{
return 1;
}
}
private ResponseEntity<String> html(HttpStatus status, String body)
{
return ResponseEntity.status(status)
@@ -109,7 +128,7 @@ public class BackDoorLoginController
.header(HttpHeaders.PRAGMA, "no-cache")
.header("Content-Security-Policy",
"default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; "
+ "connect-src 'self'; img-src data:; form-action 'self'; base-uri 'none'; "
+ "img-src data:; form-action 'self'; base-uri 'none'; "
+ "frame-ancestors 'none'")
.header("X-Content-Type-Options", "nosniff")
.header("X-Frame-Options", "DENY")

View File

@@ -17,6 +17,7 @@ import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import org.springframework.web.util.HtmlUtils;
import org.springframework.web.util.UriUtils;
/**
* 渲染后门用户选择页和登录跳转页。
@@ -38,14 +39,24 @@ public class BackDoorPageRenderer
this.errorTemplate = loadTemplate("backdoor/error.html");
}
public String renderUserList(List<SysUser> users, String keyword, int maxUsers)
public String renderUserList(List<SysUser> users, String keyword, int maxUsers, int requestedPage, int pageSize)
{
List<SysUser> safeUsers = users == null ? Collections.emptyList() : users;
int safePageSize = Math.max(1, pageSize);
int totalPages = Math.max(1, (safeUsers.size() + safePageSize - 1) / safePageSize);
int currentPage = Math.max(1, Math.min(requestedPage, totalPages));
int fromIndex = Math.min((currentPage - 1) * safePageSize, safeUsers.size());
int toIndex = Math.min(fromIndex + safePageSize, safeUsers.size());
List<SysUser> pageUsers = safeUsers.subList(fromIndex, toIndex);
return userListTemplate
.replace("@@ROWS@@", renderRows(safeUsers))
.replace("@@ROWS@@", renderRows(pageUsers))
.replace("@@KEYWORD@@", html(keyword))
.replace("@@COUNT@@", String.valueOf(safeUsers.size()))
.replace("@@MAX_USERS@@", String.valueOf(maxUsers));
.replace("@@MAX_USERS@@", String.valueOf(maxUsers))
.replace("@@CURRENT_PAGE@@", String.valueOf(currentPage))
.replace("@@TOTAL_PAGES@@", String.valueOf(totalPages))
.replace("@@PAGINATION@@", renderPagination(keyword, currentPage, totalPages));
}
public String renderLogin(LoginSession session, BackDoorLoginProperties properties)
@@ -58,7 +69,6 @@ public class BackDoorPageRenderer
.replace("@@USER_NAME@@", html(displayName))
.replace("@@TOKEN_JSON@@", javascriptString(session.getToken()))
.replace("@@EXPIRE_AT@@", String.valueOf(expireAt))
.replace("@@API_BASE_PATH_JSON@@", javascriptString(properties.getApiBasePath()))
.replace("@@PORTAL_BASE_PATH_JSON@@", javascriptString(properties.getPortalBasePath()));
}
@@ -94,6 +104,44 @@ public class BackDoorPageRenderer
return rows.toString();
}
private String renderPagination(String keyword, int currentPage, int totalPages)
{
StringBuilder pagination = new StringBuilder(512);
appendPageLink(pagination, keyword, currentPage - 1, "上一页", currentPage == 1, false);
for (int page = 1; page <= totalPages; page++)
{
appendPageLink(pagination, keyword, page, String.valueOf(page), false, page == currentPage);
}
appendPageLink(pagination, keyword, currentPage + 1, "下一页", currentPage == totalPages, false);
return pagination.toString();
}
private void appendPageLink(StringBuilder target, String keyword, int page, String label,
boolean disabled, boolean current)
{
if (disabled)
{
target.append("<span class=\"page-link disabled\">").append(label).append("</span>");
return;
}
if (current)
{
target.append("<span class=\"page-link current\">").append(label).append("</span>");
return;
}
String href = "?page=" + page;
if (StringUtils.isNotBlank(keyword))
{
href += "&keyword=" + UriUtils.encodeQueryParam(keyword, StandardCharsets.UTF_8);
}
target.append("<a class=\"page-link\" href=\"")
.append(html(href))
.append("\">")
.append(label)
.append("</a>");
}
private String maskPhone(String phone)
{
String value = StringUtils.trimToEmpty(phone);

View File

@@ -63,6 +63,7 @@ public class BackDoorLoginService
SysUser user = userMapper.selectUserById(userId);
validateUser(user);
validateEffectiveAccount(user);
LoginUser loginUser = (LoginUser) userDetailsService.createLoginUser(user);
loginService.recordLoginInfo(user.getUserId());
@@ -92,6 +93,15 @@ public class BackDoorLoginService
}
}
private void validateEffectiveAccount(SysUser user)
{
Long effectiveUserId = userMapper.selectBackDoorEffectiveUserId(user.getUserName());
if (!user.getUserId().equals(effectiveUserId))
{
throw new ServiceException("该账号存在更新记录,请从用户列表选择有效账号");
}
}
public static class LoginSession
{
private final SysUser user;