fix: 调用API时返回了 HTML 登录页
This commit is contained in:
parent
f44c3eeae6
commit
23ec8a8803
|
|
@ -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 Key(Header: X-API-Key 或 Query: apiKey)
|
||||
// 1. 优先检查 API Key(Header: 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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user