diff --git a/build.gradle b/build.gradle index 683c7e0..3f1ad5b 100644 --- a/build.gradle +++ b/build.gradle @@ -73,6 +73,12 @@ dependencies { runtimeOnly('org.springframework.boot:spring-boot-starter-actuator') // Spring Boot Admin client. 2.7.16 = Spring Boot 2.7.x 대응 마지막 계열(Java 8 호환). runtimeOnly('de.codecentric:spring-boot-admin-starter-client:2.7.16') + // 위 runtimeOnly 원칙의 유일한 예외. InterceptorsEndpointConfig 가 actuator API + // (@Endpoint / InfoContributor)를 참조해야 하므로 compileClasspath 에만 올린다. + // compileOnly 는 runtimeClasspath 에 포함되지 않으므로 war/bootWar 산출물에는 영향이 없다. + // 해당 클래스는 @ConditionalOnClass 로 actuator 부재 시 로드되지 않으니 + // 배포본(actuator 제외)에서 NoClassDefFoundError 가 나지 않는다. + compileOnly('org.springframework.boot:spring-boot-actuator') implementation group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.0' implementation group: 'com.fasterxml.woodstox', name: 'woodstox-core', version: '6.5.1' diff --git a/src/main/java/com/eactive/apim/portal/apps/app/dto/ApiKeyRegistrationDTO.java b/src/main/java/com/eactive/apim/portal/apps/app/dto/ApiKeyRegistrationDTO.java index 41325a5..486ebeb 100644 --- a/src/main/java/com/eactive/apim/portal/apps/app/dto/ApiKeyRegistrationDTO.java +++ b/src/main/java/com/eactive/apim/portal/apps/app/dto/ApiKeyRegistrationDTO.java @@ -36,12 +36,12 @@ public class ApiKeyRegistrationDTO implements Serializable { private byte[] appIconData; private String appIconContentType; - @NotBlank(message = "앱 이름을 입력해주세요.") - @Length(max = 100, message = "앱 이름은 100자를 초과할 수 없습니다.") + @NotBlank(message = "클라이언트 이름을 입력해주세요.") + @Length(max = 100, message = "클라이언트 이름은 100자를 초과할 수 없습니다.") private String appName; - @NotBlank(message = "앱 설명을 입력해주세요.") - @Length(max = 500, message = "앱 설명은 500자를 초과할 수 없습니다.") + @NotBlank(message = "클라이언트 설명을 입력해주세요.") + @Length(max = 500, message = "클라이언트 설명은 500자를 초과할 수 없습니다.") private String appDescription; private String callbackUrl; diff --git a/src/main/java/com/eactive/apim/portal/common/util/UserTypeUtil.java b/src/main/java/com/eactive/apim/portal/common/util/UserTypeUtil.java index 221d879..489e623 100644 --- a/src/main/java/com/eactive/apim/portal/common/util/UserTypeUtil.java +++ b/src/main/java/com/eactive/apim/portal/common/util/UserTypeUtil.java @@ -3,9 +3,18 @@ package com.eactive.apim.portal.common.util; import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; import org.apache.commons.lang3.StringUtils; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + /** * 사용자 유형 판별 유틸리티 * 내부 운영자와 외부 사용자를 구분하는 공통 로직 제공 + * + * 내부 사용자로 인정할 이메일 도메인은 application.yml 의 + * {@code portal.internal-user.email-domains} 로 관리한다. + * 설정이 없으면 {@link #DEFAULT_INTERNAL_EMAIL_DOMAINS} 를 사용한다. */ public final class UserTypeUtil { @@ -13,7 +22,47 @@ public final class UserTypeUtil { throw new IllegalStateException("Utility class"); } - private static final String INTERNAL_EMAIL_DOMAIN = "@kjbank.com"; + /** 설정 미주입 시 사용할 기본 내부 도메인 */ + private static final List DEFAULT_INTERNAL_EMAIL_DOMAINS = + Collections.unmodifiableList(Arrays.asList("@djbank.com", "@unit-test.com")); + + private static volatile List internalEmailDomains = DEFAULT_INTERNAL_EMAIL_DOMAINS; + + /** + * 내부 사용자 이메일 도메인 목록 설정. + * {@code UserTypeConfigurer} 가 기동 시 application.yml 값으로 주입한다. + * + * @param domains 도메인 목록 ("@" 접두사 유무 무관, 대소문자 무관) + */ + public static void setInternalEmailDomains(List domains) { + if (domains == null || domains.isEmpty()) { + internalEmailDomains = DEFAULT_INTERNAL_EMAIL_DOMAINS; + return; + } + List normalized = new ArrayList<>(); + for (String domain : domains) { + if (StringUtils.isBlank(domain)) { + continue; + } + String value = domain.trim().toLowerCase(); + if (!value.startsWith("@")) { + value = "@" + value; + } + if (!normalized.contains(value)) { + normalized.add(value); + } + } + internalEmailDomains = normalized.isEmpty() + ? DEFAULT_INTERNAL_EMAIL_DOMAINS + : Collections.unmodifiableList(normalized); + } + + /** + * 현재 적용 중인 내부 사용자 이메일 도메인 목록 + */ + public static List getInternalEmailDomains() { + return internalEmailDomains; + } /** * 현재 사용자가 내부 운영자인지 확인 @@ -21,10 +70,28 @@ public final class UserTypeUtil { * @return 내부 운영자 여부 */ public static boolean isInternalUser(PortalAuthenticatedUser user) { - if (user == null || StringUtils.isEmpty(user.getEmailAddr())) { + if (user == null) { return false; } - return user.getEmailAddr().toLowerCase().endsWith(INTERNAL_EMAIL_DOMAIN); + return isInternalEmail(user.getEmailAddr()); + } + + /** + * 이메일 주소의 호스트(도메인)로 내부 운영자 여부 판별 + * @param emailAddr 이메일 주소 + * @return 내부 운영자 여부 + */ + public static boolean isInternalEmail(String emailAddr) { + if (StringUtils.isEmpty(emailAddr)) { + return false; + } + String email = emailAddr.trim().toLowerCase(); + for (String domain : internalEmailDomains) { + if (email.endsWith(domain)) { + return true; + } + } + return false; } /** diff --git a/src/main/java/com/eactive/apim/portal/config/PortalProperties.java b/src/main/java/com/eactive/apim/portal/config/PortalProperties.java index 41d6cb0..3de600d 100644 --- a/src/main/java/com/eactive/apim/portal/config/PortalProperties.java +++ b/src/main/java/com/eactive/apim/portal/config/PortalProperties.java @@ -5,6 +5,7 @@ import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; +import java.util.ArrayList; import java.util.List; @Data @@ -25,6 +26,20 @@ public class PortalProperties { private FileProperties file = new FileProperties(); + private InternalUserProperties internalUser = new InternalUserProperties(); + + /** + * 내부 사용자(운영자) 판별 설정 + */ + @Data + public static class InternalUserProperties { + /** + * 내부 사용자로 분류할 이메일 도메인 목록. + * "@" 접두사는 있어도 되고 없어도 되며, 대소문자를 구분하지 않는다. + */ + private List emailDomains = new ArrayList<>(); + } + /** * 파일 업로드 관련 설정 */ diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index ec05031..24a3384 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -127,6 +127,15 @@ portal: logging: log-path: /logs/eapim + # 내부 사용자(운영자) 판별 설정 + # 로그인 계정 이메일의 호스트가 아래 도메인이면 내부 사용자로 분류한다. + # (첨부파일 형식/크기 검증 우회 등 FileService 내부 사용자 컨텍스트에 사용) + # "@" 접두사 유무·대소문자 무관 + internal-user: + email-domains: + - djbank.com + - unit-test.com + auth-ttl: 300 auth: resend_limit_seconds: 30 diff --git a/src/main/resources/templates/views/apps/mypage/apiKeyModifyStep1.html b/src/main/resources/templates/views/apps/mypage/apiKeyModifyStep1.html index 3d8a41f..d405b73 100644 --- a/src/main/resources/templates/views/apps/mypage/apiKeyModifyStep1.html +++ b/src/main/resources/templates/views/apps/mypage/apiKeyModifyStep1.html @@ -90,18 +90,18 @@ - +
- + + placeholder="클라이언트 이름을 입력하세요." required maxlength="100">
- +
- + + placeholder="클라이언트 설명을 입력하세요." required maxlength="500">
0 / 500
@@ -132,9 +132,9 @@
- +
- +
@@ -154,7 +154,7 @@
-

앱 아이콘 이미지 파일을 업로드 하세요

+

클라이언트 아이콘 이미지 파일을 업로드 하세요

권장 사이즈는 512 * 512 픽셀이며 JPG,PNG,GIF 파일만 등록할 수 있습니다.