Webhook URL 형식 검증 로직 분리 및 재사용성 개선:
- WebhookUrlValidator 추가: URL 형식 검증 전담 - Service/Controller에서 공통 Validator 활용하도록 수정
This commit is contained in:
@@ -11,6 +11,7 @@ import com.eactive.apim.portal.djb.webhook.dto.WebhookDTO;
|
|||||||
import com.eactive.apim.portal.djb.webhook.dto.WebhookRegistrationDTO;
|
import com.eactive.apim.portal.djb.webhook.dto.WebhookRegistrationDTO;
|
||||||
import com.eactive.apim.portal.djb.webhook.service.WebhookEventTypeProvider;
|
import com.eactive.apim.portal.djb.webhook.service.WebhookEventTypeProvider;
|
||||||
import com.eactive.apim.portal.djb.webhook.service.WebhookService;
|
import com.eactive.apim.portal.djb.webhook.service.WebhookService;
|
||||||
|
import com.eactive.apim.portal.djb.webhook.service.WebhookUrlValidator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -375,10 +376,9 @@ public class WebhookController {
|
|||||||
|
|
||||||
private void validateStep1(WebhookRegistrationDTO dto, BindingResult bindingResult) {
|
private void validateStep1(WebhookRegistrationDTO dto, BindingResult bindingResult) {
|
||||||
String url = dto.getTargetUrl() == null ? "" : dto.getTargetUrl().trim();
|
String url = dto.getTargetUrl() == null ? "" : dto.getTargetUrl().trim();
|
||||||
if (!bindingResult.hasFieldErrors("targetUrl")
|
if (!bindingResult.hasFieldErrors("targetUrl") && !WebhookUrlValidator.isValid(url)) {
|
||||||
&& !url.startsWith("http://") && !url.startsWith("https://")) {
|
|
||||||
bindingResult.rejectValue("targetUrl", "invalid.url",
|
bindingResult.rejectValue("targetUrl", "invalid.url",
|
||||||
"URL은 http:// 또는 https:// 로 시작해야 합니다.");
|
"올바른 URL 형식이 아닙니다. http:// 또는 https:// 로 시작하는 전체 주소를 입력해주세요.");
|
||||||
}
|
}
|
||||||
if (dto.getEventTypes() == null || dto.getEventTypes().isEmpty()) {
|
if (dto.getEventTypes() == null || dto.getEventTypes().isEmpty()) {
|
||||||
bindingResult.rejectValue("eventTypes", "empty.eventTypes",
|
bindingResult.rejectValue("eventTypes", "empty.eventTypes",
|
||||||
|
|||||||
@@ -170,9 +170,8 @@ public class WebhookService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void validate(WebhookRegistrationDTO dto) {
|
private void validate(WebhookRegistrationDTO dto) {
|
||||||
String url = dto.getTargetUrl() == null ? "" : dto.getTargetUrl().trim();
|
if (!WebhookUrlValidator.isValid(dto.getTargetUrl())) {
|
||||||
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
throw new IllegalArgumentException("올바른 URL 형식이 아닙니다. http:// 또는 https:// 로 시작하는 전체 주소를 입력해주세요.");
|
||||||
throw new IllegalArgumentException("URL은 http:// 또는 https:// 로 시작해야 합니다.");
|
|
||||||
}
|
}
|
||||||
if (dto.getEventTypes() == null || dto.getEventTypes().isEmpty()) {
|
if (dto.getEventTypes() == null || dto.getEventTypes().isEmpty()) {
|
||||||
throw new IllegalArgumentException("EventType을 1개 이상 선택해주세요.");
|
throw new IllegalArgumentException("EventType을 1개 이상 선택해주세요.");
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.eactive.apim.portal.djb.webhook.service;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Webhook 수신 URL 형식 검증. 컨트롤러(필드 에러 표시)와 서비스(방어적 재검증) 양쪽에서 공용으로 쓴다.
|
||||||
|
*
|
||||||
|
* http/https 스킴 + host 존재까지 확인한다("http://" 만 있고 host 가 없는 값 등을 차단).
|
||||||
|
* 순수 문법 검증만 하며, 실제 도달 가능성(DNS/네트워크)은 검사하지 않는다.
|
||||||
|
*/
|
||||||
|
public final class WebhookUrlValidator {
|
||||||
|
|
||||||
|
private WebhookUrlValidator() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isValid(String url) {
|
||||||
|
if (url == null || url.trim().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
URI uri = new URI(url.trim());
|
||||||
|
String scheme = uri.getScheme();
|
||||||
|
return ("http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme))
|
||||||
|
&& uri.getHost() != null && !uri.getHost().isEmpty();
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25733,29 +25733,31 @@ input[type=checkbox]:checked + .custom-checkbox {
|
|||||||
padding: 24px 16px 60px;
|
padding: 24px 16px 60px;
|
||||||
font-family: "Spoqa Han Sans Neo", "Noto Sans CJK KR", sans-serif;
|
font-family: "Spoqa Han Sans Neo", "Noto Sans CJK KR", sans-serif;
|
||||||
}
|
}
|
||||||
.webhook-container .field-help {
|
|
||||||
|
.step1-wrap .field-help {
|
||||||
color: #64748b;
|
color: #64748b;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
}
|
}
|
||||||
.webhook-container .field-help-red {
|
.step1-wrap .field-help-red {
|
||||||
color: #f4253c;
|
color: #f4253c;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.webhook-container .field-help-red {
|
.step1-wrap .field-help-red {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.webhook-container .field-error,
|
.step1-wrap .field-error,
|
||||||
.webhook-container .webhook-field-error {
|
.step1-wrap .webhook-field-error {
|
||||||
color: #f4253c;
|
color: #f4253c;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
}
|
}
|
||||||
.webhook-container .req {
|
.step1-wrap .req {
|
||||||
color: #f4253c;
|
color: #f4253c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -14,7 +14,12 @@ $wh-bg-soft: #f9f9f9;
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 24px 16px 60px;
|
padding: 24px 16px 60px;
|
||||||
font-family: 'Spoqa Han Sans Neo', 'Noto Sans CJK KR', sans-serif;
|
font-family: 'Spoqa Han Sans Neo', 'Noto Sans CJK KR', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 실제 페이지 마크업은 .webhook-container 가 아닌 .step1-wrap 으로 감싸여 있어
|
||||||
|
// (webhookRegisterStep1/webhookModifyStep1/webhookList 공통) 여기로 스코프한다.
|
||||||
|
// 위 .webhook-container 블록은 미사용이라 색상 규칙이 전혀 적용되지 않았었다.
|
||||||
|
.step1-wrap {
|
||||||
.field-help {
|
.field-help {
|
||||||
color: $wh-muted;
|
color: $wh-muted;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
@@ -36,6 +41,7 @@ $wh-bg-soft: #f9f9f9;
|
|||||||
.webhook-field-error {
|
.webhook-field-error {
|
||||||
color: $wh-danger;
|
color: $wh-danger;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user