From c44e8db08ebdf726a0543305e66b0cbc79e816d0 Mon Sep 17 00:00:00 2001 From: jade Date: Sat, 11 Jul 2026 01:52:15 +0900 Subject: [PATCH] fix: WebConfig markdown API key bypass & ApiKeyInterceptor empty keys logic & UI tool identifier mapping --- .../axhub/common/mcp/config/WebConfig.java | 2 +- .../axhub/common/mcp/security/ApiKeyInterceptor.java | 12 ++++++++++-- axhub-gateway/src/main/resources/static/catalog.html | 2 +- axhub-gateway/src/main/resources/static/index.html | 4 ++-- .../src/main/resources/static/playground.html | 4 ++-- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/axhub-common/src/main/java/io/shinhanlife/axhub/common/mcp/config/WebConfig.java b/axhub-common/src/main/java/io/shinhanlife/axhub/common/mcp/config/WebConfig.java index 07545b4..3de739d 100644 --- a/axhub-common/src/main/java/io/shinhanlife/axhub/common/mcp/config/WebConfig.java +++ b/axhub-common/src/main/java/io/shinhanlife/axhub/common/mcp/config/WebConfig.java @@ -37,7 +37,7 @@ public class WebConfig implements WebMvcConfigurer { .excludePathPatterns( "/test/**", "/health", "/error", "/mcp/api/v1/admin/**", "/swagger-ui/**", "/v3/api-docs/**", "/swagger-resources/**", "/webjars/**", // Swagger UI 경로는 인증 제외 - "/mcp/api/v1/tools/docs/markdown", "/favicon.ico" + "/mcp/api/v1/tools/docs/markdown", "/favicon.ico", "/mcp/api/v1/tools/list" ); } diff --git a/axhub-common/src/main/java/io/shinhanlife/axhub/common/mcp/security/ApiKeyInterceptor.java b/axhub-common/src/main/java/io/shinhanlife/axhub/common/mcp/security/ApiKeyInterceptor.java index f52cb00..513b28f 100644 --- a/axhub-common/src/main/java/io/shinhanlife/axhub/common/mcp/security/ApiKeyInterceptor.java +++ b/axhub-common/src/main/java/io/shinhanlife/axhub/common/mcp/security/ApiKeyInterceptor.java @@ -42,14 +42,22 @@ public class ApiKeyInterceptor implements HandlerInterceptor { String apiKey = request.getHeader("X-API-KEY"); Map validApiKeys = securityProperties.getApiKeys(); - // 2. 헤더로 넘어온 API Key가 우리가 발급한 키 목록(Map)에 존재하는지 확인합니다. + // 2. 만약 프로퍼티에 API Key가 하나도 설정되어 있지 않다면 (개발/로컬 환경 등) 인증 없이 통과시킵니다. + if (validApiKeys == null || validApiKeys.isEmpty()) { + MDC.put("tenantId", "anonymous"); + request.setAttribute("tenantId", "anonymous"); + log.debug(" [보안 패스] 등록된 API Key 없음 - 익명 사용자(anonymous)로 통과"); + return true; + } + + // 3. 헤더로 들어온 API Key가 우리가 발급해준 목록(Map)에 존재하는지 확인합니다. if (apiKey == null || !validApiKeys.containsKey(apiKey)) { log.warn(" [보안 차단] 유효하지 않은 API Key 접근 시도 - IP: {}", request.getRemoteAddr()); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid API Key"); return false; // 컨트롤러로 넘어가지 않음 } - // 3. 유효한 키라면, 해당 키와 맵핑된 Tenant ID(식별자)를 가져옵니다. (ex. mcp-client-1) + // 4. 유효하다면 해당 키에 맵핑된 Tenant ID(식별자)를 가져옵니다. (ex. mcp-client-1) String tenantId = validApiKeys.get(apiKey); // 4. 추출한 Tenant ID를 현재 스레드의 로깅 컨텍스트(MDC)에 저장합니다. diff --git a/axhub-gateway/src/main/resources/static/catalog.html b/axhub-gateway/src/main/resources/static/catalog.html index 3c2d890..97e9e6e 100644 --- a/axhub-gateway/src/main/resources/static/catalog.html +++ b/axhub-gateway/src/main/resources/static/catalog.html @@ -317,7 +317,7 @@
${tool.integrationType || 'DIRECT'}
-

${tool.name}

+

${tool.displayName} (${tool.name})

${tool.description || '설명이 없습니다.'}

${paramHtml} diff --git a/axhub-gateway/src/main/resources/static/index.html b/axhub-gateway/src/main/resources/static/index.html index 75d5dbd..aa472cd 100644 --- a/axhub-gateway/src/main/resources/static/index.html +++ b/axhub-gateway/src/main/resources/static/index.html @@ -333,9 +333,9 @@ groupedTools[group].forEach(tool => { const option = document.createElement('option'); - option.value = tool.toolName; // Now the function name + option.value = tool.name; // Now the function name const commType = tool.integrationType ? tool.integrationType : 'HTTP'; - option.textContent = tool.description ? `[${commType}] ${tool.description} (${tool.toolName})` : `[${commType}] ${tool.toolName}`; + option.textContent = tool.description ? `[${commType}] ${tool.description} (${tool.name})` : `[${commType}] ${tool.name}`; option.dataset.schema = JSON.stringify(tool.parametersSchema); option.dataset.prompts = JSON.stringify(tool.actionPrompts || {}); optgroup.appendChild(option); diff --git a/axhub-gateway/src/main/resources/static/playground.html b/axhub-gateway/src/main/resources/static/playground.html index 84ee405..78f9a51 100644 --- a/axhub-gateway/src/main/resources/static/playground.html +++ b/axhub-gateway/src/main/resources/static/playground.html @@ -325,8 +325,8 @@ groupedTools[group].forEach(tool => { const option = document.createElement('option'); - option.value = tool.toolName; - option.textContent = tool.description ? `[${tool.integrationType || 'HTTP'}] ${tool.description} (${tool.toolName})` : tool.toolName; + option.value = tool.name; + option.textContent = tool.description ? `[${tool.integrationType || 'HTTP'}] ${tool.description} (${tool.name})` : tool.name; option.dataset.schema = JSON.stringify(tool.parametersSchema); option.dataset.prompts = JSON.stringify(tool.actionPrompts || {}); optgroup.appendChild(option);