forked from kimhyungsik/ax_hub_mcp_tool
fix: WebConfig markdown API key bypass & ApiKeyInterceptor empty keys logic & UI tool identifier mapping
This commit is contained in:
@@ -37,7 +37,7 @@ public class WebConfig implements WebMvcConfigurer {
|
|||||||
.excludePathPatterns(
|
.excludePathPatterns(
|
||||||
"/test/**", "/health", "/error", "/mcp/api/v1/admin/**",
|
"/test/**", "/health", "/error", "/mcp/api/v1/admin/**",
|
||||||
"/swagger-ui/**", "/v3/api-docs/**", "/swagger-resources/**", "/webjars/**", // Swagger UI 경로는 인증 제외
|
"/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"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,14 +42,22 @@ public class ApiKeyInterceptor implements HandlerInterceptor {
|
|||||||
String apiKey = request.getHeader("X-API-KEY");
|
String apiKey = request.getHeader("X-API-KEY");
|
||||||
Map<String, String> validApiKeys = securityProperties.getApiKeys();
|
Map<String, String> 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)) {
|
if (apiKey == null || !validApiKeys.containsKey(apiKey)) {
|
||||||
log.warn(" [보안 차단] 유효하지 않은 API Key 접근 시도 - IP: {}", request.getRemoteAddr());
|
log.warn(" [보안 차단] 유효하지 않은 API Key 접근 시도 - IP: {}", request.getRemoteAddr());
|
||||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid API Key");
|
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid API Key");
|
||||||
return false; // 컨트롤러로 넘어가지 않음
|
return false; // 컨트롤러로 넘어가지 않음
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 유효한 키라면, 해당 키와 맵핑된 Tenant ID(식별자)를 가져옵니다. (ex. mcp-client-1)
|
// 4. 유효하다면 해당 키에 맵핑된 Tenant ID(식별자)를 가져옵니다. (ex. mcp-client-1)
|
||||||
String tenantId = validApiKeys.get(apiKey);
|
String tenantId = validApiKeys.get(apiKey);
|
||||||
|
|
||||||
// 4. 추출한 Tenant ID를 현재 스레드의 로깅 컨텍스트(MDC)에 저장합니다.
|
// 4. 추출한 Tenant ID를 현재 스레드의 로깅 컨텍스트(MDC)에 저장합니다.
|
||||||
|
|||||||
@@ -317,7 +317,7 @@
|
|||||||
<div class="absolute top-4 right-4 px-2 py-0.5 rounded text-[9px] font-bold font-mono border tracking-wider ${typeClass}">
|
<div class="absolute top-4 right-4 px-2 py-0.5 rounded text-[9px] font-bold font-mono border tracking-wider ${typeClass}">
|
||||||
${tool.integrationType || 'DIRECT'}
|
${tool.integrationType || 'DIRECT'}
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg font-bold text-white mb-1 pr-16">${tool.name}</h3>
|
<h3 class="text-lg font-bold text-white mb-1 pr-16">${tool.displayName} <span class="text-xs text-slate-500 font-mono">(${tool.name})</span></h3>
|
||||||
<p class="text-sm text-slate-400 leading-relaxed min-h-[40px]">${tool.description || '설명이 없습니다.'}</p>
|
<p class="text-sm text-slate-400 leading-relaxed min-h-[40px]">${tool.description || '설명이 없습니다.'}</p>
|
||||||
|
|
||||||
${paramHtml}
|
${paramHtml}
|
||||||
|
|||||||
@@ -333,9 +333,9 @@
|
|||||||
|
|
||||||
groupedTools[group].forEach(tool => {
|
groupedTools[group].forEach(tool => {
|
||||||
const option = document.createElement('option');
|
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';
|
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.schema = JSON.stringify(tool.parametersSchema);
|
||||||
option.dataset.prompts = JSON.stringify(tool.actionPrompts || {});
|
option.dataset.prompts = JSON.stringify(tool.actionPrompts || {});
|
||||||
optgroup.appendChild(option);
|
optgroup.appendChild(option);
|
||||||
|
|||||||
@@ -325,8 +325,8 @@
|
|||||||
|
|
||||||
groupedTools[group].forEach(tool => {
|
groupedTools[group].forEach(tool => {
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
option.value = tool.toolName;
|
option.value = tool.name;
|
||||||
option.textContent = tool.description ? `[${tool.integrationType || 'HTTP'}] ${tool.description} (${tool.toolName})` : tool.toolName;
|
option.textContent = tool.description ? `[${tool.integrationType || 'HTTP'}] ${tool.description} (${tool.name})` : tool.name;
|
||||||
option.dataset.schema = JSON.stringify(tool.parametersSchema);
|
option.dataset.schema = JSON.stringify(tool.parametersSchema);
|
||||||
option.dataset.prompts = JSON.stringify(tool.actionPrompts || {});
|
option.dataset.prompts = JSON.stringify(tool.actionPrompts || {});
|
||||||
optgroup.appendChild(option);
|
optgroup.appendChild(option);
|
||||||
|
|||||||
Reference in New Issue
Block a user