fix: 调用API时返回了 HTML 登录页

This commit is contained in:
叁玖领域 2026-06-09 12:08:22 +08:00
parent f44c3eeae6
commit 23ec8a8803
2 changed files with 88 additions and 28 deletions

View File

@ -9,6 +9,9 @@ import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@Component
public class ApiKeyInterceptor implements HandlerInterceptor {
@ -17,8 +20,11 @@ public class ApiKeyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 1. 优先检查 API KeyHeader: X-API-Key Query: apiKey
// 1. 优先检查 API KeyHeader: X-API-Key / X-API-TOKEN Query: apiKey
String reqApiKey = request.getHeader("X-API-Key");
if (!StringUtils.hasText(reqApiKey)) {
reqApiKey = request.getHeader("X-API-TOKEN");
}
if (!StringUtils.hasText(reqApiKey)) {
reqApiKey = request.getParameter("apiKey");
}
@ -35,6 +41,32 @@ public class ApiKeyInterceptor implements HandlerInterceptor {
return true;
}
// 3. 鉴权失败 API 请求返回 JSON浏览器请求抛异常跳转登录页
if (isApiRequest(request)) {
writeJsonResponse(response, 403, "{\"code\":403,\"msg\":\"无权限API Key 无效或未提供\",\"data\":null}");
return false;
}
throw new UnauthorizedException("redirect:/admin/login?error=please login first");
}
private boolean isApiRequest(HttpServletRequest request) {
String header = request.getHeader("X-API-Key");
if (!StringUtils.hasText(header)) {
header = request.getHeader("X-API-TOKEN");
}
return StringUtils.hasText(header)
|| StringUtils.hasText(request.getParameter("apiKey"))
|| request.getRequestURI().startsWith("/api/");
}
private void writeJsonResponse(HttpServletResponse response, int status, String body) {
response.setStatus(status);
response.setContentType("application/json");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
try {
response.getWriter().write(body);
response.getWriter().flush();
} catch (IOException ignored) {
}
}
}

View File

@ -28,7 +28,7 @@ class ApiKeyInterceptorTest {
response = new MockHttpServletResponse();
}
// ==================== API Key Header ====================
// ==================== X-API-Key Header ====================
@Test
void validApiKeyInHeader_shouldPass() {
@ -40,20 +40,35 @@ class ApiKeyInterceptorTest {
}
@Test
void invalidApiKeyInHeader_shouldThrow() {
void invalidApiKeyInHeader_shouldReturnJson403() {
request.addHeader("X-API-Key", "wrong-key");
assertThatThrownBy(() -> interceptor.preHandle(request, response, null))
.isInstanceOf(UnauthorizedException.class);
boolean result = interceptor.preHandle(request, response, null);
assertThat(result).isFalse();
assertThat(response.getStatus()).isEqualTo(403);
assertThat(response.getContentType()).startsWith("application/json");
}
// ==================== X-API-TOKEN Header ====================
@Test
void validApiTokenHeader_shouldPass() {
request.addHeader("X-API-TOKEN", VALID_API_KEY);
boolean result = interceptor.preHandle(request, response, null);
assertThat(result).isTrue();
}
@Test
void emptyApiKeyInHeader_shouldFallbackToSession() {
request.addHeader("X-API-Key", "");
void invalidApiTokenHeader_shouldReturnJson403() {
request.addHeader("X-API-TOKEN", "wrong-token");
// no session, should throw
assertThatThrownBy(() -> interceptor.preHandle(request, response, null))
.isInstanceOf(UnauthorizedException.class);
boolean result = interceptor.preHandle(request, response, null);
assertThat(result).isFalse();
assertThat(response.getStatus()).isEqualTo(403);
}
// ==================== API Key Query Param ====================
@ -68,11 +83,25 @@ class ApiKeyInterceptorTest {
}
@Test
void invalidApiKeyInQueryParam_shouldThrow() {
void invalidApiKeyInQueryParam_shouldReturnJson403() {
request.setParameter("apiKey", "wrong-key");
assertThatThrownBy(() -> interceptor.preHandle(request, response, null))
.isInstanceOf(UnauthorizedException.class);
boolean result = interceptor.preHandle(request, response, null);
assertThat(result).isFalse();
assertThat(response.getStatus()).isEqualTo(403);
}
// ==================== Priority ====================
@Test
void apiKeyHeaderTakesPriorityOverTokenHeader() {
request.addHeader("X-API-Key", VALID_API_KEY);
request.addHeader("X-API-TOKEN", "wrong-token");
boolean result = interceptor.preHandle(request, response, null);
assertThat(result).isTrue();
}
@Test
@ -80,13 +109,25 @@ class ApiKeyInterceptorTest {
request.addHeader("X-API-Key", VALID_API_KEY);
request.setParameter("apiKey", "wrong-key");
// header should be used and pass
boolean result = interceptor.preHandle(request, response, null);
assertThat(result).isTrue();
}
// ==================== Session Fallback ====================
// ==================== API URI path ====================
@Test
void requestToApiPath_withoutAuth_shouldReturnJson403() {
request.setRequestURI("/api/whitelist/list");
boolean result = interceptor.preHandle(request, response, null);
assertThat(result).isFalse();
assertThat(response.getStatus()).isEqualTo(403);
assertThat(response.getContentType()).startsWith("application/json");
}
// ==================== Session Fallback (browser) ====================
@Test
void validAdminSession_shouldPass() {
@ -113,7 +154,6 @@ class ApiKeyInterceptorTest {
void sessionNoAdminAccount_shouldThrow() {
HttpSession session = request.getSession(true);
session.setAttribute("isLoggedIn", true);
// no adminAccount set
assertThatThrownBy(() -> interceptor.preHandle(request, response, null))
.isInstanceOf(UnauthorizedException.class);
@ -121,18 +161,6 @@ class ApiKeyInterceptorTest {
@Test
void noSession_noApiKey_shouldThrow() {
// no session created, no API key set
assertThatThrownBy(() -> interceptor.preHandle(request, response, null))
.isInstanceOf(UnauthorizedException.class);
}
@Test
void nullSession_noApiKey_shouldThrow() {
// explicitly test null session behavior
// MockHttpServletRequest.getSession() returns non-null by default,
// but we test invalid session attributes above which covers this path
assertThatThrownBy(() -> interceptor.preHandle(request, response, null))
.isInstanceOf(UnauthorizedException.class);
}