"신규 신청 쿨다운 로직 제거 - Repository/Service/Controller 코드 정리 - 불필요한 HTML 스크립트 제거"
This commit is contained in:
@@ -16,7 +16,6 @@ import com.eactive.apim.portal.apps.auth.twofactor.StepUpProtectedPaths;
|
|||||||
import com.eactive.apim.portal.apps.auth.twofactor.TwoFactorProperties;
|
import com.eactive.apim.portal.apps.auth.twofactor.TwoFactorProperties;
|
||||||
import com.eactive.apim.portal.apps.auth.twofactor.TwoFactorService;
|
import com.eactive.apim.portal.apps.auth.twofactor.TwoFactorService;
|
||||||
import com.eactive.apim.portal.common.exception.UserErrorMessageResolver;
|
import com.eactive.apim.portal.common.exception.UserErrorMessageResolver;
|
||||||
import com.eactive.apim.portal.common.security.WriteRateLimitService;
|
|
||||||
import com.eactive.apim.portal.common.user.PortalAuthenticatedUser;
|
import com.eactive.apim.portal.common.user.PortalAuthenticatedUser;
|
||||||
import com.eactive.apim.portal.common.util.ApiServiceHelper;
|
import com.eactive.apim.portal.common.util.ApiServiceHelper;
|
||||||
import com.eactive.apim.portal.common.util.SecurityUtil;
|
import com.eactive.apim.portal.common.util.SecurityUtil;
|
||||||
@@ -89,7 +88,6 @@ public class MyAppController {
|
|||||||
private final FileTypeDetector fileTypeDetector;
|
private final FileTypeDetector fileTypeDetector;
|
||||||
private final TwoFactorService twoFactorService;
|
private final TwoFactorService twoFactorService;
|
||||||
private final TwoFactorProperties twoFactorProperties;
|
private final TwoFactorProperties twoFactorProperties;
|
||||||
private final WriteRateLimitService writeRateLimitService;
|
|
||||||
|
|
||||||
private static final long MAX_APP_ICON_BYTES = 2L * 1024 * 1024; // 2MB
|
private static final long MAX_APP_ICON_BYTES = 2L * 1024 * 1024; // 2MB
|
||||||
|
|
||||||
@@ -532,18 +530,9 @@ public class MyAppController {
|
|||||||
@RequestParam(value = "clear", required = false, defaultValue = "false") boolean clear,
|
@RequestParam(value = "clear", required = false, defaultValue = "false") boolean clear,
|
||||||
@ModelAttribute("apiKeyRegistration") ApiKeyRegistrationDTO registration,
|
@ModelAttribute("apiKeyRegistration") ApiKeyRegistrationDTO registration,
|
||||||
SessionStatus sessionStatus,
|
SessionStatus sessionStatus,
|
||||||
RedirectAttributes redirectAttributes,
|
|
||||||
Model model) {
|
Model model) {
|
||||||
PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser();
|
PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser();
|
||||||
|
|
||||||
// 재신청 쿨다운이면 입력을 다 하고 막히지 않도록 진입 시점에 되돌린다(최종 차단은 step2 가 한다).
|
|
||||||
long cooldownMinutes = writeRateLimitService.newAppRequestCooldownRemainingMinutes();
|
|
||||||
if (cooldownMinutes > 0) {
|
|
||||||
sessionStatus.setComplete();
|
|
||||||
redirectAttributes.addFlashAttribute("error", writeRateLimitService.cooldownMessage(cooldownMinutes));
|
|
||||||
return new ModelAndView("redirect:/clients");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 명시적으로 요청된 경우에만 세션 초기화 (새 등록 시작)
|
// 명시적으로 요청된 경우에만 세션 초기화 (새 등록 시작)
|
||||||
// 페이지 새로고침이나 뒤로가기 시에는 세션 데이터를 보존
|
// 페이지 새로고침이나 뒤로가기 시에는 세션 데이터를 보존
|
||||||
if (clear) {
|
if (clear) {
|
||||||
@@ -728,14 +717,6 @@ public class MyAppController {
|
|||||||
return new ModelAndView("redirect:/clients/register/step1");
|
return new ModelAndView("redirect:/clients/register/step1");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 무제한 신청 차단 — 신규 신청은 1시간에 1건으로 고정한다(PortalProperty 무관).
|
|
||||||
long cooldownMinutes = writeRateLimitService.newAppRequestCooldownRemainingMinutes();
|
|
||||||
if (cooldownMinutes > 0) {
|
|
||||||
sessionStatus.setComplete();
|
|
||||||
redirectAttributes.addFlashAttribute("error", writeRateLimitService.cooldownMessage(cooldownMinutes));
|
|
||||||
return new ModelAndView("redirect:/clients");
|
|
||||||
}
|
|
||||||
|
|
||||||
PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser();
|
PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
package com.eactive.apim.portal.common.security;
|
package com.eactive.apim.portal.common.security;
|
||||||
|
|
||||||
import com.eactive.apim.portal.apprequest.entity.AppRequest;
|
|
||||||
import com.eactive.apim.portal.apprequest.entity.AppRequestType;
|
|
||||||
import com.eactive.apim.portal.apprequest.repository.AppRequestRepository;
|
|
||||||
import com.eactive.apim.portal.apps.community.partnership.repository.PartnershipApplicationRepository;
|
import com.eactive.apim.portal.apps.community.partnership.repository.PartnershipApplicationRepository;
|
||||||
import com.eactive.apim.portal.apps.community.qna.repository.InquiryRepository;
|
import com.eactive.apim.portal.apps.community.qna.repository.InquiryRepository;
|
||||||
import com.eactive.apim.portal.common.entity.Auditable;
|
import com.eactive.apim.portal.common.entity.Auditable;
|
||||||
@@ -20,7 +17,6 @@ import java.time.Duration;
|
|||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 작성 요청 빈도 제한(무제한 요청 차단).
|
* 작성 요청 빈도 제한(무제한 요청 차단).
|
||||||
@@ -40,9 +36,6 @@ public class WriteRateLimitService {
|
|||||||
/** 빈도 판정 기준 구간 */
|
/** 빈도 판정 기준 구간 */
|
||||||
private static final Duration WINDOW = Duration.ofMinutes(1);
|
private static final Duration WINDOW = Duration.ofMinutes(1);
|
||||||
|
|
||||||
/** 클라이언트/API 신규 이용신청 재신청 금지 시간. PortalProperty 와 무관한 고정값. */
|
|
||||||
public static final Duration NEW_APP_REQUEST_COOLDOWN = Duration.ofHours(1);
|
|
||||||
|
|
||||||
/** 빈도 제한 대상. 대상별로 카운터가 독립이다. */
|
/** 빈도 제한 대상. 대상별로 카운터가 독립이다. */
|
||||||
public enum WriteTarget {
|
public enum WriteTarget {
|
||||||
INQUIRY("Q&A 문의"),
|
INQUIRY("Q&A 문의"),
|
||||||
@@ -64,7 +57,6 @@ public class WriteRateLimitService {
|
|||||||
private final InquiryRepository inquiryRepository;
|
private final InquiryRepository inquiryRepository;
|
||||||
private final InquiryCommentRepository inquiryCommentRepository;
|
private final InquiryCommentRepository inquiryCommentRepository;
|
||||||
private final PartnershipApplicationRepository partnershipApplicationRepository;
|
private final PartnershipApplicationRepository partnershipApplicationRepository;
|
||||||
private final AppRequestRepository appRequestRepository;
|
|
||||||
|
|
||||||
/** 현재 로그인 사용자 기준으로 허용 빈도를 넘었는지 */
|
/** 현재 로그인 사용자 기준으로 허용 빈도를 넘었는지 */
|
||||||
public boolean isExceeded(WriteTarget target) {
|
public boolean isExceeded(WriteTarget target) {
|
||||||
@@ -105,35 +97,6 @@ public class WriteRateLimitService {
|
|||||||
+ "건까지 가능합니다. 잠시 후 다시 시도해 주세요.";
|
+ "건까지 가능합니다. 잠시 후 다시 시도해 주세요.";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 클라이언트/API 신규 이용신청 재신청까지 남은 시간(분). 쿨다운이 아니면 0.
|
|
||||||
* 1분 미만 남았어도 안내 문구를 위해 최소 1을 돌려준다.
|
|
||||||
*/
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public long newAppRequestCooldownRemainingMinutes() {
|
|
||||||
String userId = currentUserId();
|
|
||||||
if (userId == null || userId.isEmpty()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
Optional<AppRequest> latest = appRequestRepository
|
|
||||||
.findFirstByApproval_Requester_IdAndTypeOrderByCreatedDateDesc(userId, AppRequestType.NEW);
|
|
||||||
if (!latest.isPresent() || latest.get().getCreatedDate() == null) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
LocalDateTime available = latest.get().getCreatedDate().plus(NEW_APP_REQUEST_COOLDOWN);
|
|
||||||
LocalDateTime now = LocalDateTime.now();
|
|
||||||
if (!available.isAfter(now)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return Math.max(1, Duration.between(now, available).toMinutes());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 쿨다운 안내 문구 */
|
|
||||||
public String cooldownMessage(long remainingMinutes) {
|
|
||||||
return "클라이언트/API 이용신청은 1시간에 1건까지 가능합니다. 약 " + remainingMinutes + "분 후 다시 시도해 주세요.";
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 감사 필드 createdBy 와 동일한 식별자(PortalUser.id). 비인증이면 null. */
|
/** 감사 필드 createdBy 와 동일한 식별자(PortalUser.id). 비인증이면 null. */
|
||||||
private static String currentUserId() {
|
private static String currentUserId() {
|
||||||
PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser();
|
PortalAuthenticatedUser user = SecurityUtil.getPortalAuthenticatedUser();
|
||||||
|
|||||||
@@ -146,12 +146,6 @@
|
|||||||
</th:block>
|
</th:block>
|
||||||
|
|
||||||
<th:block layout:fragment="contentScript">
|
<th:block layout:fragment="contentScript">
|
||||||
<!-- 신청 쿨다운 등으로 되돌아온 경우의 안내 -->
|
|
||||||
<script th:if="${error}" th:inline="javascript">
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
|
||||||
customPopups.showAlert([[${ error }]]);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<script th:inline="javascript">
|
<script th:inline="javascript">
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user