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.util.StringUtils;
|
||||||
import org.springframework.web.servlet.HandlerInterceptor;
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class ApiKeyInterceptor implements HandlerInterceptor {
|
public class ApiKeyInterceptor implements HandlerInterceptor {
|
||||||
|
|
||||||
|
|
@ -17,8 +20,11 @@ public class ApiKeyInterceptor implements HandlerInterceptor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
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");
|
String reqApiKey = request.getHeader("X-API-Key");
|
||||||
|
if (!StringUtils.hasText(reqApiKey)) {
|
||||||
|
reqApiKey = request.getHeader("X-API-TOKEN");
|
||||||
|
}
|
||||||
if (!StringUtils.hasText(reqApiKey)) {
|
if (!StringUtils.hasText(reqApiKey)) {
|
||||||
reqApiKey = request.getParameter("apiKey");
|
reqApiKey = request.getParameter("apiKey");
|
||||||
}
|
}
|
||||||
|
|
@ -35,6 +41,32 @@ public class ApiKeyInterceptor implements HandlerInterceptor {
|
||||||
return true;
|
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");
|
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();
|
response = new MockHttpServletResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== API Key Header ====================
|
// ==================== X-API-Key Header ====================
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void validApiKeyInHeader_shouldPass() {
|
void validApiKeyInHeader_shouldPass() {
|
||||||
|
|
@ -40,20 +40,35 @@ class ApiKeyInterceptorTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void invalidApiKeyInHeader_shouldThrow() {
|
void invalidApiKeyInHeader_shouldReturnJson403() {
|
||||||
request.addHeader("X-API-Key", "wrong-key");
|
request.addHeader("X-API-Key", "wrong-key");
|
||||||
|
|
||||||
assertThatThrownBy(() -> interceptor.preHandle(request, response, null))
|
boolean result = interceptor.preHandle(request, response, null);
|
||||||
.isInstanceOf(UnauthorizedException.class);
|
|
||||||
|
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
|
@Test
|
||||||
void emptyApiKeyInHeader_shouldFallbackToSession() {
|
void invalidApiTokenHeader_shouldReturnJson403() {
|
||||||
request.addHeader("X-API-Key", "");
|
request.addHeader("X-API-TOKEN", "wrong-token");
|
||||||
|
|
||||||
// no session, should throw
|
boolean result = interceptor.preHandle(request, response, null);
|
||||||
assertThatThrownBy(() -> interceptor.preHandle(request, response, null))
|
|
||||||
.isInstanceOf(UnauthorizedException.class);
|
assertThat(result).isFalse();
|
||||||
|
assertThat(response.getStatus()).isEqualTo(403);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== API Key Query Param ====================
|
// ==================== API Key Query Param ====================
|
||||||
|
|
@ -68,11 +83,25 @@ class ApiKeyInterceptorTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void invalidApiKeyInQueryParam_shouldThrow() {
|
void invalidApiKeyInQueryParam_shouldReturnJson403() {
|
||||||
request.setParameter("apiKey", "wrong-key");
|
request.setParameter("apiKey", "wrong-key");
|
||||||
|
|
||||||
assertThatThrownBy(() -> interceptor.preHandle(request, response, null))
|
boolean result = interceptor.preHandle(request, response, null);
|
||||||
.isInstanceOf(UnauthorizedException.class);
|
|
||||||
|
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
|
@Test
|
||||||
|
|
@ -80,13 +109,25 @@ class ApiKeyInterceptorTest {
|
||||||
request.addHeader("X-API-Key", VALID_API_KEY);
|
request.addHeader("X-API-Key", VALID_API_KEY);
|
||||||
request.setParameter("apiKey", "wrong-key");
|
request.setParameter("apiKey", "wrong-key");
|
||||||
|
|
||||||
// header should be used and pass
|
|
||||||
boolean result = interceptor.preHandle(request, response, null);
|
boolean result = interceptor.preHandle(request, response, null);
|
||||||
|
|
||||||
assertThat(result).isTrue();
|
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
|
@Test
|
||||||
void validAdminSession_shouldPass() {
|
void validAdminSession_shouldPass() {
|
||||||
|
|
@ -113,7 +154,6 @@ class ApiKeyInterceptorTest {
|
||||||
void sessionNoAdminAccount_shouldThrow() {
|
void sessionNoAdminAccount_shouldThrow() {
|
||||||
HttpSession session = request.getSession(true);
|
HttpSession session = request.getSession(true);
|
||||||
session.setAttribute("isLoggedIn", true);
|
session.setAttribute("isLoggedIn", true);
|
||||||
// no adminAccount set
|
|
||||||
|
|
||||||
assertThatThrownBy(() -> interceptor.preHandle(request, response, null))
|
assertThatThrownBy(() -> interceptor.preHandle(request, response, null))
|
||||||
.isInstanceOf(UnauthorizedException.class);
|
.isInstanceOf(UnauthorizedException.class);
|
||||||
|
|
@ -121,18 +161,6 @@ class ApiKeyInterceptorTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void noSession_noApiKey_shouldThrow() {
|
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))
|
assertThatThrownBy(() -> interceptor.preHandle(request, response, null))
|
||||||
.isInstanceOf(UnauthorizedException.class);
|
.isInstanceOf(UnauthorizedException.class);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user