김진영 김진영 09-16
2차 commit (mjon feature/master-send-cert)
@dbb4f537e8e76b4e7ab7b10855d24a50d853170f
pom.xml
--- pom.xml
+++ pom.xml
@@ -569,20 +569,15 @@
 		    <artifactId>spring-data-redis</artifactId>
 		    <version>1.8.11.RELEASE</version>
 		</dependency>
+
+		<!-- 해외접속IP 차단 -->
 		<dependency>
-			<groupId>junit</groupId>
-			<artifactId>junit</artifactId>
-			<version>4.13.2</version>
-			<scope>test</scope>
+			<groupId>com.maxmind.geoip2</groupId>
+			<artifactId>geoip2</artifactId>
+			<version>2.15.0</version>
 		</dependency>
 
-		<dependency>
-			<groupId>org.springframework</groupId>
-			<artifactId>spring-test</artifactId>
-			<version>${spring.maven.artifact.version}</version>
-			<scope>test</scope>
-		</dependency>
-        <!-- valkey -->
+		<!-- valkey -->
 	</dependencies>
 	
     <build>
 
src/main/java/itn/com/cmm/interceptor/GeoIpBlockInterceptor.java (added)
+++ src/main/java/itn/com/cmm/interceptor/GeoIpBlockInterceptor.java
@@ -0,0 +1,122 @@
+package itn.com.cmm.interceptor;
+
+import itn.let.mjo.block.service.MjonBlockService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+
+@Component
+public class GeoIpBlockInterceptor extends HandlerInterceptorAdapter {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(GeoIpBlockInterceptor.class);
+
+    @Autowired
+    private MjonBlockService mjonBlockService;
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+
+        if (Boolean.TRUE.equals(request.getAttribute("GEOIP_CHECKED"))) {
+            Boolean isAlreadyBlocked = (Boolean) request.getAttribute("GEOIP_BLOCKED");
+            if (Boolean.TRUE.equals(isAlreadyBlocked)) {
+                redirectToAccessBlock(request, response);
+                return false;
+            }
+            return true;
+        }
+
+        request.setAttribute("GEOIP_CHECKED", true);
+
+        String clientIp = getClientIp(request);
+
+        LOGGER.warn(
+                "[GEOIP TEST] " +
+                        "RemoteAddr={}, X-Forwarded-For={}, Proxy-Client-IP={}, " +
+                        "X-Real-IP={}, ResolvedClientIp={}",
+                request.getRemoteAddr(),
+                request.getHeader("X-Forwarded-For"),
+                request.getHeader("Proxy-Client-IP"),
+                request.getHeader("X-Real-IP"),
+                clientIp
+        );
+
+        //clientIp = "51.159.125.179";
+        if ("127.0.0.1".equals(clientIp) || "0:0:0:0:0:0:0:1".equals(clientIp) || "::1".equals(clientIp)) {
+            request.setAttribute("GEOIP_BLOCKED", false);
+            return true;
+        }
+
+        // 해외 접속 검사 및 로그 적재
+        boolean isBlocked = mjonBlockService.checkAndLogGeoIpBlock(clientIp, request.getRequestURI());
+        request.setAttribute("GEOIP_BLOCKED", isBlocked);
+
+        if (isBlocked) {
+            redirectToAccessBlock(request, response);
+            return false;
+        }
+
+        return true;
+    }
+
+    private void redirectToAccessBlock(HttpServletRequest request, HttpServletResponse response) throws Exception {
+        response.reset();
+        response.setStatus(HttpServletResponse.SC_OK);
+        response.setContentType("text/html;charset=UTF-8");
+        response.setCharacterEncoding("UTF-8");
+
+        PrintWriter writer = response.getWriter();
+
+        StringBuilder sb = new StringBuilder();
+        sb.append("<!DOCTYPE html>");
+        sb.append("<html lang='ko'>");
+        sb.append("<head>");
+        sb.append("<meta charset='UTF-8'>");
+        sb.append("<meta name='viewport' content='width=device-width, initial-scale=1.0'>");
+        sb.append("<title>국외 IP 접속 제한 안내</title>");
+        sb.append("<style>");
+        sb.append("html, body { margin: 0 !important; padding: 0 !important; width: 100% !important; height: 100% !important; overflow: hidden !important; }");
+        sb.append(".geoip-full-mask { position: fixed !important; top: 0 !important; left: 0 !important; width: 100vw !important; height: 100vh !important; background-color: #eee !important; z-index: 99999999 !important; display: flex !important; justify-content: center !important; align-items: center !important; }");
+        sb.append(".geoip-card-box { width: 620px !important; min-height: 260px !important; padding: 35px 40px 50px 40px !important; background-color: #fff !important; border: 15px solid #d8d8d8 !important; box-sizing: border-box !important; }");
+        sb.append(".geoip-card-box h1 { color: #222 !important; font-size: 24px !important; text-align: center !important; border-bottom: 1px solid #a8a8a8 !important; padding: 0 0 20px 0 !important; font-weight: 600 !important; margin-top: 0 !important; word-break: keep-all !important; font-family: 'Noto Sans KR', sans-serif !important; }");
+        sb.append(".geoip-card-box p { text-align: center !important; margin: 20px 0 0 0 !important; font-size: 16px !important; font-weight: 400 !important; color: #555 !important; line-height: 28px !important; word-break: keep-all !important; font-family: 'Noto Sans KR', sans-serif !important; }");
+        sb.append("</style>");
+        sb.append("</head>");
+        sb.append("<body>");
+        sb.append("<div class='geoip-full-mask'>");
+        sb.append("    <div class='geoip-card-box'>");
+        sb.append("        <h1>국외 IP 접속 제한 안내</h1>");
+        sb.append("        <p>문자온은 보안 정책에 따라 국내에서만 서비스를 이용하실 수 있습니다.<br><br>국외 IP로 확인되어 접속이 제한되었습니다.<br>국내 네트워크 환경에서 다시 접속해 주시기 바랍니다.</p>");
+        sb.append("    </div>");
+        sb.append("</div>");
+        sb.append("</body>");
+        sb.append("</html>");
+
+        writer.write(sb.toString());
+        writer.flush();
+
+        writer.flush();
+    }
+
+    private String getClientIp(HttpServletRequest request) {
+        String ip = request.getHeader("X-Forwarded-For");
+        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("Proxy-Client-IP");
+        }
+        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("X-Real-IP");
+        }
+        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getRemoteAddr();
+        }
+        if (ip != null && ip.contains(",")) {
+            ip = ip.split(",")[0].trim();
+        }
+        return ip;
+    }
+}(No newline at end of file)
 
src/main/java/itn/com/cmm/interceptor/UserAccessLogInterceptor.java (added)
+++ src/main/java/itn/com/cmm/interceptor/UserAccessLogInterceptor.java
@@ -0,0 +1,83 @@
+package itn.com.cmm.interceptor;
+
+import itn.com.cmm.util.ClientRequestUtil;
+import itn.let.sym.log.access.UserAccessLogService;
+import itn.let.sym.log.access.UserAccessLogVO;
+import itn.let.sym.mnu.mpm.service.EgovMenuManageService;
+import itn.let.utl.sim.service.EgovClntInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+import javax.annotation.Resource;
+import javax.servlet.DispatcherType;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@Component
+public class UserAccessLogInterceptor extends HandlerInterceptorAdapter {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(UserAccessLogInterceptor.class);
+
+    private static final String LOGGED_ATTRIBUTE = UserAccessLogInterceptor.class.getName() + ".LOGGED";
+
+
+    @Resource(name = "userAccessLogService")
+    private UserAccessLogService userAccessLogService;
+    @Resource(name = "meunManageService")
+    private EgovMenuManageService menuManageService;
+
+    @Override
+    public void afterCompletion(
+            HttpServletRequest request,
+            HttpServletResponse response,
+            Object handler,
+            Exception ex
+    ) throws Exception {
+
+        try {
+            if (request.getDispatcherType() != DispatcherType.REQUEST) {
+                return;
+            }
+
+            if (!(handler instanceof HandlerMethod)) {
+                return;
+            }
+
+            if (Boolean.TRUE.equals(request.getAttribute(LOGGED_ATTRIBUTE))) {
+                return;
+            }
+            String requestUri = request.getRequestURI();
+            String menuNm = menuManageService.selectAnonymousMenuNmByUrl(requestUri);
+
+            if (menuNm == null) {
+                return;
+            }
+
+            request.setAttribute(LOGGED_ATTRIBUTE, Boolean.TRUE);
+
+            int statusCode = response.getStatus();
+
+            boolean success = ex == null && statusCode < HttpServletResponse.SC_BAD_REQUEST;
+
+            UserAccessLogVO userAccessLogVO = new UserAccessLogVO();
+            userAccessLogVO.setUserId(ClientRequestUtil.getUserId(request));
+            userAccessLogVO.setAccessIp(EgovClntInfo.getClntIP(request));
+            userAccessLogVO.setUri(requestUri);
+            userAccessLogVO.setMenuNm(menuNm);
+            userAccessLogVO.setResult(success ? "SUCCESS" : "FAIL");
+
+            userAccessLogService.insertUserAccessLog(userAccessLogVO);
+        } catch (Exception logException) {
+            LOGGER.error(
+                    "사용자 접근 로그 저장 중 오류가 발생했습니다. URI: {}",
+                    request.getRequestURI(),
+                    logException
+            );
+        } finally {
+            super.afterCompletion(request, response, handler, ex);
+        }
+    }
+}(No newline at end of file)
 
src/main/java/itn/com/cmm/util/ClientRequestUtil.java (added)
+++ src/main/java/itn/com/cmm/util/ClientRequestUtil.java
@@ -0,0 +1,35 @@
+package itn.com.cmm.util;
+
+import itn.com.cmm.LoginVO;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+public class ClientRequestUtil {
+    private static final String ANONYMOUS = "ANONYMOUS";
+    private static final String LOGIN_VO_ATTRIBUTE = "LoginVO";
+
+    public static String getUserId(HttpServletRequest request) {
+        if (request == null) {
+            return ANONYMOUS;
+        }
+
+        HttpSession session = request.getSession(false);
+
+        if (session == null) {
+            return ANONYMOUS;
+        }
+
+        Object loginAttribute = session.getAttribute(LOGIN_VO_ATTRIBUTE);
+
+        if (!(loginAttribute instanceof LoginVO)) {
+            return ANONYMOUS;
+        }
+
+        LoginVO loginVO = (LoginVO) loginAttribute;
+
+        return loginVO.getId() == null || loginVO.getId().trim().isEmpty()
+                ? ANONYMOUS
+                : loginVO.getId();
+    }
+}
src/main/java/itn/com/cmm/util/MsgSendUtils.java
--- src/main/java/itn/com/cmm/util/MsgSendUtils.java
+++ src/main/java/itn/com/cmm/util/MsgSendUtils.java
@@ -55,7 +55,12 @@
 	public static final String LONG_MSG_TYPE = "6";
 	// 이벤트 최저 잔액
 	public static final double MIN_EVENT_REMAIN_CASH = 7.5; // 이벤트 최소 잔액
-	
+	// 문자 내용 중 외부 URL 탐지용 정규식
+	private static final Pattern URL_PATTERN = Pattern.compile(
+			  "\\b((https?://|www\\.)[\\w가-힣\\-]+(\\.[\\w가-힣\\-]+)+(/[^\\s]*)?"
+			+ "|[\\w가-힣\\-]+(\\.[\\w가-힣\\-]+)*\\.[a-z]{2,24}\\b(/[^\\s]*)?)"
+			, Pattern.CASE_INSENSITIVE);
+
 
 	/** 
 	 * @methodName	: getMsgType 
@@ -424,7 +429,7 @@
 
 	private static void checkSpamAndSetStatus(MjonMsgVO mjonMsgVO, String personalizedSmsTxt, List<String> resultSpamTxt, boolean isHolidayNotified) throws Exception {
 		
-
+		// SpamStatus: 스팸문자유무, DelayYn: 지연발송 여부
 		mjonMsgVO.setSpamStatus("N");
 		mjonMsgVO.setDelayYn("N");
 		
@@ -441,7 +446,14 @@
 					spmFilterTxt += spmTxt + ",";
 				}
 			}
-	
+
+			// 문자 내용 중 URL 탐지 (원본 텍스트: personalizedSmsTxt)
+			Matcher urlMatcher = URL_PATTERN.matcher(personalizedSmsTxt);
+			if (urlMatcher.find()) {
+				spmCnt++;
+				spmFilterTxt += urlMatcher.group() + ",";
+			}
+
 			if (spmCnt > 0) { // 스팸 문자가 포함된 경우
 	
 				if (StringUtil.getWordRight(spmFilterTxt.trim(), 1).equals(",")) {
@@ -455,6 +467,7 @@
 				 *  - 해당 시간이면 지연 미처리
 				 * */
 				mjonMsgVO.setSpamStatus("Y");
+				mjonMsgVO.setSpamKeyword(spmFilterTxt.trim());
 				if(isHolidayNotified) {
 					mjonMsgVO.setDelayYn("Y");
 				}
src/main/java/itn/com/uss/olh/hpc/service/HackIpService.java
--- src/main/java/itn/com/uss/olh/hpc/service/HackIpService.java
+++ src/main/java/itn/com/uss/olh/hpc/service/HackIpService.java
@@ -38,4 +38,6 @@
 	
 	// 차단아이피가 아닌 고트래픽 유저 검색 후 차단처리
 	public void selectHighTrafficNotIgnoreIp() throws Exception;
+
+	boolean isBlockedIp(String userIp) throws Exception;
 }
src/main/java/itn/com/uss/olh/hpc/service/impl/HackIpDAO.java
--- src/main/java/itn/com/uss/olh/hpc/service/impl/HackIpDAO.java
+++ src/main/java/itn/com/uss/olh/hpc/service/impl/HackIpDAO.java
@@ -32,6 +32,7 @@
 	}
 
 	public void updateHackIp(HackIpVO hackIpVO) throws Exception{
+
 		update("hackIpDAO.updateHackIp", hackIpVO);
 	}
 
src/main/java/itn/com/uss/olh/hpc/service/impl/HackIpServiceImpl.java
--- src/main/java/itn/com/uss/olh/hpc/service/impl/HackIpServiceImpl.java
+++ src/main/java/itn/com/uss/olh/hpc/service/impl/HackIpServiceImpl.java
@@ -135,9 +135,16 @@
 			System.out.println("@@@@@@@@@@@@@@@ ----------- "+msg);
 //			mjonCommon.sendSimpleSlackMsg(msg);
 		}
-//		
-		
-		
+//
 	}
-	
+
+	@Override
+	public boolean isBlockedIp(String userIp) throws Exception {
+		HackIpVO hackIpVO = new HackIpVO();
+		hackIpVO.setAccessIp(userIp);
+		hackIpVO.setBlockYn("Y");
+
+		return selectHackIpVO(hackIpVO) != null;
+	}
+
 }
src/main/java/itn/com/utl/page/PaginationUtil.java
--- src/main/java/itn/com/utl/page/PaginationUtil.java
+++ src/main/java/itn/com/utl/page/PaginationUtil.java
@@ -4,6 +4,17 @@
 
 
 public class PaginationUtil {
+    /**
+     * 작성자   : 조민수
+     * 작성일자 : 2026/07/24
+     * 공통 페이징 정보를 초기화한다.
+     *
+     * <p>사용 방법 및 적용 예시는 사내 Wiki의
+     * 'https://wiki.iten.co.kr/40.프로젝트:2026_base_프로젝트:0.참고자료:공용함수정보:pagination-util' 문서를 참고한다.</p>
+     *
+     * @param <T vo> BasePaginationVO를 상속한 VO
+     * @return 초기화된 PaginationInfo
+     */
     public static <T extends PageableVO> PaginationInfo init(T vo) {
         PaginationInfo paginationInfo = new PaginationInfo();
         paginationInfo.setCurrentPageNo(vo.getPageIndex());
@@ -25,5 +36,6 @@
         }
         return init(vo);
     }
-}
 
+
+}
 
src/main/java/itn/com/utl/response/ApiResponse.java (added)
+++ src/main/java/itn/com/utl/response/ApiResponse.java
@@ -0,0 +1,52 @@
+package itn.com.utl.response;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Setter
+@Getter
+public class ApiResponse<T> {
+    private boolean success;
+    private String message;
+    private T data;
+
+    public static <T> ApiResponse<T> success() {return new ApiResponse<>(true, null, null);}
+
+    public static <T> ApiResponse<T> success(T data) {
+        return new ApiResponse<>(true, null, data);
+    }
+
+    public static <T> ApiResponse<T> success(T data, String message) {
+        return  new ApiResponse<>(true, message, data);
+    }
+
+    public static <T> ApiResponse<T> fail(String message) {
+        return new ApiResponse<>(false, message, null);
+    }
+
+    public ApiResponse() {}
+
+    public ApiResponse(boolean success, String message, T data) {
+        this.success = success;
+        this.message = message;
+        this.data = data;
+    }
+
+    public static <T> ApiResponse<T> successWithMessage(String message){
+        ApiResponse<T> res = new ApiResponse<>();
+        res.success = true;
+        res.message = message;
+        res.data = null;
+        return res;
+    }
+
+    @Override
+    public String toString() {
+        return "ApiResponse{" +
+                "success=" + success +
+                ", message='" + message + '\'' +
+                ", data=" + data +
+                '}';
+    }
+}
+
 
src/main/java/itn/com/utl/response/PageResponse.java (added)
+++ src/main/java/itn/com/utl/response/PageResponse.java
@@ -0,0 +1,59 @@
+package itn.com.utl.response;
+
+import egovframework.rte.ptl.mvc.tags.ui.pagination.PaginationInfo;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.util.List;
+import java.util.Map;
+
+@Getter
+@Setter
+@NoArgsConstructor
+public class PageResponse<T> {
+    private List<T> list;
+    private int totalItems;
+    private int totalPages;
+    private int currentPage;
+    private int size;
+    private Map<String, Object> extraData;
+    
+    public PageResponse(List<T> list, Map<String, Object> extraData, int currentPage, int totalItems, int totalPages, int size) {
+        this.list = list;
+        this.extraData = extraData;
+        this.currentPage = currentPage;
+        this.totalItems = totalItems;
+        this.totalPages = totalPages;
+        this.size = size;
+    }
+
+    public static <T> PageResponse<T> withList(List<T> list, PaginationInfo pageInfo) {
+        PageResponse<T> res = new PageResponse<>();
+        res.setList(list);
+        res.setPageInfo(pageInfo);
+        return res;
+    }
+
+    public static <T> PageResponse<T> withExtra(Map<String, Object> extraData, PaginationInfo pageInfo) {
+        PageResponse<T> res = new PageResponse<>();
+        res.setExtraData(extraData);
+        res.setPageInfo(pageInfo);
+        return res;
+    }
+
+    public static <T> PageResponse<T> withAll(List<T> list, Map<String, Object> extraData, PaginationInfo pageInfo) {
+        PageResponse<T> res = new PageResponse<>();
+        res.setList(list);
+        res.setExtraData(extraData);
+        res.setPageInfo(pageInfo);
+        return res;
+    }
+
+    private void setPageInfo(PaginationInfo pageInfo) {
+        this.currentPage = pageInfo.getCurrentPageNo();
+        this.totalItems = pageInfo.getTotalRecordCount();
+        this.totalPages = pageInfo.getTotalPageCount();
+        this.size = pageInfo.getRecordCountPerPage();
+    }
+}
src/main/java/itn/let/cert/ip/service/MberCertLoginLogVO.java
--- src/main/java/itn/let/cert/ip/service/MberCertLoginLogVO.java
+++ src/main/java/itn/let/cert/ip/service/MberCertLoginLogVO.java
@@ -1,5 +1,6 @@
 package itn.let.cert.ip.service;
 
+import itn.com.utl.page.PageableVO;
 import itn.let.uss.umt.service.UserDefaultVO;
 /**
  * 
@@ -15,7 +16,7 @@
  * 
  * 
  */
-public class MberCertLoginLogVO extends UserDefaultVO{
+public class MberCertLoginLogVO extends UserDefaultVO {
 
 	
 	/**
src/main/java/itn/let/cert/phone/service/CertPhoneService.java
--- src/main/java/itn/let/cert/phone/service/CertPhoneService.java
+++ src/main/java/itn/let/cert/phone/service/CertPhoneService.java
@@ -25,12 +25,19 @@
 
 	public StatusResponse selectMberCertPhoneList(MberCertPhoneVO mberCertPhoneVO);
 
-	public StatusResponse insertCertPhone(MberCertPhoneVO mberCertPhoneVO, String moblphonNo);
+	public StatusResponse insertCertPhone(MberCertPhoneVO mberCertPhoneVO, String moblphonNo,
+			String dept, String accessIp) throws Exception;
 
-	public StatusResponse deleteCertPhone(MberCertPhoneVO mberCertPhoneVO);
+	public StatusResponse deleteCertPhone(MberCertPhoneVO mberCertPhoneVO, String accessIp) throws Exception;
 
 	public List<MberCertPhoneVO> selectMberCertPhoneList(String userId);
-	
+
+	public List<MberCertPhoneVO> selectEligibleCertPhoneList(String userId);
+
+	public boolean isRegisteredAuthPhone(String userId, String phoneNo);
+
+	public boolean isEligibleCertPhone(String userId, String phoneNo);
+
 	public StatusResponse selectMberCertPhoneListWhereUserId(String userId);
 
 	public StatusResponse updateMberCertPhone(MberCertPhoneVO mberCertPhoneVO);
@@ -41,6 +48,8 @@
 
 	public StatusResponse insertCertLog(HttpServletRequest request) throws Exception; 
 
+	public void resetMemberPhone(String mberId, String phoneNo, String accessIp) throws Exception;
+
 	
 	
 }
src/main/java/itn/let/cert/phone/service/MberCertPhoneVO.java
--- src/main/java/itn/let/cert/phone/service/MberCertPhoneVO.java
+++ src/main/java/itn/let/cert/phone/service/MberCertPhoneVO.java
@@ -31,6 +31,7 @@
 	private String certAlias;
 	private String certMemo;
 	private String certUseYn;
+	private String phoneType;
 	
 	private String checkNo;
 	
@@ -77,6 +78,12 @@
 	public void setCertUseYn(String certUseYn) {
 		this.certUseYn = certUseYn;
 	}
+	public String getPhoneType() {
+		return phoneType;
+	}
+	public void setPhoneType(String phoneType) {
+		this.phoneType = phoneType;
+	}
 	public String getCheckNo() {
 		return checkNo;
 	}
src/main/java/itn/let/cert/phone/service/impl/CertPhoneDAO.java
--- src/main/java/itn/let/cert/phone/service/impl/CertPhoneDAO.java
+++ src/main/java/itn/let/cert/phone/service/impl/CertPhoneDAO.java
@@ -15,20 +15,37 @@
 		return (List<MberCertPhoneVO>) list("mberCertPhoneVO.selectMberCertPhoneList", userId);
 	}
 
+	@SuppressWarnings("unchecked")
+	public List<MberCertPhoneVO> selectEligibleCertPhoneList(String userId) {
+		return (List<MberCertPhoneVO>) list("mberCertPhoneVO.selectEligibleCertPhoneList", userId);
+	}
+
+	public int countRegisteredAuthPhone(MberCertPhoneVO mberCertPhoneVO) {
+		return (Integer) select("mberCertPhoneVO.countRegisteredAuthPhone", mberCertPhoneVO);
+	}
+
+	public int countEligibleCertPhone(MberCertPhoneVO mberCertPhoneVO) {
+		return (Integer) select("mberCertPhoneVO.countEligibleCertPhone", mberCertPhoneVO);
+	}
+
+	public int countAdditionalCertPhone(String userId) {
+		return (Integer) select("mberCertPhoneVO.countAdditionalCertPhone", userId);
+	}
+
 	public void insertCertPhone(MberCertPhoneVO mberCertPhoneVO) {
 		insert("mberCertPhoneVO.insertCertPhone", mberCertPhoneVO);
 	}
 
-	public void deleteCertPhone(MberCertPhoneVO mberCertPhoneVO) {
-		insert("mberCertPhoneVO.deleteCertPhone", mberCertPhoneVO);
+	public int deleteCertPhone(MberCertPhoneVO mberCertPhoneVO) {
+		return update("mberCertPhoneVO.deleteCertPhone", mberCertPhoneVO);
 	}
 
 	public MberCertPhoneVO findByCertPhoneInfo(MberCertPhoneVO mberCertPhoneVO) {
 		return (MberCertPhoneVO) select("mberCertPhoneVO.findByCertPhoneInfo", mberCertPhoneVO);
 	}
 
-	public void updateMberCertPhone(MberCertPhoneVO mberCertPhoneVO) {
-		insert("mberCertPhoneVO.updateMberCertPhone", mberCertPhoneVO);
+	public int updateMberCertPhone(MberCertPhoneVO mberCertPhoneVO) {
+		return update("mberCertPhoneVO.updateMberCertPhone", mberCertPhoneVO);
 		
 	}
 
@@ -47,4 +64,8 @@
 	public void insertCertLog(AuthCertVO authCertVO) throws Exception {
 		insert("certPhoneDAO.insertCertLog", authCertVO);
 	}
+
+	public void resetMemberPhone(MberCertPhoneVO mberCertPhoneVO) {
+		update("certPhoneDAO.resetMemberPhone", mberCertPhoneVO);
+	}
 }
src/main/java/itn/let/cert/phone/service/impl/CertPhoneServiceImpl.java
--- src/main/java/itn/let/cert/phone/service/impl/CertPhoneServiceImpl.java
+++ src/main/java/itn/let/cert/phone/service/impl/CertPhoneServiceImpl.java
@@ -10,6 +10,7 @@
 
 import org.springframework.http.HttpStatus;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import egovframework.rte.fdl.cmmn.EgovAbstractServiceImpl;
 import egovframework.rte.fdl.security.userdetails.util.EgovUserDetailsHelper;
@@ -20,6 +21,9 @@
 import itn.let.cert.phone.service.MberCertPhoneVO;
 import itn.let.mail.service.StatusResponse;
 import itn.let.uat.uia.service.AuthCertVO;
+import itn.let.uat.uia.service.SecondAuthMethodLogConstants;
+import itn.let.uat.uia.service.SecondAuthMethodLogService;
+import itn.let.uat.uia.service.SecondAuthMethodLogVO;
 
 @Service("CertPhoneService")
 public class CertPhoneServiceImpl extends EgovAbstractServiceImpl implements CertPhoneService {
@@ -27,6 +31,8 @@
 	@Resource(name = "CertPhoneDAO")
 	private CertPhoneDAO certPhoneDAO;
 
+	@Resource(name = "secondAuthMethodLogService")
+	private SecondAuthMethodLogService secondAuthMethodLogService;
 
 	@Override
 	public StatusResponse selectMberCertPhoneList(MberCertPhoneVO mberCertPhoneVO) {
@@ -36,30 +42,41 @@
 
 
 	@Override
-	public StatusResponse insertCertPhone(MberCertPhoneVO mberCertPhoneVO, String moblphonNo) {
-		System.out.println("findByCertPhoneInfo : :::: ");
-		try {
-		
-			MberCertPhoneVO mberCertPhoneListVO = certPhoneDAO.findByCertPhoneInfo(mberCertPhoneVO);
-			if(mberCertPhoneListVO != null) {
-				if(mberCertPhoneListVO.getMbtlnum().equals(moblphonNo)) {
-					return new StatusResponse(HttpStatus.CONFLICT, "중복 번호입니다.", LocalDateTime.now());
-				}
-			}
-			
-			certPhoneDAO.insertCertPhone(mberCertPhoneVO);
-			
-		} catch (Exception e) {
-			e.printStackTrace();	// TODO: handle exception
-			return new StatusResponse(HttpStatus.OK, "오류가 발생하였습니다.", LocalDateTime.now());
+	@Transactional(rollbackFor = Exception.class)
+	public StatusResponse insertCertPhone(MberCertPhoneVO mberCertPhoneVO, String moblphonNo, String dept, String accessIp) throws Exception {
+		int maxCount = "c".equalsIgnoreCase(dept) ? 4 : 3;
+		if (certPhoneDAO.countAdditionalCertPhone(mberCertPhoneVO.getMberId()) >= maxCount - 1) {
+			return new StatusResponse(HttpStatus.CONFLICT,
+					"인증 휴대폰번호는 최대 " + maxCount + "개까지 등록할 수 있습니다.", LocalDateTime.now());
 		}
+		if (!isEligibleCertPhone(mberCertPhoneVO.getMberId(), mberCertPhoneVO.getMbtlnum())) {
+			return new StatusResponse(HttpStatus.BAD_REQUEST,
+					"승인 완료된 발신번호가 아닙니다.", LocalDateTime.now());
+		}
+		MberCertPhoneVO mberCertPhoneListVO = certPhoneDAO.findByCertPhoneInfo(mberCertPhoneVO);
+		if(mberCertPhoneListVO != null) {
+			return new StatusResponse(HttpStatus.CONFLICT, "중복 번호입니다.", LocalDateTime.now());
+		}
+		if (mberCertPhoneVO.getMbtlnum() != null && mberCertPhoneVO.getMbtlnum().equals(moblphonNo)) {
+			return new StatusResponse(HttpStatus.CONFLICT, "가입 휴대폰번호와 동일한 번호입니다.", LocalDateTime.now());
+		}
+
+		certPhoneDAO.insertCertPhone(mberCertPhoneVO);
+		insertMethodChangeLog(mberCertPhoneVO.getMberId(), mberCertPhoneVO.getMbtlnum(),
+				SecondAuthMethodLogConstants.ACTION_REGISTER, SecondAuthMethodLogConstants.VERIFY_SMS, accessIp);
 		return new StatusResponse(HttpStatus.OK, "등록 하였습니다.", LocalDateTime.now());
 	}
 
 
 	@Override
-	public StatusResponse deleteCertPhone(MberCertPhoneVO mberCertPhoneVO) {
-		certPhoneDAO.deleteCertPhone(mberCertPhoneVO);
+	@Transactional(rollbackFor = Exception.class)
+	public StatusResponse deleteCertPhone(MberCertPhoneVO mberCertPhoneVO, String accessIp) throws Exception {
+		int updatedCount = certPhoneDAO.deleteCertPhone(mberCertPhoneVO);
+		if (updatedCount < 1) {
+			return new StatusResponse(HttpStatus.NOT_FOUND, "삭제할 휴대폰번호를 찾을 수 없습니다.", LocalDateTime.now());
+		}
+		insertMethodChangeLog(mberCertPhoneVO.getMberId(), mberCertPhoneVO.getMbtlnum(),
+				SecondAuthMethodLogConstants.ACTION_DELETE, SecondAuthMethodLogConstants.VERIFY_SMS, accessIp);
 		return new StatusResponse(HttpStatus.OK, "삭제 하였습니다.", LocalDateTime.now());
 	}
 	
@@ -77,6 +94,26 @@
 		
 	}
 
+	@Override
+	public List<MberCertPhoneVO> selectEligibleCertPhoneList(String userId) {
+		return certPhoneDAO.selectEligibleCertPhoneList(userId);
+	}
+
+	@Override
+	public boolean isRegisteredAuthPhone(String userId, String phoneNo) {
+		MberCertPhoneVO vo = new MberCertPhoneVO();
+		vo.setMberId(userId);
+		vo.setMbtlnum(phoneNo);
+		return certPhoneDAO.countRegisteredAuthPhone(vo) > 0;
+	}
+
+	@Override
+	public boolean isEligibleCertPhone(String userId, String phoneNo) {
+		MberCertPhoneVO vo = new MberCertPhoneVO();
+		vo.setMberId(userId);
+		vo.setMbtlnum(phoneNo);
+		return certPhoneDAO.countEligibleCertPhone(vo) > 0;
+	}
 
 	@Override
 	public StatusResponse selectMberCertPhoneListWhereUserId(String userId) {
@@ -88,7 +125,10 @@
 
 	@Override
 	public StatusResponse updateMberCertPhone(MberCertPhoneVO mberCertPhoneVO) {
-		certPhoneDAO.updateMberCertPhone(mberCertPhoneVO);
+		int updatedCount = certPhoneDAO.updateMberCertPhone(mberCertPhoneVO);
+		if (updatedCount < 1) {
+			return new StatusResponse(HttpStatus.NOT_FOUND, "수정할 휴대폰번호를 찾을 수 없습니다.", LocalDateTime.now());
+		}
 		return new StatusResponse(HttpStatus.OK, "수정 하였습니다.", LocalDateTime.now());
 	}
 
@@ -135,6 +175,28 @@
 		
 		return new StatusResponse(HttpStatus.OK, "", LocalDateTime.now());
 	}
+
+	@Override
+	@Transactional(rollbackFor = Exception.class)
+	public void resetMemberPhone(String mberId, String phoneNo, String accessIp) throws Exception {
+		MberCertPhoneVO mberCertPhoneVO = new MberCertPhoneVO();
+		mberCertPhoneVO.setMberId(mberId);
+		mberCertPhoneVO.setMbtlnum(phoneNo);
+		certPhoneDAO.resetMemberPhone(mberCertPhoneVO);
+		insertMethodChangeLog(mberId, phoneNo, SecondAuthMethodLogConstants.ACTION_UPDATE,
+				SecondAuthMethodLogConstants.VERIFY_KMC, accessIp);
+	}
+
+	private void insertMethodChangeLog(String mberId, String phoneNo, String actionType,
+			String verifyType, String accessIp) throws Exception {
+		SecondAuthMethodLogVO logVO = new SecondAuthMethodLogVO();
+		logVO.setMberId(mberId);
+		logVO.setPhoneNo(phoneNo);
+		logVO.setActionType(actionType);
+		logVO.setVerifyType(verifyType);
+		logVO.setAccessIp(accessIp);
+		secondAuthMethodLogService.insertSecondAuthMethodLog(logVO);
+	}
 	
 	
 	
 
src/main/java/itn/let/cert/phone/support/CertPhoneAuthConstants.java (added)
+++ src/main/java/itn/let/cert/phone/support/CertPhoneAuthConstants.java
@@ -0,0 +1,21 @@
+package itn.let.cert.phone.support;
+
+public final class CertPhoneAuthConstants {
+
+    public static final String PURPOSE_ADD_EXISTING = "ADD_EXISTING";
+    public static final String PURPOSE_ADD_NEW = "ADD_NEW";
+    public static final String PURPOSE_DELETE_EXISTING = "DELETE_EXISTING";
+
+    public static final String PENDING_PHONE_PREFIX = "certPhonePendingPhone.";
+    public static final String SEND_COUNT_PREFIX = "certPhoneSendCount.";
+    public static final String LAST_SENT_AT_PREFIX = "certPhoneLastSentAt.";
+    public static final String VERIFIED_PHONE_PREFIX = "certPhoneVerifiedPhone.";
+    public static final String VERIFIED_AT_PREFIX = "certPhoneVerifiedAt.";
+
+    public static final int MAX_SEND_COUNT = 4;
+    public static final long RESEND_INTERVAL_MILLIS = 30 * 1000L;
+    public static final long VERIFIED_TIMEOUT_MILLIS = 5 * 60 * 1000L;
+
+    private CertPhoneAuthConstants() {
+    }
+}
 
src/main/java/itn/let/cert/phone/support/PhoneResetSessionConstants.java (added)
+++ src/main/java/itn/let/cert/phone/support/PhoneResetSessionConstants.java
@@ -0,0 +1,11 @@
+package itn.let.cert.phone.support;
+
+public final class PhoneResetSessionConstants {
+
+	public static final String PENDING_USER_ID = "phoneResetPendingUserId";
+	public static final String PENDING_AT = "phoneResetPendingAt";
+	public static final long TIMEOUT_MILLIS = 10 * 60 * 1000L;
+
+	private PhoneResetSessionConstants() {
+	}
+}
src/main/java/itn/let/cert/phone/web/CertPhoneController.java
--- src/main/java/itn/let/cert/phone/web/CertPhoneController.java
+++ src/main/java/itn/let/cert/phone/web/CertPhoneController.java
@@ -6,19 +6,23 @@
 
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
 
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.servlet.ModelAndView;
 
 import egovframework.rte.fdl.security.userdetails.util.EgovUserDetailsHelper;
 import itn.com.cmm.LoginVO;
+import itn.com.cmm.util.IpUtil;
 import itn.com.utl.fcc.service.EgovStringUtil;
 import itn.let.cert.phone.service.CertPhoneService;
 import itn.let.cert.phone.service.MberCertPhoneVO;
+import itn.let.cert.phone.support.CertPhoneAuthConstants;
 import itn.let.mail.service.StatusResponse;
 import itn.let.mjo.msg.service.MjonMsgVO;
 import itn.let.mjo.msgdata.service.MjonMsgDataService;
@@ -26,214 +30,284 @@
 import itn.let.uat.uia.web.SendLogVO;
 
 /**
- * 
- * @author 		: 이호영
- * @fileName 	: CertIpController.java 
- * @date 		: 2024.11.19
+ *
+ * @author : 이호영
+ * @fileName : CertIpController.java
+ * @date : 2024.11.19
  * @description : 허용 IP 관련 컨트롤러
- * =========================================================== 
- * DATE          AUTHOR   NOTE 
+ * ===========================================================
+ * DATE          AUTHOR   NOTE
  * ----------------------------------------------------------- *
  * 2024.11.19    이호영          최초 생성
- * 
- * 
- * 
+ *
+ *
+ *
  */
 @Controller
 public class CertPhoneController {
-	
-	
-	/* certPhoneService */
-	@Resource(name = "CertPhoneService")
-	private CertPhoneService certPhoneService;
-	
-	@Resource(name = "MjonMsgDataService")
-	private MjonMsgDataService mjonMsgDataService;
-	
-	
-	
-	@RequestMapping(value = {"/cert/phone/selectMberCertPhoneList"})
-	public ResponseEntity<StatusResponse> selectMberCertPhoneList(MberCertPhoneVO mberCertPhoneVO) throws Exception {
-		
-		ModelAndView modelAndView = new ModelAndView();
-		modelAndView.setViewName("jsonView");
-		
-		//로그인 권한정보 불러오기
-		LoginVO loginVO = EgovUserDetailsHelper.isAuthenticated()? (LoginVO)EgovUserDetailsHelper.getAuthenticatedUser():null;
-		String userId = loginVO == null ? "" : EgovStringUtil.isNullToString(loginVO.getId());
-		
-		if(userId == null) {
-			if(StringUtils.isEmpty(userId)) return ResponseEntity.ok().body(new StatusResponse(HttpStatus.BAD_REQUEST, "로그인 후 이용해 주세요", LocalDateTime.now()));
-		}
-		mberCertPhoneVO.setMberId(userId);
 
-		return ResponseEntity.ok().body(certPhoneService.selectMberCertPhoneListWhereUserId(userId));
-	}	
 
-	@RequestMapping(value = {"/cert/phone/findByCertPhoneInfo.do"})
-	public ResponseEntity<StatusResponse> findByCertPhoneInfo(MberCertPhoneVO mberCertPhoneVO) throws Exception {
-		
-		ModelAndView modelAndView = new ModelAndView();
-		modelAndView.setViewName("jsonView");
-		
-		//로그인 권한정보 불러오기
-		LoginVO loginVO = EgovUserDetailsHelper.isAuthenticated()? (LoginVO)EgovUserDetailsHelper.getAuthenticatedUser():null;
-		String userId = loginVO == null ? "" : EgovStringUtil.isNullToString(loginVO.getId());
-		
-		if(userId == null) {
-			if(StringUtils.isEmpty(userId)) return ResponseEntity.ok().body(new StatusResponse(HttpStatus.BAD_REQUEST, "로그인 후 이용해 주세요", LocalDateTime.now()));
-		}
-		return ResponseEntity.ok().body(certPhoneService.findByCertPhoneInfo(mberCertPhoneVO));
-	}	
-	
-	@RequestMapping(value = {"/cert/phone/insertCertPhone.do"})
-	public ResponseEntity<StatusResponse> insertCertPhone(MberCertPhoneVO mberCertPhoneVO) throws Exception {
-		
-		ModelAndView modelAndView = new ModelAndView();
-		modelAndView.setViewName("jsonView");
-		
-		
-		System.out.println(" :: insertCertPhone :: ");
-		
-		//로그인 권한정보 불러오기
-		LoginVO loginVO = EgovUserDetailsHelper.isAuthenticated()? (LoginVO)EgovUserDetailsHelper.getAuthenticatedUser():null;
-		String userId = loginVO == null ? "" : EgovStringUtil.isNullToString(loginVO.getId());
-		
-		if(userId == null) {
-			if(StringUtils.isEmpty(userId)) return ResponseEntity.ok().body(new StatusResponse(HttpStatus.BAD_REQUEST, "로그인 후 이용해 주세요", LocalDateTime.now()));
-		}
-		mberCertPhoneVO.setFrstRegisterId(userId);
-		mberCertPhoneVO.setMberId(userId);
+    /* certPhoneService */
+    @Resource(name = "CertPhoneService")
+    private CertPhoneService certPhoneService;
 
-		
-		return ResponseEntity.ok().body(certPhoneService.insertCertPhone(mberCertPhoneVO, loginVO.getMoblphonNo()));
-	}	
-	
-	@RequestMapping(value = {"/cert/phone/updateCertPhone.do"})
-	public ResponseEntity<StatusResponse> updateCertPhone(MberCertPhoneVO mberCertPhoneVO) throws Exception {
-		
-		ModelAndView modelAndView = new ModelAndView();
-		modelAndView.setViewName("jsonView");
-		
-		//로그인 권한정보 불러오기
-		LoginVO loginVO = EgovUserDetailsHelper.isAuthenticated()? (LoginVO)EgovUserDetailsHelper.getAuthenticatedUser():null;
-		String userId = loginVO == null ? "" : EgovStringUtil.isNullToString(loginVO.getId());
-		
-		if(userId == null) {
-			if(StringUtils.isEmpty(userId)) return ResponseEntity.ok().body(new StatusResponse(HttpStatus.BAD_REQUEST, "로그인 후 이용해 주세요", LocalDateTime.now()));
-		}
-		mberCertPhoneVO.setFrstRegisterId(userId);
-		if(StringUtils.isEmpty(mberCertPhoneVO.getMberId()) ) {
-			mberCertPhoneVO.setMberId(userId);
-		}
-		
-		
-		return ResponseEntity.ok().body(certPhoneService.updateMberCertPhone(mberCertPhoneVO));
-	}	
-	
-	@RequestMapping(value = {"/cert/phone/deleteCertPhone.do"})
-	public ResponseEntity<StatusResponse> deleteCertPhone(MberCertPhoneVO mberCertPhoneVO) throws Exception {
-		
-		ModelAndView modelAndView = new ModelAndView();
-		modelAndView.setViewName("jsonView");
-		
-		//로그인 권한정보 불러오기
-		LoginVO loginVO = EgovUserDetailsHelper.isAuthenticated()? (LoginVO)EgovUserDetailsHelper.getAuthenticatedUser():null;
-		String userId = loginVO == null ? "" : EgovStringUtil.isNullToString(loginVO.getId());
-		
-		if(userId == null) {
-			if(StringUtils.isEmpty(userId)) return ResponseEntity.ok().body(new StatusResponse(HttpStatus.BAD_REQUEST, "로그인 후 이용해 주세요", LocalDateTime.now()));
-		}
-		mberCertPhoneVO.setLastUpdusrId(userId);
+    @Resource(name = "MjonMsgDataService")
+    private MjonMsgDataService mjonMsgDataService;
 
-		if(StringUtils.isEmpty(mberCertPhoneVO.getMberId()) ) {
-			mberCertPhoneVO.setMberId(userId);
-		}
-		
-		
-		return ResponseEntity.ok().body(certPhoneService.deleteCertPhone(mberCertPhoneVO));
-	}	
-	
-	
-	
-	
-	
-	@RequestMapping(value = {"/cert/phone/sendSysMsgDataAjax.do"})
-	public ResponseEntity<StatusResponse> sendSysMsgDataAjax(MberCertPhoneVO mberCertPhoneVO,
-			HttpServletRequest request) throws Exception {
-		
-		
-		//로그인 권한정보 불러오기
-		LoginVO loginVO = EgovUserDetailsHelper.isAuthenticated()? (LoginVO)EgovUserDetailsHelper.getAuthenticatedUser():null;
-		String userId = loginVO == null ? "" : EgovStringUtil.isNullToString(loginVO.getId());
-		
-		if(userId == null) {
-			if(StringUtils.isEmpty(userId)) return ResponseEntity.ok().body(new StatusResponse(HttpStatus.BAD_REQUEST, "로그인 후 이용해 주세요", LocalDateTime.now()));
-		}
-		mberCertPhoneVO.setMberId(userId);
 
-		// 번호 중복 확인
-		MberCertPhoneVO mberCertIpListVO = (MberCertPhoneVO) certPhoneService.findByCertPhoneInfo(mberCertPhoneVO).getObject();
-		if(mberCertIpListVO != null || loginVO.getMoblphonNo().equals(mberCertPhoneVO.getMbtlnum()) ) {
-			return ResponseEntity.ok().body(new StatusResponse(HttpStatus.CONFLICT, "등록된 번호입니다.", LocalDateTime.now()));
-		}
+    @RequestMapping(value = {"/cert/phone/selectMberCertPhoneList", "/cert/phone/selectMberCertPhoneList.do"})
+    public ResponseEntity<StatusResponse> selectMberCertPhoneList(MberCertPhoneVO mberCertPhoneVO) throws Exception {
 
-		MjonMsgVO mjonMsgVO = new MjonMsgVO();
-		mjonMsgVO.setCallTo(mberCertPhoneVO.getMbtlnum());
-		MjonMsgReturnVO returnVO = mjonMsgDataService.sendSysMsgData(mjonMsgVO, request);
+        ModelAndView modelAndView = new ModelAndView();
+        modelAndView.setViewName("jsonView");
 
-		String resultSts = returnVO.getSendMsgCnt();
-		String resultBlockSts = returnVO.getSendMsgBlockCnt();
+        //로그인 권한정보 불러오기
+        LoginVO loginVO = EgovUserDetailsHelper.isAuthenticated() ? (LoginVO) EgovUserDetailsHelper.getAuthenticatedUser() : null;
+        String userId = loginVO == null ? "" : EgovStringUtil.isNullToString(loginVO.getId());
 
-		String msg= "";
-		Map<String, String> returnMap = new HashMap<>();
-		HttpStatus httpStatus = null;
-		if (!resultSts.equals("0")) {
-	
-			httpStatus = HttpStatus.OK;
-			returnMap.put("msg", "문자 전송이 완료되었습니다.");
-			returnMap.put("resultSts", resultSts);
-			returnMap.put("resultBlockSts", resultBlockSts);
+        if (StringUtils.isEmpty(userId))
+            return ResponseEntity.ok().body(new StatusResponse(HttpStatus.BAD_REQUEST, "로그인 후 이용해 주세요", LocalDateTime.now()));
+        mberCertPhoneVO.setMberId(userId);
 
-		} else {
+        return ResponseEntity.ok().body(certPhoneService.selectMberCertPhoneListWhereUserId(userId));
+    }
 
-			httpStatus = HttpStatus.BAD_REQUEST;
-			returnMap.put("msg", "문자 전송에 오류가 발생하였습니다.");
-			returnMap.put("resultSts", "0");
-			returnMap.put("resultBlockSts", "0");
+    @RequestMapping(value = {"/cert/phone/findByCertPhoneInfo.do"})
+    public ResponseEntity<StatusResponse> findByCertPhoneInfo(MberCertPhoneVO mberCertPhoneVO) throws Exception {
 
-		}
-		return ResponseEntity.ok().body(new StatusResponse(httpStatus, returnMap, LocalDateTime.now()));	
+        ModelAndView modelAndView = new ModelAndView();
+        modelAndView.setViewName("jsonView");
 
-	}	
-	
-	
+        //로그인 권한정보 불러오기
+        LoginVO loginVO = EgovUserDetailsHelper.isAuthenticated() ? (LoginVO) EgovUserDetailsHelper.getAuthenticatedUser() : null;
+        String userId = loginVO == null ? "" : EgovStringUtil.isNullToString(loginVO.getId());
 
-	@RequestMapping(value = {"/cert/phone/selectSysMsgLogCheck.do"})
-	public ResponseEntity<StatusResponse> selectSysMsgLogCheck(MberCertPhoneVO mberCertPhoneVO) throws Exception {
+        if (StringUtils.isEmpty(userId))
+            return ResponseEntity.ok().body(new StatusResponse(HttpStatus.BAD_REQUEST, "로그인 후 이용해 주세요", LocalDateTime.now()));
+        mberCertPhoneVO.setMberId(userId);
+        return ResponseEntity.ok().body(certPhoneService.findByCertPhoneInfo(mberCertPhoneVO));
+    }
 
-		//로그인 권한정보 불러오기
-		LoginVO loginVO = EgovUserDetailsHelper.isAuthenticated()? (LoginVO)EgovUserDetailsHelper.getAuthenticatedUser():null;
-		String userId = loginVO == null ? "" : EgovStringUtil.isNullToString(loginVO.getId());
+    @RequestMapping(value = {"/cert/phone/insertCertPhone.do"})
+    public ResponseEntity<StatusResponse> insertCertPhone(MberCertPhoneVO mberCertPhoneVO,
+                                                          HttpServletRequest request, HttpSession session) throws Exception {
 
-		if(userId == null) {
-			if(StringUtils.isEmpty(userId)) return ResponseEntity.ok().body(new StatusResponse(HttpStatus.BAD_REQUEST, "로그인 후 이용해 주세요", LocalDateTime.now()));
-		}
-		mberCertPhoneVO.setMberId(userId);
-//		
-		return ResponseEntity.ok().body(certPhoneService.selectSysMsgLogCheck(mberCertPhoneVO));	
+        ModelAndView modelAndView = new ModelAndView();
+        modelAndView.setViewName("jsonView");
 
-	}
-	
-	
-	
-	
-	
-	
-	
-	
-	
-	
-	
-	
-}
(No newline at end of file)
+
+        System.out.println(" :: insertCertPhone :: ");
+
+        //로그인 권한정보 불러오기
+        LoginVO loginVO = EgovUserDetailsHelper.isAuthenticated() ? (LoginVO) EgovUserDetailsHelper.getAuthenticatedUser() : null;
+        String userId = loginVO == null ? "" : EgovStringUtil.isNullToString(loginVO.getId());
+
+        if (StringUtils.isEmpty(userId))
+            return ResponseEntity.ok().body(new StatusResponse(HttpStatus.BAD_REQUEST, "로그인 후 이용해 주세요", LocalDateTime.now()));
+        String phoneNo = normalizePhone(mberCertPhoneVO.getMbtlnum());
+        if (!isVerified(session, CertPhoneAuthConstants.PURPOSE_ADD_EXISTING, null)
+                || !isVerified(session, CertPhoneAuthConstants.PURPOSE_ADD_NEW, phoneNo)) {
+            return ResponseEntity.ok().body(new StatusResponse(HttpStatus.BAD_REQUEST,
+                    "휴대폰 인증을 다시 진행해 주세요.", LocalDateTime.now()));
+        }
+        mberCertPhoneVO.setMbtlnum(phoneNo);
+        mberCertPhoneVO.setFrstRegisterId(userId);
+        mberCertPhoneVO.setMberId(userId);
+
+        StatusResponse response = certPhoneService.insertCertPhone(
+                mberCertPhoneVO, normalizePhone(loginVO.getMoblphonNo()), loginVO.getDept(), IpUtil.getClientIP(request));
+        if (HttpStatus.OK.equals(response.getStatus())) {
+            clearPurpose(session, CertPhoneAuthConstants.PURPOSE_ADD_EXISTING);
+            clearPurpose(session, CertPhoneAuthConstants.PURPOSE_ADD_NEW);
+        }
+        return ResponseEntity.ok().body(response);
+    }
+
+    @RequestMapping(value = {"/cert/phone/updateCertPhone.do"})
+    public ResponseEntity<StatusResponse> updateCertPhone(MberCertPhoneVO mberCertPhoneVO) throws Exception {
+
+        ModelAndView modelAndView = new ModelAndView();
+        modelAndView.setViewName("jsonView");
+
+        //로그인 권한정보 불러오기
+        LoginVO loginVO = EgovUserDetailsHelper.isAuthenticated() ? (LoginVO) EgovUserDetailsHelper.getAuthenticatedUser() : null;
+        String userId = loginVO == null ? "" : EgovStringUtil.isNullToString(loginVO.getId());
+        if (StringUtils.isEmpty(userId))
+            return ResponseEntity.ok().body(new StatusResponse(HttpStatus.BAD_REQUEST, "로그인 후 이용해 주세요", LocalDateTime.now()));
+        mberCertPhoneVO.setLastUpdusrId(userId);
+        mberCertPhoneVO.setMberId(userId);
+        return ResponseEntity.ok().body(certPhoneService.updateMberCertPhone(mberCertPhoneVO));
+    }
+
+    @RequestMapping(value = {"/cert/phone/deleteCertPhone.do"})
+    public ResponseEntity<StatusResponse> deleteCertPhone(MberCertPhoneVO mberCertPhoneVO,
+                                                          HttpServletRequest request, HttpSession session) throws Exception {
+
+        ModelAndView modelAndView = new ModelAndView();
+        modelAndView.setViewName("jsonView");
+
+        //로그인 권한정보 불러오기
+        LoginVO loginVO = EgovUserDetailsHelper.isAuthenticated() ? (LoginVO) EgovUserDetailsHelper.getAuthenticatedUser() : null;
+        String userId = loginVO == null ? "" : EgovStringUtil.isNullToString(loginVO.getId());
+
+        if (StringUtils.isEmpty(userId))
+            return ResponseEntity.ok().body(new StatusResponse(HttpStatus.BAD_REQUEST, "로그인 후 이용해 주세요", LocalDateTime.now()));
+        String phoneNo = normalizePhone(mberCertPhoneVO.getMbtlnum());
+        if (!isVerified(session, CertPhoneAuthConstants.PURPOSE_DELETE_EXISTING, null)) {
+            return ResponseEntity.ok().body(new StatusResponse(HttpStatus.BAD_REQUEST,
+                    "휴대폰 인증을 다시 진행해 주세요.", LocalDateTime.now()));
+        }
+        mberCertPhoneVO.setMbtlnum(phoneNo);
+        mberCertPhoneVO.setLastUpdusrId(userId);
+        mberCertPhoneVO.setMberId(userId);
+
+
+        StatusResponse response = certPhoneService.deleteCertPhone(
+                mberCertPhoneVO, IpUtil.getClientIP(request));
+        if (HttpStatus.OK.equals(response.getStatus())) {
+            clearPurpose(session, CertPhoneAuthConstants.PURPOSE_DELETE_EXISTING);
+        }
+        return ResponseEntity.ok().body(response);
+    }
+
+    @RequestMapping(value = {"/cert/phone/sendSysMsgDataAjax.do"})
+    public ResponseEntity<StatusResponse> sendSysMsgDataAjax(MberCertPhoneVO mberCertPhoneVO,
+                                                             @RequestParam("purpose") String purpose,
+                                                             HttpServletRequest request, HttpSession session) throws Exception {
+
+        LoginVO loginVO = EgovUserDetailsHelper.isAuthenticated() ? (LoginVO) EgovUserDetailsHelper.getAuthenticatedUser() : null;
+        String userId = loginVO == null ? "" : EgovStringUtil.isNullToString(loginVO.getId());
+        if (StringUtils.isEmpty(userId)) {
+            return ResponseEntity.ok().body(new StatusResponse(HttpStatus.BAD_REQUEST, "로그인 후 이용해 주세요", LocalDateTime.now()));
+        }
+
+        String phoneNo = normalizePhone(mberCertPhoneVO.getMbtlnum());
+        if (!isValidPurpose(purpose) || !phoneNo.matches("^01[0-9]{8,9}$")) {
+            return ResponseEntity.ok().body(new StatusResponse(HttpStatus.BAD_REQUEST, "요청 정보를 확인해 주세요.", LocalDateTime.now()));
+        }
+        if (CertPhoneAuthConstants.PURPOSE_ADD_NEW.equals(purpose)) {
+            if (!isVerified(session, CertPhoneAuthConstants.PURPOSE_ADD_EXISTING, null)) {
+                return ResponseEntity.ok().body(new StatusResponse(HttpStatus.BAD_REQUEST, "기존 번호 본인확인을 먼저 완료해 주세요.", LocalDateTime.now()));
+            }
+            if (certPhoneService.isRegisteredAuthPhone(userId, phoneNo)) {
+                return ResponseEntity.ok().body(new StatusResponse(HttpStatus.CONFLICT,
+                        "이미 등록된 인증 휴대폰번호입니다.", LocalDateTime.now()));
+            }
+            if (!certPhoneService.isEligibleCertPhone(userId, phoneNo)) {
+                return ResponseEntity.ok().body(new StatusResponse(HttpStatus.BAD_REQUEST,
+                        "승인 완료된 발신번호가 아닙니다.", LocalDateTime.now()));
+            }
+        } else if (!certPhoneService.isRegisteredAuthPhone(userId, phoneNo)) {
+            return ResponseEntity.ok().body(new StatusResponse(HttpStatus.BAD_REQUEST, "등록된 인증 휴대폰번호가 아닙니다.", LocalDateTime.now()));
+        }
+
+        long now = System.currentTimeMillis();
+        Integer sendCount = (Integer) session.getAttribute(CertPhoneAuthConstants.SEND_COUNT_PREFIX + purpose);
+        Long lastSentAt = (Long) session.getAttribute(CertPhoneAuthConstants.LAST_SENT_AT_PREFIX + purpose);
+        int currentCount = sendCount == null ? 0 : sendCount;
+        if (currentCount >= CertPhoneAuthConstants.MAX_SEND_COUNT) {
+            return ResponseEntity.ok().body(new StatusResponse(HttpStatus.TOO_MANY_REQUESTS, "인증번호 발송 가능 횟수를 초과했습니다.", LocalDateTime.now()));
+        }
+        if (lastSentAt != null && now - lastSentAt < CertPhoneAuthConstants.RESEND_INTERVAL_MILLIS) {
+            return ResponseEntity.ok().body(new StatusResponse(HttpStatus.TOO_MANY_REQUESTS, "30초 후 다시 요청해 주세요.", LocalDateTime.now()));
+        }
+
+        MjonMsgVO mjonMsgVO = new MjonMsgVO();
+        mjonMsgVO.setCallTo(phoneNo);
+        MjonMsgReturnVO returnVO = mjonMsgDataService.sendSysMsgData(mjonMsgVO, request);
+
+        String resultSts = returnVO.getSendMsgCnt();
+        String resultBlockSts = returnVO.getSendMsgBlockCnt();
+
+        Map<String, String> returnMap = new HashMap<>();
+        HttpStatus httpStatus;
+        if (!resultSts.equals("0")) {
+            httpStatus = HttpStatus.OK;
+            returnMap.put("msg", "문자 전송이 완료되었습니다.");
+            returnMap.put("resultSts", resultSts);
+            returnMap.put("resultBlockSts", resultBlockSts);
+            returnMap.put("remainingSends", String.valueOf(CertPhoneAuthConstants.MAX_SEND_COUNT - currentCount - 1));
+            session.setAttribute(CertPhoneAuthConstants.PENDING_PHONE_PREFIX + purpose, phoneNo);
+            session.setAttribute(CertPhoneAuthConstants.SEND_COUNT_PREFIX + purpose, currentCount + 1);
+            session.setAttribute(CertPhoneAuthConstants.LAST_SENT_AT_PREFIX + purpose, now);
+            session.removeAttribute(CertPhoneAuthConstants.VERIFIED_PHONE_PREFIX + purpose);
+            session.removeAttribute(CertPhoneAuthConstants.VERIFIED_AT_PREFIX + purpose);
+        } else {
+            httpStatus = HttpStatus.BAD_REQUEST;
+            returnMap.put("msg", "문자 전송에 오류가 발생하였습니다.");
+            returnMap.put("resultSts", "0");
+            returnMap.put("resultBlockSts", "0");
+
+        }
+        return ResponseEntity.ok().body(new StatusResponse(httpStatus, returnMap, LocalDateTime.now()));
+    }
+
+    @RequestMapping(value = {"/cert/phone/selectSysMsgLogCheck.do"})
+    public ResponseEntity<StatusResponse> selectSysMsgLogCheck(MberCertPhoneVO mberCertPhoneVO,
+                                                               @RequestParam("purpose") String purpose, HttpSession session) throws Exception {
+
+        //로그인 권한정보 불러오기
+        LoginVO loginVO = EgovUserDetailsHelper.isAuthenticated() ? (LoginVO) EgovUserDetailsHelper.getAuthenticatedUser() : null;
+        String userId = loginVO == null ? "" : EgovStringUtil.isNullToString(loginVO.getId());
+
+        if (StringUtils.isEmpty(userId)) {
+            return ResponseEntity.ok().body(new StatusResponse(HttpStatus.BAD_REQUEST, "로그인 후 이용해 주세요", LocalDateTime.now()));
+        }
+        String phoneNo = normalizePhone(mberCertPhoneVO.getMbtlnum());
+        Object pendingPhone = session.getAttribute(CertPhoneAuthConstants.PENDING_PHONE_PREFIX + purpose);
+        if (!isValidPurpose(purpose) || !phoneNo.equals(pendingPhone)) {
+            return ResponseEntity.ok().body(new StatusResponse(HttpStatus.BAD_REQUEST, "인증번호를 다시 요청해 주세요.", LocalDateTime.now()));
+        }
+        mberCertPhoneVO.setMbtlnum(phoneNo);
+        mberCertPhoneVO.setMberId(userId);
+        StatusResponse response = certPhoneService.selectSysMsgLogCheck(mberCertPhoneVO);
+        if (HttpStatus.OK.equals(response.getStatus())) {
+            session.setAttribute(CertPhoneAuthConstants.VERIFIED_PHONE_PREFIX + purpose, phoneNo);
+            session.setAttribute(CertPhoneAuthConstants.VERIFIED_AT_PREFIX + purpose, System.currentTimeMillis());
+            if (CertPhoneAuthConstants.PURPOSE_ADD_EXISTING.equals(purpose)) {
+                clearPurpose(session, CertPhoneAuthConstants.PURPOSE_ADD_NEW);
+            }
+        }
+        return ResponseEntity.ok().body(response);
+
+    }
+
+    @RequestMapping(value = {"/cert/phone/resetAuthFlow.do"})
+    public ResponseEntity<StatusResponse> resetAuthFlow(HttpSession session) {
+        clearPurpose(session, CertPhoneAuthConstants.PURPOSE_ADD_EXISTING);
+        clearPurpose(session, CertPhoneAuthConstants.PURPOSE_ADD_NEW);
+        clearPurpose(session, CertPhoneAuthConstants.PURPOSE_DELETE_EXISTING);
+        return ResponseEntity.ok().body(new StatusResponse(HttpStatus.OK, "", LocalDateTime.now()));
+    }
+
+    private boolean isValidPurpose(String purpose) {
+        return CertPhoneAuthConstants.PURPOSE_ADD_EXISTING.equals(purpose)
+                || CertPhoneAuthConstants.PURPOSE_ADD_NEW.equals(purpose)
+                || CertPhoneAuthConstants.PURPOSE_DELETE_EXISTING.equals(purpose);
+    }
+
+    private boolean isVerified(HttpSession session, String purpose, String expectedPhone) {
+        Object verifiedPhone = session.getAttribute(CertPhoneAuthConstants.VERIFIED_PHONE_PREFIX + purpose);
+        Object verifiedAt = session.getAttribute(CertPhoneAuthConstants.VERIFIED_AT_PREFIX + purpose);
+        if (!(verifiedPhone instanceof String) || !(verifiedAt instanceof Long)) {
+            return false;
+        }
+        if (System.currentTimeMillis() - (Long) verifiedAt > CertPhoneAuthConstants.VERIFIED_TIMEOUT_MILLIS) {
+            clearPurpose(session, purpose);
+            return false;
+        }
+        return expectedPhone == null || expectedPhone.equals(verifiedPhone);
+    }
+
+    private void clearPurpose(HttpSession session, String purpose) {
+        session.removeAttribute(CertPhoneAuthConstants.PENDING_PHONE_PREFIX + purpose);
+        session.removeAttribute(CertPhoneAuthConstants.SEND_COUNT_PREFIX + purpose);
+        session.removeAttribute(CertPhoneAuthConstants.LAST_SENT_AT_PREFIX + purpose);
+        session.removeAttribute(CertPhoneAuthConstants.VERIFIED_PHONE_PREFIX + purpose);
+        session.removeAttribute(CertPhoneAuthConstants.VERIFIED_AT_PREFIX + purpose);
+    }
+
+    private String normalizePhone(String phoneNo) {
+        return phoneNo == null ? "" : phoneNo.replaceAll("[^0-9]", "");
+    }
+}
src/main/java/itn/let/cert/phone/web/CertPhoneWebController.java
--- src/main/java/itn/let/cert/phone/web/CertPhoneWebController.java
+++ src/main/java/itn/let/cert/phone/web/CertPhoneWebController.java
@@ -1,36 +1,46 @@
 package itn.let.cert.phone.web;
 
 import java.time.LocalDateTime;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.List;
 
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
 
 import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Controller;
+import org.springframework.ui.ModelMap;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.servlet.ModelAndView;
-
-import egovframework.rte.fdl.security.userdetails.util.EgovUserDetailsHelper;
-import itn.com.cmm.LoginVO;
-import itn.com.utl.fcc.service.EgovStringUtil;
+import itn.com.cmm.util.IpUtil;
 import itn.let.cert.phone.service.CertPhoneService;
 import itn.let.cert.phone.service.MberCertPhoneVO;
+import itn.let.cert.phone.support.PhoneResetSessionConstants;
 import itn.let.mail.service.StatusResponse;
-import itn.let.mjo.msg.service.MjonMsgVO;
-import itn.let.mjo.msgdata.service.MjonMsgDataService;
-import itn.let.mjo.msgdata.service.MjonMsgReturnVO;
+import itn.let.mjo.pay.service.KmcVO;
 import itn.let.uat.uia.service.AuthCertVO;
-import itn.let.uat.uia.web.SendLogVO;
+import itn.let.uat.uia.support.SecondAuthSessionConstants;
+import itn.let.uat.uia.web.KmcCertChecker;
+import itn.let.uss.umt.service.EgovUserManageService;
 
 @Controller
 public class CertPhoneWebController {
+	private static final Logger LOGGER = LoggerFactory.getLogger(CertPhoneWebController.class);
 	
 	@Resource(name = "CertPhoneService")
 	private CertPhoneService certPhoneService;
+
+	@Resource(name = "KmcCertChecker")
+	private KmcCertChecker kmcCertChecker;
+
+	@Resource(name = "userManageService")
+	private EgovUserManageService userManageService;
 	
 	@RequestMapping(value = {"/web/cert/log/insertCertLog.do"})
 	public ResponseEntity<StatusResponse> insertCertLog(HttpServletRequest request) throws Exception {
@@ -41,4 +51,106 @@
 	public String kmcErrorPage() throws Exception {
 		return "web/cop/kmc/certResultError";
 	}
-}
(No newline at end of file)
+
+	@RequestMapping(value = {"/web/cert/phoneList.do"})
+	public ResponseEntity<List<MberCertPhoneVO>> selectPhoneList(
+			@RequestParam("userId") String userId,
+			HttpSession session){
+		try {
+			if (!userId.equals(session.getAttribute(PhoneResetSessionConstants.PENDING_USER_ID))) {
+				return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
+			}
+			List<MberCertPhoneVO> phoneList = certPhoneService.selectMberCertPhoneList(userId);
+			return ResponseEntity.ok(phoneList);
+		} catch (Exception e) {
+			return ResponseEntity.notFound().build();
+		}
+	}
+
+	@RequestMapping(value = "/web/cert/resetPhoneRequest.do", method = RequestMethod.POST)
+	public ResponseEntity<?> resetPhoneRequest(HttpServletRequest request, HttpSession session) {
+		String userId = getPendingUserId(session);
+		if (StringUtils.isBlank(userId)) {
+			return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
+					.body(new StatusResponse(HttpStatus.UNAUTHORIZED,
+							"로그인 정보를 다시 확인해주세요.", LocalDateTime.now()));
+		}
+
+		MberCertPhoneVO mberCertPhoneVO = new MberCertPhoneVO();
+		mberCertPhoneVO.setTrUrl("/web/cert/resetPhone.do");
+		mberCertPhoneVO.setPlusInfo(userId);
+
+		AuthCertVO certVO = kmcCertChecker.authCertCheck(mberCertPhoneVO, request);
+		if (StringUtils.isBlank(certVO.getTr_cert()) || StringUtils.isBlank(certVO.getTr_url())) {
+			return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
+					.body(new StatusResponse(HttpStatus.INTERNAL_SERVER_ERROR,
+							"KMC 인증 설정을 확인해주세요.", LocalDateTime.now()));
+		}
+
+		return ResponseEntity.ok(certVO);
+	}
+
+	@RequestMapping(value = "/web/cert/resetPhone.do", method = RequestMethod.POST)
+	public String resetPhone(
+			HttpServletRequest request,
+			HttpServletResponse response,
+			HttpSession session,
+			ModelMap model) throws Exception {
+		boolean success = false;
+		String message = "본인인증 처리에 실패했습니다.";
+
+		KmcVO kmcVO = kmcCertChecker.authCertResult(request, response, model);
+		if (response.isCommitted()) {
+			return null;
+		}
+
+		String userId = getPendingUserId(session);
+		if (StringUtils.isBlank(userId)) {
+			message = "인증 유효시간이 만료되었습니다. 다시 로그인해주세요.";
+		} else if (!"Y".equals(kmcVO.getResult())) {
+			message = "KMC 본인인증이 완료되지 않았습니다.";
+		} else if (!userId.equals(kmcVO.getPlusInfo())) {
+			message = "본인인증 요청 정보가 일치하지 않습니다.";
+		} else if (!kmcVO.getPhoneNo().matches("^01[0-9]{8,9}$")) {
+			message = "인증된 휴대폰번호가 올바르지 않습니다.";
+		} else {
+			String memberDi = userManageService.selectUserMblDnById(userId);
+			if (StringUtils.isBlank(memberDi) || !memberDi.equals(kmcVO.getDI())) {
+				message = "가입 당시 본인 명의와 일치하지 않습니다.";
+			} else {
+				certPhoneService.resetMemberPhone(userId, kmcVO.getPhoneNo(), IpUtil.getClientIP(request));
+				session.removeAttribute(SecondAuthSessionConstants.SEND_COUNT);
+				session.removeAttribute(SecondAuthSessionConstants.LAST_SENT_AT);
+				try {
+					kmcCertChecker.insertCertLog(kmcVO, "KMC_2차 로그인 휴대폰번호 변경");
+				} catch (Exception e) {
+					LOGGER.warn("KMC phone reset certification log failed. userId={}", userId, e);
+				}
+				success = true;
+				message = "휴대폰번호 변경 및 본인인증이 완료되었습니다.";
+			}
+		}
+
+		model.addAttribute("success", success ? "Y" : "N");
+		model.addAttribute("message", message);
+		model.addAttribute("phoneNo", success ? kmcVO.getPhoneNo() : "");
+		return "web/login/resetPhoneKmcResult";
+	}
+
+	private String getPendingUserId(HttpSession session) {
+		Object pendingUserId = session.getAttribute(PhoneResetSessionConstants.PENDING_USER_ID);
+		Object pendingAt = session.getAttribute(PhoneResetSessionConstants.PENDING_AT);
+
+		if (!(pendingUserId instanceof String) || !(pendingAt instanceof Long)) {
+			return null;
+		}
+
+		if (System.currentTimeMillis() - (Long) pendingAt > PhoneResetSessionConstants.TIMEOUT_MILLIS) {
+			session.removeAttribute(PhoneResetSessionConstants.PENDING_USER_ID);
+			session.removeAttribute(PhoneResetSessionConstants.PENDING_AT);
+			return null;
+		}
+
+		return (String) pendingUserId;
+	}
+}
src/main/java/itn/let/mjo/apikey/service/ApiKeyMngService.java
--- src/main/java/itn/let/mjo/apikey/service/ApiKeyMngService.java
+++ src/main/java/itn/let/mjo/apikey/service/ApiKeyMngService.java
@@ -1,6 +1,10 @@
 package itn.let.mjo.apikey.service;
 
+import itn.let.mjo.conf.service.MjonConfVO;
+import itn.let.mjo.msgsent.service.MjonMsgSentVO;
+
 import java.util.List;
+import java.util.Map;
 
 /**
  * api key 관리
@@ -39,5 +43,11 @@
 	public List<ApiKeyVO> selectApiKeyApplyStatus(ApiKeyVO apiKeyVO) throws Exception;
 
 	public int selectMberApiKeyChk(ApiKeyVO apiKeyVO) throws Exception;
+
+	public Map<String, Object> selectApiMsgSentList(MjonMsgSentVO mjonMsgSentVO) throws Exception;
+
+	public int selectCountApiMsgSentList(MjonMsgSentVO mjonMsgSentVO) throws Exception;
+
+	public int updateApproveApiMsg(MjonMsgSentVO mjonMsgSentVO) throws Exception;
 	
 }
(No newline at end of file)
src/main/java/itn/let/mjo/apikey/service/impl/ApiKeyMngDAO.java
--- src/main/java/itn/let/mjo/apikey/service/impl/ApiKeyMngDAO.java
+++ src/main/java/itn/let/mjo/apikey/service/impl/ApiKeyMngDAO.java
@@ -2,6 +2,7 @@
 
 import java.util.List;
 
+import itn.let.mjo.msgsent.service.MjonMsgSentVO;
 import org.springframework.stereotype.Repository;
 
 import itn.com.cmm.service.impl.EgovComAbstractDAO;
@@ -59,5 +60,19 @@
 	public void deleteApiKeyReal(ApiKeyVO apiKeyVO) throws Exception{
 		update("ApiKeyMngDAO.deleteApiKeyReal", apiKeyVO);
 	}
-	
+
+	//API 발송내역 리스트
+	public List<MjonMsgSentVO> selectApiMsgSentList(MjonMsgSentVO mjonMsgSentVO) throws Exception{
+		return (List<MjonMsgSentVO>) list("ApiKeyMngDAO.selectApiMsgSentList",mjonMsgSentVO);
+	}
+
+	public int selectCountApiMsgSentList(MjonMsgSentVO mjonMsgSentVO) {
+		return (Integer)select("ApiKeyMngDAO.selectCountApiMsgSentList", mjonMsgSentVO);
+	}
+
+	public int updateApproveApiMsg(MjonMsgSentVO mjonMsgSentVO) throws Exception{
+		return update("ApiKeyMngDAO.updateApproveApiMsg", mjonMsgSentVO);
+	}
+
+
 }
(No newline at end of file)
src/main/java/itn/let/mjo/apikey/service/impl/ApiKeyMngServiceImpl.java
--- src/main/java/itn/let/mjo/apikey/service/impl/ApiKeyMngServiceImpl.java
+++ src/main/java/itn/let/mjo/apikey/service/impl/ApiKeyMngServiceImpl.java
@@ -1,9 +1,14 @@
 package itn.let.mjo.apikey.service.impl;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import javax.annotation.Resource;
 
+import itn.let.fax.addr.service.FaxAddrVO;
+import itn.let.mjo.msgsent.service.MjonMsgSentVO;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.stereotype.Service;
 
 import egovframework.rte.fdl.cmmn.EgovAbstractServiceImpl;
@@ -78,5 +83,32 @@
 	public List<ApiKeyVO> selectApiKeyApplyStatus(ApiKeyVO apiKeyVO) throws Exception {
 		return apiKeyMngDAO.selectApiKeyApplyStatus(apiKeyVO);
 	}
-	
+
+	public Map<String, Object> selectApiMsgSentList(MjonMsgSentVO mjonMsgSentVO) throws Exception{
+
+		Map<String, Object> resultMap = new HashMap<String, Object>();
+		// 목록
+		List<MjonMsgSentVO> resultList = apiKeyMngDAO.selectApiMsgSentList(mjonMsgSentVO);
+
+		resultList.stream().forEach(t->{
+			if("6".equals(t.getMsgType())
+					&& StringUtils.isEmpty(t.getSmsTxt())
+					&& !"0".equals(t.getFileCnt())
+			) {
+				t.setSmsTxt("이미지");
+			}
+		});
+
+		resultMap.put("resultList", resultList);
+		return resultMap;
+	}
+
+	public int selectCountApiMsgSentList(MjonMsgSentVO mjonMsgSentVO) throws Exception{
+		return  apiKeyMngDAO.selectCountApiMsgSentList(mjonMsgSentVO);
+	}
+
+	public int updateApproveApiMsg(MjonMsgSentVO mjonMsgSentVO) throws Exception{
+		return  apiKeyMngDAO.updateApproveApiMsg(mjonMsgSentVO);
+	}
+
 }
(No newline at end of file)
 
src/main/java/itn/let/mjo/block/service/GeoIpBlockLogVO.java (added)
+++ src/main/java/itn/let/mjo/block/service/GeoIpBlockLogVO.java
@@ -0,0 +1,38 @@
+package itn.let.mjo.block.service;
+
+import itn.com.cmm.ComDefaultVO;
+
+import java.time.LocalDateTime;
+
+public class GeoIpBlockLogVO extends ComDefaultVO {
+
+    private long blockLogId;
+    private LocalDateTime blockDt;
+    private String accessIp;
+    private String requestUri;
+    private String countryCode;
+    private String countryName;
+    private String blockYn;
+
+    public Long getBlockLogId() { return blockLogId; }
+    public void setBlockLogId(Long blockLogId) { this.blockLogId = blockLogId; }
+
+    public LocalDateTime getBlockDt() { return blockDt; }
+    public void setBlockDt(LocalDateTime blockDt) { this.blockDt = blockDt; }
+
+    public String getAccessIp() { return accessIp; }
+    public void setAccessIp(String accessIp) { this.accessIp = accessIp; }
+
+    public String getRequestUri() { return requestUri; }
+    public void setRequestUri(String requestUri) { this.requestUri = requestUri; }
+
+    public String getCountryCode() { return countryCode; }
+    public void setCountryCode(String countryCode) { this.countryCode = countryCode; }
+
+    public String getCountryName() { return countryName; }
+    public void setCountryName(String countryName) { this.countryName = countryName; }
+
+    public String getBlockYn() { return blockYn; }
+    public void setBlockYn(String blockYn) { this.blockYn = blockYn; }
+
+}
src/main/java/itn/let/mjo/block/service/MjonBlockService.java
--- src/main/java/itn/let/mjo/block/service/MjonBlockService.java
+++ src/main/java/itn/let/mjo/block/service/MjonBlockService.java
@@ -29,5 +29,11 @@
 
 	// 주소록 삭제
 	void deleteBlockAddr(MjonBlockVO blockVO) throws Exception;
+
+	// 해외접근 IP 차단 로그
+	boolean checkAndLogGeoIpBlock(String clientIp, String requestUri) throws Exception;
+
+	String selectGeoIpBlockYn() throws Exception;
+
 	
 }
src/main/java/itn/let/mjo/block/service/impl/MjonBlockDAO.java
--- src/main/java/itn/let/mjo/block/service/impl/MjonBlockDAO.java
+++ src/main/java/itn/let/mjo/block/service/impl/MjonBlockDAO.java
@@ -2,6 +2,7 @@
 
 import java.util.List;
 
+import itn.let.mjo.block.service.GeoIpBlockLogVO;
 import org.springframework.stereotype.Repository;
 
 import egovframework.rte.psl.dataaccess.EgovAbstractDAO;
@@ -56,7 +57,16 @@
 	// 주소록 삭제
 	public void deleteBlockAddr(MjonBlockVO blockVO) throws Exception {
 		delete("MjonBlockDAO.deleteBlockAddr", blockVO);
-	}	
+	}
+
+	// 해외접근 IP 차단 로그
+	public void insertGeoIpBlockLog(GeoIpBlockLogVO geoIpBlockLogVO) throws Exception {
+		insert("MjonBlockDAO.insertGeoIpBlockLog", geoIpBlockLogVO);
+	}
+
+	public String selectGeoIpBlockYn() throws Exception{
+		return (String) select("MjonBlockDAO.selectGeoIpBlockYn");
+	}
 	
 	
 }
src/main/java/itn/let/mjo/block/service/impl/MjonBlockServiceImpl.java
--- src/main/java/itn/let/mjo/block/service/impl/MjonBlockServiceImpl.java
+++ src/main/java/itn/let/mjo/block/service/impl/MjonBlockServiceImpl.java
@@ -1,9 +1,23 @@
 package itn.let.mjo.block.service.impl;
 
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.net.InetAddress;
 import java.util.List;
 
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
 import javax.annotation.Resource;
 
+import com.maxmind.db.CHMCache;
+import com.maxmind.geoip2.DatabaseReader;
+import com.maxmind.geoip2.model.CountryResponse;
+import itn.let.mjo.block.service.GeoIpBlockLogVO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.io.ClassPathResource;
 import org.springframework.stereotype.Service;
 
 import egovframework.rte.fdl.cmmn.EgovAbstractServiceImpl;
@@ -16,6 +30,8 @@
 
 @Service("mjonBlockService")
 public class MjonBlockServiceImpl extends EgovAbstractServiceImpl implements MjonBlockService {
+
+	private static final Logger LOGGER = LoggerFactory.getLogger(MjonBlockServiceImpl.class);
 
 	@Resource(name = "egovMjonBlockIdGnrService")
 	private EgovIdGnrService idgenBlockId;
@@ -32,7 +48,164 @@
 	/** 메모 idgen */
 	@Resource(name="egovGnrlUserSelectMemoIdGnrService")
 	private EgovIdGnrService memoidgenService;
-	
+
+	/* 해외접근 IP */
+	@Value("#{globalSettings['Globals.geoip.database.path']}")
+	private String globalGeoIpDbPath;
+
+	private DatabaseReader databaseReader;
+
+	private volatile boolean isGeoIpBlockEnabled = false;
+	private volatile long lastCheckTime = 0L;
+
+	private static final long CACHE_TTL_MS = 30 * 1000L; // 60 * 1000L;, 2 * 60 * 1000L;
+
+
+	@PostConstruct
+	public void initGeoIpDatabase() {
+		reloadGeoIpBlockStatusFromDb();
+		
+		if (this.databaseReader != null) {
+			return;
+		}
+
+		try {
+			if (globalGeoIpDbPath == null || globalGeoIpDbPath.trim().isEmpty()) {
+				globalGeoIpDbPath = "egovframework/geoip/GeoLite2-Country.mmdb";
+			}
+
+			ClassPathResource resource = new ClassPathResource(globalGeoIpDbPath);
+
+			if (resource.exists()) {
+				File tempFile = File.createTempFile("GeoLite2-Country", ".mmdb");
+				tempFile.deleteOnExit();
+
+				try (InputStream is = resource.getInputStream();
+					 FileOutputStream fos = new FileOutputStream(tempFile)) {
+
+					byte[] buffer = new byte[8192];
+					int bytesRead;
+					while ((bytesRead = is.read(buffer)) != -1) {
+						fos.write(buffer, 0, bytesRead);
+					}
+					fos.flush();
+				}
+
+				this.databaseReader = new DatabaseReader.Builder(tempFile)
+						.withCache(new CHMCache())
+						.build();
+			} else {
+				LOGGER.error("file error classpath at: {}", globalGeoIpDbPath);
+			}
+
+		} catch (Exception e) {
+			LOGGER.error("fail geoIp databaseReader", e);
+		}
+	}
+
+	@Override
+	public boolean checkAndLogGeoIpBlock(String clientIp, String requestUri) throws Exception {
+		LOGGER.warn(
+				"[GEOIP CHECK] enabled={}, clientIp={}, uri={}",
+				isGeoIpBlockEnabled(),
+				clientIp,
+				requestUri
+		);
+
+		if (!isGeoIpBlockEnabled()) {
+			return false;
+		}
+
+		if (databaseReader == null) {
+			initGeoIpDatabase();
+		}
+
+		if (databaseReader == null) {
+			return false;
+		}
+
+		try {
+			InetAddress ipAddress = InetAddress.getByName(clientIp);
+			CountryResponse response = databaseReader.country(ipAddress);
+			String countryCode = response.getCountry().getIsoCode();
+			String countryName = response.getCountry().getName();
+
+			LOGGER.warn(
+					"[GEOIP RESULT] ip={}, countryCode={}, countryName={}",
+					clientIp,
+					countryCode,
+					countryName
+			);
+
+
+			// 한국(KR)이 아닌 해외 IP 차단
+			if (!"KR".equalsIgnoreCase(countryCode)) {
+				GeoIpBlockLogVO logVo = new GeoIpBlockLogVO();
+				logVo.setAccessIp(clientIp);
+				logVo.setRequestUri(requestUri);
+				logVo.setCountryCode(countryCode);
+				logVo.setCountryName(countryName);
+				logVo.setBlockYn("Y");
+
+				try {
+					blockDAO.insertGeoIpBlockLog(logVo);
+				} catch (Exception e) {
+					LOGGER.error("fail insert DB", e);
+				}
+				return true;
+			}
+
+			LOGGER.warn(
+					"[GEOIP ALLOW] ip={}, countryCode={}",
+					clientIp,
+					countryCode
+			);
+		} catch (Exception e) {
+			LOGGER.debug("geoIp check failed IP {}: {}", clientIp, e.getMessage());
+		}
+
+		return false;
+	}
+
+	public void reloadGeoIpBlockStatusFromDb() {
+		try {
+			String useYn = blockDAO.selectGeoIpBlockYn();
+			this.isGeoIpBlockEnabled = "Y".equalsIgnoreCase(useYn);
+			this.lastCheckTime = System.currentTimeMillis();
+		} catch (Exception e) {
+			LOGGER.error("load fail DB", e);
+		}
+	}
+
+	public boolean isGeoIpBlockEnabled() {
+		long now = System.currentTimeMillis();
+
+		if (now - lastCheckTime > CACHE_TTL_MS) {
+			synchronized (this) {
+				if (now - lastCheckTime > CACHE_TTL_MS) {
+					reloadGeoIpBlockStatusFromDb();
+				}
+			}
+		}
+		return this.isGeoIpBlockEnabled;
+	}
+
+	public void setGeoIpBlockEnabled(boolean enabled) {
+		this.isGeoIpBlockEnabled = enabled;
+		this.lastCheckTime = System.currentTimeMillis();
+	}
+
+	@PreDestroy
+	public void closeGeoIpDatabase() {
+		if (this.databaseReader != null) {
+			try {
+				this.databaseReader.close();
+			} catch (Exception e) {
+				LOGGER.error("Error close DB", e);
+			}
+		}
+	}
+
 	public void insertBlock(MjonBlockVO blockVO) throws Exception {
 		blockVO.setBlockId(idgenBlockId.getNextStringId());
 		blockDAO.insertBlock(blockVO);
@@ -137,7 +310,12 @@
 	// 주소록 삭제
 	public void deleteBlockAddr(MjonBlockVO blockVO) throws Exception {
 		blockDAO.deleteBlockAddr(blockVO);
-	}	
-	
-	
+	}
+
+	@Override
+	public String selectGeoIpBlockYn() throws Exception {
+		return blockDAO.selectGeoIpBlockYn();
+	}
+
+
 }
 
src/main/java/itn/let/mjo/msg/service/MjEmployeeCertVO.java (added)
+++ src/main/java/itn/let/mjo/msg/service/MjEmployeeCertVO.java
@@ -0,0 +1,27 @@
+package itn.let.mjo.msg.service;
+
+import java.io.Serializable;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class MjEmployeeCertVO implements Serializable {
+
+	private static final long serialVersionUID = 1L;
+
+	private String mberId;
+	private Integer certCnt;
+	private String lastHstrySeq;
+	private String authHstrySeq;
+	private String employeeCertHstrySeq;
+	private Integer applyCnt;
+	private String authYn;
+	private String rejectCode;
+	private String rejectReason;
+	private String atchFileId;
+	private String agreeYn;
+	private String frstRegisterId;
+	private String lastUpdusrId;
+}
src/main/java/itn/let/mjo/msg/service/MjPhoneMemberVO.java
--- src/main/java/itn/let/mjo/msg/service/MjPhoneMemberVO.java
+++ src/main/java/itn/let/mjo/msg/service/MjPhoneMemberVO.java
@@ -1,7 +1,11 @@
 package itn.let.mjo.msg.service;
 
 import itn.com.cmm.ComDefaultVO;
+import lombok.Getter;
+import lombok.Setter;
 
+@Setter
+@Getter
 public class MjPhoneMemberVO extends ComDefaultVO{
 	
 	private static final long serialVersionUID = 1L;
@@ -63,470 +67,14 @@
 	
 	private String CI;
 	private String birthDay;
-	
-	public String getAuthYnOrigin() {
-		return authYnOrigin;
-	}
 
-	public void setAuthYnOrigin(String authYnOrigin) {
-		this.authYnOrigin = authYnOrigin;
-	}
-
-	public String getMemo() {
-		return memo;
-	}
-
-	public void setMemo(String memo) {
-		this.memo = memo;
-	}
-
-	public String getManagerNm() {
-		return managerNm;
-	}
-
-	public void setManagerNm(String managerNm) {
-		this.managerNm = managerNm;
-	}
-
-	private int sumTotCnt; //통계 총합 변수
+    private int sumTotCnt; //통계 총합 변수
 	private String mbtlNum;
-	
-	public String getMbtlNum() {
-		return mbtlNum;
-	}
+	private String phmLineType;
 
-	public void setMbtlNum(String mbtlNum) {
-		this.mbtlNum = mbtlNum;
-	}
-
-	public String getPhmId() {
-		return phmId;
-	}
-
-	public void setPhmId(String phmId) {
-		this.phmId = phmId;
-	}
-
-	public String getUserId() {
-		return userId;
-	}
-
-	public void setUserId(String userId) {
-		this.userId = userId;
-	}
-
-	public String getCertId() {
-		return certId;
-	}
-
-	public void setCertId(String certId) {
-		this.certId = certId;
-	}
-
-	public String getPhmType() {
-		return phmType;
-	}
-
-	public void setPhmType(String phmType) {
-		this.phmType = phmType;
-	}
-
-	public String getPhmAuthType() {
-		return phmAuthType;
-	}
-
-	public void setPhmAuthType(String phmAuthType) {
-		this.phmAuthType = phmAuthType;
-	}
-
-	public String getFrstRegistPnttm() {
-		return frstRegistPnttm;
-	}
-
-	public void setFrstRegistPnttm(String frstRegistPnttm) {
-		this.frstRegistPnttm = frstRegistPnttm;
-	}
-
-	public String getFrstRegisterId() {
-		return frstRegisterId;
-	}
-
-	public void setFrstRegisterId(String frstRegisterId) {
-		this.frstRegisterId = frstRegisterId;
-	}
-
-	public String getLastUpdtPnttm() {
-		return lastUpdtPnttm;
-	}
-
-	public void setLastUpdtPnttm(String lastUpdtPnttm) {
-		this.lastUpdtPnttm = lastUpdtPnttm;
-	}
-
-	public String getLastUpdusrId() {
-		return lastUpdusrId;
-	}
-
-	public void setLastUpdusrId(String lastUpdusrId) {
-		this.lastUpdusrId = lastUpdusrId;
-	}
-
-	public String getDelFlag() {
-		return delFlag;
-	}
-
-	public void setDelFlag(String delFlag) {
-		this.delFlag = delFlag;
-	}
-
-	public static long getSerialversionuid() {
+    public static long getSerialversionuid() {
 		return serialVersionUID;
 	}
 
-	public String getPhoneNumber() {
-		return phoneNumber;
-	}
-
-	public void setPhoneNumber(String phoneNumber) {
-		this.phoneNumber = phoneNumber;
-	}
-
-	public String getPhmTypeTxt() {
-		return phmTypeTxt;
-	}
-
-	public void setPhmTypeTxt(String phmTypeTxt) {
-		this.phmTypeTxt = phmTypeTxt;
-	}
-
-	public String getPhmAuthTypeTxt() {
-		return phmAuthTypeTxt;
-	}
-
-	public void setPhmAuthTypeTxt(String phmAuthTypeTxt) {
-		this.phmAuthTypeTxt = phmAuthTypeTxt;
-	}
-
-	public String getDelFlagTxt() {
-		return delFlagTxt;
-	}
-
-	public void setDelFlagTxt(String delFlagTxt) {
-		this.delFlagTxt = delFlagTxt;
-	}
-
-	public String getAuthYn() {
-		return authYn;
-	}
-
-	public void setAuthYn(String authYn) {
-		this.authYn = authYn;
-	}
-
-	public String getAuthYnTxt() {
-		return authYnTxt;
-	}
-
-	public void setAuthYnTxt(String authYnTxt) {
-		this.authYnTxt = authYnTxt;
-	}
-
-	public String getUserName() {
-		return userName;
-	}
-
-	public void setUserName(String userName) {
-		this.userName = userName;
-	}
-
-	public String getSearchCondition2() {
-		return searchCondition2;
-	}
-
-	public void setSearchCondition2(String searchCondition2) {
-		this.searchCondition2 = searchCondition2;
-	}
-
-	public String getSearchCondition3() {
-		return searchCondition3;
-	}
-
-	public void setSearchCondition3(String searchCondition3) {
-		this.searchCondition3 = searchCondition3;
-	}
-
-	public String getRank() {
-		return rank;
-	}
-
-	public void setRank(String rank) {
-		this.rank = rank;
-	}
-
-	public String getMsgGroupCnt() {
-		return msgGroupCnt;
-	}
-
-	public void setMsgGroupCnt(String msgGroupCnt) {
-		this.msgGroupCnt = msgGroupCnt;
-	}
-
-	public String getCallFrom() {
-		return callFrom;
-	}
-
-	public void setCallFrom(String callFrom) {
-		this.callFrom = callFrom;
-	}
-
-	public String getMsgType() {
-		return msgType;
-	}
-
-	public void setMsgType(String msgType) {
-		this.msgType = msgType;
-	}
-
-	public String getMsgTypeTxt() {
-		return msgTypeTxt;
-	}
-
-	public void setMsgTypeTxt(String msgTypeTxt) {
-		this.msgTypeTxt = msgTypeTxt;
-	}
-
-	public String getAgentCode() {
-		return agentCode;
-	}
-
-	public void setAgentCode(String agentCode) {
-		this.agentCode = agentCode;
-	}
-
-	public String getAgentCodeTxt() {
-		return agentCodeTxt;
-	}
-
-	public void setAgentCodeTxt(String agentCodeTxt) {
-		this.agentCodeTxt = agentCodeTxt;
-	}
-
-	public String getNtceBgnde() {
-		return ntceBgnde;
-	}
-
-	public void setNtceBgnde(String ntceBgnde) {
-		this.ntceBgnde = ntceBgnde;
-	}
-
-	public String getNtceEndde() {
-		return ntceEndde;
-	}
-
-	public void setNtceEndde(String ntceEndde) {
-		this.ntceEndde = ntceEndde;
-	}
-
-	public String getSbscrbDe() {
-		return sbscrbDe;
-	}
-
-	public void setSbscrbDe(String sbscrbDe) {
-		this.sbscrbDe = sbscrbDe;
-	}
-
-	public String getEachPrice() {
-		return eachPrice;
-	}
-
-	public void setEachPrice(String eachPrice) {
-		this.eachPrice = eachPrice;
-	}
-
-	public String getTotPrice() {
-		return totPrice;
-	}
-
-	public void setTotPrice(String totPrice) {
-		this.totPrice = totPrice;
-	}
-
-	public String getPhmDenyType() {
-		return phmDenyType;
-	}
-
-	public void setPhmDenyType(String phmDenyType) {
-		this.phmDenyType = phmDenyType;
-	}
-
-	public String getPhmDenyTypeTxt() {
-		return phmDenyTypeTxt;
-	}
-
-	public void setPhmDenyTypeTxt(String phmDenyTypeTxt) {
-		this.phmDenyTypeTxt = phmDenyTypeTxt;
-	}
-
-	public String getSearchCondition4() {
-		return searchCondition4;
-	}
-
-	public void setSearchCondition4(String searchCondition4) {
-		this.searchCondition4 = searchCondition4;
-	}
-
-	
-	public String getSearchCondition5() {
-		return searchCondition5;
-	}
-
-	public void setSearchCondition5(String searchCondition5) {
-		this.searchCondition5 = searchCondition5;
-	}
-
-	public String getSearchCondition6() {
-		return searchCondition6;
-	}
-
-	public void setSearchCondition6(String searchCondition6) {
-		this.searchCondition6 = searchCondition6;
-	}
-
-	public String getPhmValue() {
-		return phmValue;
-	}
-
-	public void setPhmValue(String phmValue) {
-		this.phmValue = phmValue;
-	}
-
-	public String getSendPhoneNumber() {
-		return sendPhoneNumber;
-	}
-
-	public void setSendPhoneNumber(String sendPhoneNumber) {
-		this.sendPhoneNumber = sendPhoneNumber;
-	}
-
-	public String getBaseYn() {
-		return baseYn;
-	}
-
-	public void setBaseYn(String baseYn) {
-		this.baseYn = baseYn;
-	}
-
-	public String getRejectReason() {
-		return rejectReason;
-	}
-
-	public void setRejectReason(String rejectReason) {
-		this.rejectReason = rejectReason;
-	}
-
-	public String getNameType() {
-		return nameType;
-	}
-
-	public void setNameType(String nameType) {
-		this.nameType = nameType;
-	}
-
-	public String getDept() {
-		return dept;
-	}
-
-	public void setDept(String dept) {
-		this.dept = dept;
-	}
-
-	public String getUserMoney() {
-		return userMoney;
-	}
-
-	public void setUserMoney(String userMoney) {
-		this.userMoney = userMoney;
-	}
-
-	public String getAtchFileId() {
-		return atchFileId;
-	}
-
-	public void setAtchFileId(String atchFileId) {
-		this.atchFileId = atchFileId;
-	}
-
-	public String getFileSn() {
-		return fileSn;
-	}
-
-	public void setFileSn(String fileSn) {
-		this.fileSn = fileSn;
-	}
-
-	public String getOrignlFileNm() {
-		return orignlFileNm;
-	}
-
-	public void setOrignlFileNm(String orignlFileNm) {
-		this.orignlFileNm = orignlFileNm;
-	}
-
-	public String getFileExtsn() {
-		return fileExtsn;
-	}
-
-	public void setFileExtsn(String fileExtsn) {
-		this.fileExtsn = fileExtsn;
-	}
-
-	public int getSumTotCnt() {
-		return sumTotCnt;
-	}
-
-	public void setSumTotCnt(int sumTotCnt) {
-		this.sumTotCnt = sumTotCnt;
-	}
-
-	public String getAdmNm() {
-		return admNm;
-	}
-
-	public void setAdmNm(String admNm) {
-		this.admNm = admNm;
-	}
-
-	public String getOwnerName() {
-		return ownerName;
-	}
-
-	public void setOwnerName(String ownerName) {
-		this.ownerName = ownerName;
-	}
-
-	public String getSearchSortAuthYN() {
-		return searchSortAuthYN;
-	}
-
-	public void setSearchSortAuthYN(String searchSortAuthYN) {
-		this.searchSortAuthYN = searchSortAuthYN;
-	}
-
-	public String getCI() {
-		return CI;
-	}
-
-	public void setCI(String cI) {
-		CI = cI;
-	}
-
-	public String getBirthDay() {
-		return birthDay;
-	}
-
-	public void setBirthDay(String birthDay) {
-		this.birthDay = birthDay;
-	}
 
-	
 }
src/main/java/itn/let/mjo/msg/service/MjonMsgService.java
--- src/main/java/itn/let/mjo/msg/service/MjonMsgService.java
+++ src/main/java/itn/let/mjo/msg/service/MjonMsgService.java
@@ -21,6 +21,10 @@
 	void deleteSendMsg(MjonMsgVO mjonMsgVO) throws Exception;
 
 	List<MjPhoneMemberVO> selectSendNumberList(MjPhoneMemberVO mjPhoneMemberVO)  throws Exception;
+
+	SendNumberStatusVO selectSendNumberStatus(String userId) throws Exception;
+
+	SendNumberStatusVO applyEmployeeCert(MjEmployeeCertVO employeeCertVO) throws Exception;
 	
 	MjPhoneMemberVO selectSendPhoneNumberByPhmId(String phmId) throws Exception;
 	
@@ -30,6 +34,10 @@
 
 	void insertSendNumber(MjPhoneMemberVO mjPhoneMemberVO) throws Exception;
 
+	void insertUserSendNumber(MjPhoneMemberVO mjPhoneMemberVO) throws Exception;
+
+	void validateUserSendNumberAvailability(MjPhoneMemberVO mjPhoneMemberVO) throws Exception;
+
 	List<MjonMsgVO> selectMjonMsgGroupList(MjonMsgVO searchVO)  throws Exception;
 	
 	List<MjonMsgVO> selectMjonMsgGroupCompleteList(MjonMsgVO searchVO)  throws Exception;
 
src/main/java/itn/let/mjo/msg/service/SendNumberStatusVO.java (added)
+++ src/main/java/itn/let/mjo/msg/service/SendNumberStatusVO.java
@@ -0,0 +1,34 @@
+package itn.let.mjo.msg.service;
+
+import java.io.Serializable;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class SendNumberStatusVO implements Serializable {
+
+	private static final long serialVersionUID = 1L;
+
+	private String userId;
+	private String dept;
+	private String nationality;
+	private int mobileCnt;
+	private int mobileMaxCnt;
+	private int wiredCnt;
+	private Integer wiredMaxCnt;
+	private String mobileAddYn;
+	private String wiredAddYn;
+	private String mobileExceedYn;
+	private String wiredExceedYn;
+
+	private Integer certCnt;
+	private String employeeCertHstrySeq;
+	private Integer applyCnt;
+	private String authYn;
+	private String authYnTxt;
+	private String rejectCode;
+	private String rejectReason;
+	private String applyPnttm;
+}
src/main/java/itn/let/mjo/msg/service/impl/MjonMsgDAO.java
--- src/main/java/itn/let/mjo/msg/service/impl/MjonMsgDAO.java
+++ src/main/java/itn/let/mjo/msg/service/impl/MjonMsgDAO.java
@@ -6,10 +6,12 @@
 
 import egovframework.rte.psl.dataaccess.EgovAbstractDAO;
 import itn.let.mjo.msg.service.MjPhoneMemberVO;
+import itn.let.mjo.msg.service.MjEmployeeCertVO;
 import itn.let.mjo.msg.service.MjonMapAddressVO;
 import itn.let.mjo.msg.service.MjonMsgResultCodeVO;
 import itn.let.mjo.msg.service.MjonMsgStatVO;
 import itn.let.mjo.msg.service.MjonMsgVO;
+import itn.let.mjo.msg.service.SendNumberStatusVO;
 import itn.let.mjo.msg.service.MjonSwearWordVO;
 import itn.let.mjo.reservmsg.service.MjonResvMsgVO;
 @Repository("mjonMsgDAO")
@@ -46,6 +48,26 @@
 	public List<MjPhoneMemberVO> selectSendNumberList(MjPhoneMemberVO mjPhoneMemberVO) throws Exception{
 		return (List<MjPhoneMemberVO>)list("mjonMsgDAO.selectSendNumberList", mjPhoneMemberVO);
 	}
+
+	public SendNumberStatusVO selectSendNumberStatus(SendNumberStatusVO searchVO) throws Exception {
+		return (SendNumberStatusVO) select("mjonMsgDAO.selectSendNumberStatus", searchVO);
+	}
+
+	public void insertEmployeeCertBase(MjEmployeeCertVO employeeCertVO) {
+		insert("mjonMsgDAO.insertEmployeeCertBase", employeeCertVO);
+	}
+
+	public MjEmployeeCertVO selectEmployeeCertForUpdate(MjEmployeeCertVO employeeCertVO) {
+		return (MjEmployeeCertVO) select("mjonMsgDAO.selectEmployeeCertForUpdate", employeeCertVO);
+	}
+
+	public void insertEmployeeCertHstry(MjEmployeeCertVO employeeCertVO) {
+		insert("mjonMsgDAO.insertEmployeeCertHstry", employeeCertVO);
+	}
+
+	public int updateEmployeeCertLastHstry(MjEmployeeCertVO employeeCertVO) {
+		return update("mjonMsgDAO.updateEmployeeCertLastHstry", employeeCertVO);
+	}
 	
 	public MjPhoneMemberVO selectSendPhoneNumberByPhmId(String phmId) throws Exception{
 		return (MjPhoneMemberVO) select("mjonMsgDAO.selectSendPhoneNumberByPhmId", phmId);
src/main/java/itn/let/mjo/msg/service/impl/MjonMsgServiceImpl.java
--- src/main/java/itn/let/mjo/msg/service/impl/MjonMsgServiceImpl.java
+++ src/main/java/itn/let/mjo/msg/service/impl/MjonMsgServiceImpl.java
@@ -9,6 +9,7 @@
 import javax.servlet.http.HttpServletRequest;
 
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.context.request.RequestContextHolder;
 import org.springframework.web.context.request.ServletRequestAttributes;
 
@@ -19,12 +20,14 @@
 import itn.com.cmm.LoginVO;
 import itn.com.utl.fcc.service.EgovDateUtil;
 import itn.let.mjo.msg.service.MjPhoneMemberVO;
+import itn.let.mjo.msg.service.MjEmployeeCertVO;
 import itn.let.mjo.msg.service.MjonMapAddressVO;
 import itn.let.mjo.msg.service.MjonMsgResultCodeVO;
 import itn.let.mjo.msg.service.MjonMsgService;
 import itn.let.mjo.msg.service.MjonMsgStatVO;
 import itn.let.mjo.msg.service.MjonMsgVO;
 import itn.let.mjo.msg.service.MjonSwearWordVO;
+import itn.let.mjo.msg.service.SendNumberStatusVO;
 import itn.let.mjo.msgdata.service.MjonMsgDataService;
 import itn.let.mjo.pay.service.MjonPayVO;
 import itn.let.mjo.pay.service.impl.MjonPayDAO;
@@ -50,6 +53,9 @@
 	
 	@Resource(name = "egovPhoneMemberIdGnrService")
     private EgovIdGnrService idgenPhoneMemberId;
+
+	@Resource(name = "egovEmployeeCertHstryIdGnrService")
+	private EgovIdGnrService idgenEmployeeCertHstryId;
 	
 	@Resource(name = "egovMjonMsgGroupIdGnrService")
     private EgovIdGnrService idgenMjonMsgGroupId;
@@ -174,6 +180,66 @@
 	public List<MjPhoneMemberVO> selectSendNumberList(MjPhoneMemberVO mjPhoneMemberVO) throws Exception {
 		return mjonMsgDAO.selectSendNumberList(mjPhoneMemberVO);
 	}
+
+	@Override
+	public SendNumberStatusVO selectSendNumberStatus(String userId) throws Exception {
+		if (userId == null || userId.trim().isEmpty()) {
+			throw new IllegalArgumentException("회원 아이디가 없습니다.");
+		}
+
+		SendNumberStatusVO searchVO = new SendNumberStatusVO();
+		searchVO.setUserId(userId);
+		SendNumberStatusVO status = mjonMsgDAO.selectSendNumberStatus(searchVO);
+		status.setMobileAddYn(status.getMobileCnt() < status.getMobileMaxCnt() ? "Y" : "N");
+		status.setWiredAddYn(status.getWiredMaxCnt() != null
+				&& status.getWiredCnt() < status.getWiredMaxCnt() ? "Y" : "N");
+		status.setMobileExceedYn(status.getMobileCnt() > status.getMobileMaxCnt() ? "Y" : "N");
+		status.setWiredExceedYn(status.getWiredMaxCnt() != null
+				&& status.getWiredCnt() > status.getWiredMaxCnt() ? "Y" : "N");
+		return status;
+	}
+
+	@Override
+	@Transactional(rollbackFor = Exception.class)
+	public SendNumberStatusVO applyEmployeeCert(MjEmployeeCertVO employeeCertVO) throws Exception {
+		if (employeeCertVO == null || employeeCertVO.getMberId() == null
+				|| employeeCertVO.getMberId().trim().isEmpty()) {
+			throw new IllegalArgumentException("회원 아이디가 없습니다.");
+		}
+		if (employeeCertVO.getApplyCnt() == null || employeeCertVO.getApplyCnt() < 1) {
+			throw new IllegalArgumentException("종사자 수를 1명 이상 입력해 주세요.");
+		}
+		if (!"Y".equals(employeeCertVO.getAgreeYn())) {
+			throw new IllegalArgumentException("증빙서류 수집·이용 동의가 필요합니다.");
+		}
+		if (employeeCertVO.getAtchFileId() == null || employeeCertVO.getAtchFileId().trim().isEmpty()) {
+			throw new IllegalArgumentException("종사자 수 증빙서류를 첨부해 주세요.");
+		}
+
+		SendNumberStatusVO status = selectSendNumberStatus(employeeCertVO.getMberId());
+		if (!"c".equalsIgnoreCase(status.getDept())) {
+			throw new IllegalStateException("법인회원만 종사자 수 인증을 신청할 수 있습니다.");
+		}
+
+		employeeCertVO.setAuthYn("N");
+		employeeCertVO.setFrstRegisterId(employeeCertVO.getMberId());
+		employeeCertVO.setLastUpdusrId(employeeCertVO.getMberId());
+
+		mjonMsgDAO.insertEmployeeCertBase(employeeCertVO);
+		MjEmployeeCertVO current = mjonMsgDAO.selectEmployeeCertForUpdate(employeeCertVO);
+		if (current != null && "N".equals(current.getAuthYn())) {
+			throw new IllegalStateException("이미 종사자 수 인증 심사가 진행 중입니다.");
+		}
+
+		employeeCertVO.setEmployeeCertHstrySeq(idgenEmployeeCertHstryId.getNextStringId());
+		mjonMsgDAO.insertEmployeeCertHstry(employeeCertVO);
+		if (employeeCertVO.getEmployeeCertHstrySeq() == null
+				|| mjonMsgDAO.updateEmployeeCertLastHstry(employeeCertVO) != 1) {
+			throw new IllegalStateException("종사자 수 인증 신청 이력 저장에 실패했습니다.");
+		}
+
+		return selectSendNumberStatus(employeeCertVO.getMberId());
+	}
 	
 	@Override
 	public MjPhoneMemberVO selectSendPhoneNumberByPhmId(String phmId) throws Exception{
@@ -192,11 +258,68 @@
 
 	@Override
 	public void insertSendNumber(MjPhoneMemberVO mjPhoneMemberVO) throws Exception {
+		setPhoneLineType(mjPhoneMemberVO);
 		mjPhoneMemberVO.setPhmId(idgenPhoneMemberId.getNextStringId());
 		mjonMsgDAO.insertSendNumber(mjPhoneMemberVO);
 	}
 
 	@Override
+	public void insertUserSendNumber(MjPhoneMemberVO mjPhoneMemberVO) throws Exception {
+		validateUserSendNumberAvailability(mjPhoneMemberVO);
+		mjPhoneMemberVO.setPhmId(idgenPhoneMemberId.getNextStringId());
+		mjonMsgDAO.insertSendNumber(mjPhoneMemberVO);
+	}
+
+	@Override
+	public void validateUserSendNumberAvailability(MjPhoneMemberVO mjPhoneMemberVO) throws Exception {
+		if (mjPhoneMemberVO == null || mjPhoneMemberVO.getUserId() == null
+				|| mjPhoneMemberVO.getUserId().trim().isEmpty()) {
+			throw new IllegalArgumentException("로그인 정보를 확인할 수 없습니다.");
+		}
+		if (!isValidSendNumber(mjPhoneMemberVO.getPhoneNumber())) {
+			throw new IllegalArgumentException("등록할 수 없는 발신번호입니다.");
+		}
+
+		setPhoneLineType(mjPhoneMemberVO);
+		SendNumberStatusVO status = selectSendNumberStatus(mjPhoneMemberVO.getUserId());
+
+		if ("M".equals(mjPhoneMemberVO.getPhmLineType()) && "N".equals(status.getMobileAddYn())) {
+			throw new IllegalStateException("무선 발신번호는 최대 " + status.getMobileMaxCnt() + "개까지 등록할 수 있습니다.");
+		}
+		if (!"M".equals(mjPhoneMemberVO.getPhmLineType()) && "N".equals(status.getWiredAddYn())) {
+			if ("c".equalsIgnoreCase(status.getDept()) && status.getWiredMaxCnt() == null) {
+				throw new IllegalStateException("법인 유선 발신번호는 종사자 수 인증 완료 후 등록할 수 있습니다.");
+			}
+			throw new IllegalStateException("유선 발신번호 등록 가능 개수를 초과했습니다.");
+		}
+	}
+
+	private boolean isValidSendNumber(String phoneNumber) {
+		String normalized = phoneNumber == null ? "" : phoneNumber.replaceAll("[^0-9]", "");
+		if (normalized.matches("^010[0-9]{8}$")) {
+			return true;
+		}
+		if (normalized.matches("^(15|16|18)[0-9]{6}$")) {
+			return true;
+		}
+		return !normalized.startsWith("050")
+				&& normalized.matches("^(02[0-9]{7,8}|0[3-9][0-9][0-9]{7,8})$");
+	}
+
+	private void setPhoneLineType(MjPhoneMemberVO mjPhoneMemberVO) {
+		String phoneNumber = mjPhoneMemberVO.getPhoneNumber();
+		String normalized = phoneNumber == null ? "" : phoneNumber.replaceAll("[^0-9]", "");
+
+		if (normalized.startsWith("010")) {
+			mjPhoneMemberVO.setPhmLineType("M");
+		} else if (normalized.matches("^(15|16|18)[0-9]{6}$")) {
+			mjPhoneMemberVO.setPhmLineType("R");
+		} else {
+			mjPhoneMemberVO.setPhmLineType("L");
+		}
+	}
+
+	@Override
 	public List<MjonMsgVO> selectMjonMsgGroupList(MjonMsgVO mjonMsgVO) throws Exception {
 		return mjonMsgDAO.selectMjonMsgGroupList(mjonMsgVO);
 	}
src/main/java/itn/let/mjo/msgdata/service/MjonMsgDataService.java
--- src/main/java/itn/let/mjo/msgdata/service/MjonMsgDataService.java
+++ src/main/java/itn/let/mjo/msgdata/service/MjonMsgDataService.java
@@ -54,7 +54,9 @@
 	
 	//내 문자 보관함 리스트 불러오기
 	public List<?> selectMyMsgDataListAjax(MjonMsgDataVO mjonMsgDataVO) throws Exception;
-	
+
+	public List<MberManageVO> selectSendPhonNumAndPhmLineTypeList(String userId) throws Exception;
+
 	//발신번호 리스트 불러오기
 	public List<String> selectSendPhonNumList(String userId) throws Exception;
 	
@@ -191,6 +193,8 @@
 
 	public MjonMsgReturnVO sendSysMsgData(MjonMsgVO mjonMsgVO, HttpServletRequest request) throws Exception;
 
+	public MjonMsgReturnVO sendSenderAuthCode(String phoneNo, String code, HttpServletRequest request) throws Exception;
+
 	public Boolean selectSysMsgTodaySendYn(SendLogVO sendLogVO) throws Exception;
 
 
 
src/main/java/itn/let/mjo/msgdata/service/MsgSenderAuthService.java (added)
+++ src/main/java/itn/let/mjo/msgdata/service/MsgSenderAuthService.java
@@ -0,0 +1,17 @@
+package itn.let.mjo.msgdata.service;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+public interface MsgSenderAuthService {
+
+    SenderAuthResult getStatus(String userId, String callFrom, HttpSession session) throws Exception;
+
+    SenderAuthResult sendCode(String userId, String callFrom, HttpSession session,
+                              HttpServletRequest request) throws Exception;
+
+    SenderAuthResult verifyCode(String userId, String callFrom, String code,
+                                HttpSession session) throws Exception;
+
+    boolean isVerified(String userId, String callFrom, HttpSession session) throws Exception;
+}
 
src/main/java/itn/let/mjo/msgdata/service/SenderAuthRequest.java (added)
+++ src/main/java/itn/let/mjo/msgdata/service/SenderAuthRequest.java
@@ -0,0 +1,13 @@
+package itn.let.mjo.msgdata.service;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Setter
+@Getter
+public class SenderAuthRequest {
+
+    private String callFrom;
+    private String code;
+
+}
 
src/main/java/itn/let/mjo/msgdata/service/SenderAuthResult.java (added)
+++ src/main/java/itn/let/mjo/msgdata/service/SenderAuthResult.java
@@ -0,0 +1,28 @@
+package itn.let.mjo.msgdata.service;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Setter
+@Getter
+public class SenderAuthResult {
+
+    private boolean verified;
+    private String channel;
+    private String maskedDestination;
+    private int expiresIn;
+    private int resendAfter;
+
+    public SenderAuthResult() {
+    }
+
+    public SenderAuthResult(boolean verified, String channel, String maskedDestination,
+                            int expiresIn, int resendAfter) {
+        this.verified = verified;
+        this.channel = channel;
+        this.maskedDestination = maskedDestination;
+        this.expiresIn = expiresIn;
+        this.resendAfter = resendAfter;
+    }
+
+}
src/main/java/itn/let/mjo/msgdata/service/impl/MjonMsgDataDAO.java
--- src/main/java/itn/let/mjo/msgdata/service/impl/MjonMsgDataDAO.java
+++ src/main/java/itn/let/mjo/msgdata/service/impl/MjonMsgDataDAO.java
@@ -107,7 +107,12 @@
 		
 		return list("mjonMsgDAO.selectMyMsgDataListAjax", mjonMsgDataVO);
 	}
-	
+
+	@SuppressWarnings("unchecked")
+	public List<MberManageVO> selectSendPhonNumAndPhmLineTypeList(String userId) throws Exception{
+		return (List<MberManageVO>) list("mjonMsgDAO.selectSendPhonNumAndPhmLineTypeList", userId);
+	}
+
 	@SuppressWarnings("unchecked")
 	public List<String> selectSendPhonNumList(String userId) throws Exception{
 		
src/main/java/itn/let/mjo/msgdata/service/impl/MjonMsgDataServiceImpl.java
--- src/main/java/itn/let/mjo/msgdata/service/impl/MjonMsgDataServiceImpl.java
+++ src/main/java/itn/let/mjo/msgdata/service/impl/MjonMsgDataServiceImpl.java
@@ -12,7 +12,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Random;
+import java.security.SecureRandom;
 import java.util.stream.Collectors;
 
 import javax.annotation.Resource;
@@ -64,6 +64,7 @@
 import itn.let.mjo.spammsg.service.MjonSpamMsgService;
 import itn.let.module.base.PriceAndPoint;
 import itn.let.sym.site.service.EgovSiteManagerService;
+import itn.let.sym.site.service.SiteManagerVO;
 import itn.let.sym.site.service.JoinSettingVO;
 import itn.let.sym.site.service.impl.SiteManagerDAO;
 import itn.let.uat.uia.web.SendLogVO;
@@ -2525,6 +2526,10 @@
 		return mjonMsgDataDAO.selectMyMsgDataListAjax(mjonMsgDataVO);
 	}
 
+	public List<MberManageVO> selectSendPhonNumAndPhmLineTypeList(String userId) throws Exception {
+		return mjonMsgDataDAO.selectSendPhonNumAndPhmLineTypeList(userId);
+	}
+
 	public List<String> selectSendPhonNumList(String userId) throws Exception {
 
 		return mjonMsgDataDAO.selectSendPhonNumList(userId);
@@ -4297,6 +4302,11 @@
 
 		boolean isNotified = mjonCommon.processUserAndCheckSms(mjonMsgVO, userId);
 
+		// 업무시간(평일 09:30~13:00, 14:00~18:30 - 점심시간 제외) 중 스팸 키워드가 감지된 경우,
+		// 금칙어 팝업 띄움 msgDataView.jsp -> businessHourSpam : true
+		// 금칙어/URL 탐지 여부만 담는 spamKeyword로 판단
+		boolean businessHourSpam = isBusinessHour() && StringUtils.isNotEmpty(mjonMsgVO.getSpamKeyword());
+
 		if (isNotified) {
 			System.out.println("스미싱 알림이 처리되었습니다.");
 			mjonCommon.getAdminMsgSandSlack(mjonMsgVO);
@@ -4306,6 +4316,8 @@
 		}else {
 			System.out.println("알림 조건을 충족하지 않았습니다.");
 		}
+
+		returnMap.put("businessHourSpam", businessHourSpam);
 
 		statusResponse.setStatus(HttpStatus.OK);
 		statusResponse.setObject(returnMap);
@@ -4606,6 +4618,59 @@
 
 		return returnMap;
 	}*/
+
+	/**
+	 * @methodName	: isBusinessHour
+	 * @author : 염성찬
+	 * @description	: 업무시간(평일, 점심시간 제외) 여부 판단,
+	 *              테이블 (SITEMANAGER 시간, MJ_HOLIDAY 공휴일)
+	 * @return : boolean
+	 */
+	private boolean isBusinessHour() {
+		try {
+			Calendar cal = Calendar.getInstance();
+
+			int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK); // 주말(토, 일) 체크
+			if (dayOfWeek == Calendar.SATURDAY || dayOfWeek == Calendar.SUNDAY) {
+				return false;
+			}
+
+			// 공휴일 여부 확인 (MJ_HOLIDAY 테이블)
+			String today = new SimpleDateFormat("yyyy-MM-dd").format(cal.getTime());
+
+			MsgHolidayVO holidaySearchVO = new MsgHolidayVO();
+			holidaySearchVO.setSearchHoliYear(Integer.toString(cal.get(Calendar.YEAR)));
+			holidaySearchVO.setFirstIndex(0);
+			holidaySearchVO.setRecordCountPerPage(10000);
+			List<MsgHolidayVO> holidayList = msgHolidayService.selectMsgHolidayList(holidaySearchVO);
+
+			boolean isHoliday = holidayList.stream()
+					.anyMatch(holiday -> today.equals(holiday.getHolidayDate()));
+			if (isHoliday) {
+				return false;
+			}
+
+			// 운영시간 설정 조회 (SITEMANAGER 테이블)
+			SiteManagerVO siteManagerVO = egovSiteManagerService.selectSiteManagerVO(new SiteManagerVO());
+
+			SimpleDateFormat dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
+			String todayPrefix = today + " ";
+
+			Date now = cal.getTime();
+			Date operStart = dateTimeFormat.parse(todayPrefix + siteManagerVO.getOperationStartTime());
+			Date lunchStart = dateTimeFormat.parse(todayPrefix + siteManagerVO.getLunchStartTime());
+			Date lunchEnd = dateTimeFormat.parse(todayPrefix + siteManagerVO.getLunchEndTime());
+			Date operEnd = dateTimeFormat.parse(todayPrefix + siteManagerVO.getOperationEndTime());
+
+			return (!now.before(operStart) && now.before(lunchStart))
+					|| (!now.before(lunchEnd) && now.before(operEnd));
+
+		} catch (Exception e) {
+			// isBusinessHour 오류 발생하더라도 비지니스 로직은 정상작동 (문자 발송 정상)
+			log.error("isBusinessHour 업무시간 판단 중 오류 발생 : {}", e.getMessage());
+			return false;
+		}
+	}
 
 	private void handleSpamMsg(MjonMsgVO mjonMsgVO) {
 		try {
@@ -5163,25 +5228,21 @@
 
 	@Override
 	public MjonMsgReturnVO sendSysMsgData(MjonMsgVO mjonMsgVO,HttpServletRequest request) throws Exception {
+		String code = String.format("%06d", new SecureRandom().nextInt(1000000));
+		return sendSysMsgData(mjonMsgVO, request, code);
+	}
 
-		
+	@Override
+	public MjonMsgReturnVO sendSenderAuthCode(String phoneNo, String code,
+			HttpServletRequest request) throws Exception {
+		MjonMsgVO mjonMsgVO = new MjonMsgVO();
+		mjonMsgVO.setCallTo(phoneNo);
+		return sendSysMsgData(mjonMsgVO, request, code);
+	}
 
-		// 비밀번호 찾기 문자내용
-		String contents = "[문자고] 인증번호 : %code%";
-		String code = "";
-		long seed = System.currentTimeMillis();
-		Random random = new Random(seed);
-		// code = Integer.toString(random.nextInt(4));
-		StringBuffer randomCode = new StringBuffer();
-		for (int i = 0; i < 6; i++) {
-			code = randomCode.append(Integer.toString(random.nextInt(10))).toString();
-		}
-		contents = contents.replace("%code%", code);
-		System.out.println("contents : "+ contents);
-		System.out.println("contents : "+ contents);
-		System.out.println("contents : "+ contents);
-		System.out.println("contents : "+ contents);
-		System.out.println("contents : "+ contents);
+	private MjonMsgReturnVO sendSysMsgData(MjonMsgVO mjonMsgVO, HttpServletRequest request,
+			String code) throws Exception {
+		String contents = "[문자고] 인증번호 : " + code;
 
 		mjonMsgVO.setSmsTxt(contents);
 		mjonMsgVO.setReserveYn("N");
@@ -5251,10 +5312,6 @@
 			
 		} catch (Exception e) {
 		}
-		
-		
-		
-		
 		return returnVO;
 	}
 
 
src/main/java/itn/let/mjo/msgdata/service/impl/MsgSenderAuthServiceImpl.java (added)
+++ src/main/java/itn/let/mjo/msgdata/service/impl/MsgSenderAuthServiceImpl.java
@@ -0,0 +1,277 @@
+package itn.let.mjo.msgdata.service.impl;
+
+import java.io.Serializable;
+import java.security.SecureRandom;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+
+import itn.let.mjo.msgdata.service.MjonMsgDataService;
+import itn.let.mjo.msgdata.service.MjonMsgReturnVO;
+import itn.let.mjo.msgdata.service.MsgSenderAuthService;
+import itn.let.mjo.msgdata.service.SenderAuthResult;
+import itn.let.uat.uia.web.EmailVO;
+import itn.let.uat.uia.web.SendMail;
+import itn.let.uss.umt.service.MberManageVO;
+
+@Service("MsgSenderAuthService")
+public class MsgSenderAuthServiceImpl implements MsgSenderAuthService {
+
+    private static final String PENDING_AUTHS = "msgSenderAuth.pendingAuths";
+    private static final String VERIFIED_SENDERS = "msgSenderAuth.verifiedSenders";
+
+    private static final int EXPIRES_IN_SECONDS = 180;
+    private static final int RESEND_AFTER_SECONDS = 30;
+
+    private final SecureRandom secureRandom = new SecureRandom();
+
+    @Resource(name = "MjonMsgDataService")
+    private MjonMsgDataService mjonMsgDataService;
+
+    @Override
+    public SenderAuthResult getStatus(String userId, String callFrom, HttpSession session) throws Exception {
+        String sender = validateSender(userId, callFrom);
+        String channel = getChannel(sender);
+        String destination = getDestinationForStatus(userId, sender, channel);
+        return result(isVerifiedSender(userId, sender, session), channel, destination);
+    }
+
+    @Override
+    public SenderAuthResult sendCode(String userId, String callFrom, HttpSession session, HttpServletRequest request) throws Exception {
+        String sender = validateSender(userId, callFrom);
+        String channel = getChannel(sender);
+        String destination = getDestination(userId, sender, channel);
+        long now = System.currentTimeMillis();
+        String senderKey = verifiedKey(userId, sender);
+        Map<String, PendingAuth> pendingAuths = getPendingAuths(session);
+        String code = String.format("%06d", secureRandom.nextInt(1000000));
+        PendingAuth pendingAuth = new PendingAuth(code, now);
+        PendingAuth previousAuth;
+
+        synchronized (pendingAuths) {
+            previousAuth = pendingAuths.get(senderKey);
+            if (previousAuth != null
+                    && now - previousAuth.getIssuedAt() < RESEND_AFTER_SECONDS * 1000L) {
+                throw new IllegalStateException("30초 후 다시 요청해 주세요.");
+            }
+            pendingAuths.put(senderKey, pendingAuth);
+        }
+
+        try {
+            if ("sms".equals(channel)) {
+                MjonMsgReturnVO sendResult = mjonMsgDataService.sendSenderAuthCode(destination, code, request);
+                if (sendResult == null || "0".equals(sendResult.getSendMsgCnt())) {
+                    throw new IllegalStateException("인증번호 문자 발송에 실패했습니다.");
+                }
+            } else {
+                sendEmail(destination, sender, code);
+            }
+        } catch (Exception e) {
+            restorePendingAuth(pendingAuths, senderKey, pendingAuth, previousAuth);
+            session.setAttribute(PENDING_AUTHS, pendingAuths);
+            throw e;
+        }
+
+        session.setAttribute(PENDING_AUTHS, pendingAuths);
+        return result(false, channel, destination);
+    }
+
+    @Override
+    public SenderAuthResult verifyCode(String userId, String callFrom, String code, HttpSession session) throws Exception {
+        String sender = validateSender(userId, callFrom);
+        if (code == null || !code.matches("^[0-9]{6}$")) {
+            throw new IllegalArgumentException("인증번호 숫자 6자리를 입력해 주세요.");
+        }
+
+        String senderKey = verifiedKey(userId, sender);
+        Map<String, PendingAuth> pendingAuths = getPendingAuths(session);
+
+        synchronized (pendingAuths) {
+            PendingAuth pendingAuth = pendingAuths.get(senderKey);
+            if (pendingAuth == null) {
+                throw new IllegalStateException("인증번호를 먼저 요청해 주세요.");
+            }
+            if (System.currentTimeMillis() - pendingAuth.getIssuedAt() > EXPIRES_IN_SECONDS * 1000L) {
+                pendingAuths.remove(senderKey);
+                session.setAttribute(PENDING_AUTHS, pendingAuths);
+                throw new IllegalStateException("인증번호 입력 시간이 만료되었습니다. 다시 요청해 주세요.");
+            }
+            if (!code.equals(pendingAuth.getCode())) {
+                throw new IllegalArgumentException("인증번호가 일치하지 않습니다.");
+            }
+            pendingAuths.remove(senderKey);
+        }
+
+        Set<String> verifiedSenders = getVerifiedSenders(session);
+        verifiedSenders.add(senderKey);
+        session.setAttribute(PENDING_AUTHS, pendingAuths);
+        session.setAttribute(VERIFIED_SENDERS, verifiedSenders);
+
+        String channel = getChannel(sender);
+        return result(true, channel, getDestinationForStatus(userId, sender, channel));
+    }
+
+    @Override
+    public boolean isVerified(String userId, String callFrom, HttpSession session) throws Exception {
+        if (session == null || StringUtils.isBlank(userId)) {
+            return false;
+        }
+        String sender = validateSender(userId, callFrom);
+        return isVerifiedSender(userId, sender, session);
+    }
+
+    private boolean isVerifiedSender(String userId, String sender, HttpSession session) {
+        return session != null && getVerifiedSenders(session).contains(verifiedKey(userId, sender));
+    }
+
+    private String validateSender(String userId, String callFrom) throws Exception {
+        if (StringUtils.isBlank(userId)) {
+            throw new IllegalStateException("로그인 후 이용해 주세요.");
+        }
+
+        String sender = normalizePhone(callFrom);
+        if (!sender.matches("^[0-9]{8,12}$")) {
+            throw new IllegalArgumentException("발신번호를 확인해 주세요.");
+        }
+
+        List<String> senders = mjonMsgDataService.selectSendPhonNumList(userId);
+        if (senders != null) {
+            for (String registeredSender : senders) {
+                if (sender.equals(normalizePhone(registeredSender))) {
+                    return sender;
+                }
+            }
+        }
+        throw new IllegalArgumentException("승인 완료된 발신번호가 아닙니다.");
+    }
+
+    private String getChannel(String sender) {
+        return sender.startsWith("010") ? "sms" : "email";
+    }
+
+    private String getDestinationForStatus(String userId, String sender, String channel) throws Exception {
+        try {
+            return getDestination(userId, sender, channel);
+        } catch (IllegalStateException e) {
+            return "";
+        }
+    }
+
+    private String getDestination(String userId, String sender, String channel) throws Exception {
+        if ("sms".equals(channel)) {
+            return sender;
+        }
+
+        MberManageVO member = mjonMsgDataService.selectMberManageInfo(userId);
+        String email = member == null ? "" : StringUtils.trimToEmpty(member.getMberEmailAdres());
+        if (!email.matches("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$")) {
+            throw new IllegalStateException("회원가입 담당자 이메일을 확인해 주세요.");
+        }
+        return email;
+    }
+
+    private void sendEmail(String email, String sender, String code) throws Exception {
+        EmailVO mail = new EmailVO();
+        mail.setSend_from("noreply@munjaon.co.kr");
+        mail.setSend_to(email);
+        mail.setTitle("[문자온] 발신번호 인증번호 안내");
+        mail.setContents("문자 발송을 위한 발신번호 " + sender
+                + "의 인증번호는 <strong>" + code + "</strong> 입니다.<br>"
+                + "인증번호는 3분 동안 유효합니다.");
+        new SendMail().itnSendMail(mail);
+    }
+
+    private SenderAuthResult result(boolean verified, String channel, String destination) {
+        return new SenderAuthResult(verified, channel, maskDestination(destination, channel),
+                EXPIRES_IN_SECONDS, RESEND_AFTER_SECONDS);
+    }
+
+    private String maskDestination(String destination, String channel) {
+        if (StringUtils.isEmpty(destination)) {
+            return "";
+        }
+        if ("sms".equals(channel)) {
+            return destination.substring(0, 3) + "****" + destination.substring(destination.length() - 4);
+        }
+
+        int at = destination.indexOf('@');
+        String local = destination.substring(0, at);
+        return local.substring(0, Math.min(2, local.length())) + "***" + destination.substring(at);
+    }
+
+    @SuppressWarnings("unchecked")
+    private Map<String, PendingAuth> getPendingAuths(HttpSession session) {
+        synchronized (session) {
+            Map<String, PendingAuth> pendingAuths =
+                    (Map<String, PendingAuth>) session.getAttribute(PENDING_AUTHS);
+            if (pendingAuths == null) {
+                pendingAuths = new ConcurrentHashMap<String, PendingAuth>();
+                session.setAttribute(PENDING_AUTHS, pendingAuths);
+            }
+            return pendingAuths;
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private Set<String> getVerifiedSenders(HttpSession session) {
+        synchronized (session) {
+            Set<String> verifiedSenders = (Set<String>) session.getAttribute(VERIFIED_SENDERS);
+            if (verifiedSenders == null) {
+                verifiedSenders = ConcurrentHashMap.<String>newKeySet();
+                session.setAttribute(VERIFIED_SENDERS, verifiedSenders);
+            }
+            return verifiedSenders;
+        }
+    }
+
+    private void restorePendingAuth(Map<String, PendingAuth> pendingAuths, String senderKey,
+            PendingAuth failedAuth, PendingAuth previousAuth) {
+        synchronized (pendingAuths) {
+            if (pendingAuths.get(senderKey) != failedAuth) {
+                return;
+            }
+            if (previousAuth == null) {
+                pendingAuths.remove(senderKey);
+            } else {
+                pendingAuths.put(senderKey, previousAuth);
+            }
+        }
+    }
+
+    private String normalizePhone(String phone) {
+        return phone == null ? "" : phone.replaceAll("[^0-9]", "");
+    }
+
+    private String verifiedKey(String userId, String sender) {
+        return userId + ":" + sender;
+    }
+
+    private static final class PendingAuth implements Serializable {
+
+        private static final long serialVersionUID = 1L;
+
+        private final String code;
+        private final long issuedAt;
+
+        private PendingAuth(String code, long issuedAt) {
+            this.code = code;
+            this.issuedAt = issuedAt;
+        }
+
+        private String getCode() {
+            return code;
+        }
+
+        private long getIssuedAt() {
+            return issuedAt;
+        }
+    }
+}
src/main/java/itn/let/mjo/msgdata/web/MjonMsgDataController.java
--- src/main/java/itn/let/mjo/msgdata/web/MjonMsgDataController.java
+++ src/main/java/itn/let/mjo/msgdata/web/MjonMsgDataController.java
@@ -637,16 +637,11 @@
 		if(!userId.equals("")) {//로그인 된 사용자만 발신번호를 불러오도록 수정
 			
 			//아이디 발신번호 리스트 불러오기.
-			List<String> resultSendPhonList = mjonMsgDataService.selectSendPhonNumList(userId);
-			List<String> resultPhonList = new ArrayList<String>();
-			MJUtil mjUtil = new MJUtil();
-			
-			for(String phone : resultSendPhonList) {
-				
-				resultPhonList.add(mjUtil.addDash(phone));
-				
+			List<MberManageVO> resultSendPhonList = mjonMsgDataService.selectSendPhonNumAndPhmLineTypeList(userId);
+			for(MberManageVO vo : resultSendPhonList) {
+				vo.setPhoneNumber(MJUtil.addDash(vo.getPhoneNumber()));
 			}
-			model.addAttribute("resultPhonList", resultPhonList);
+			model.addAttribute("resultPhonList", resultSendPhonList);
 			
 		}
 
@@ -1331,16 +1326,11 @@
 		if(!userId.equals("")) {//로그인 된 사용자만 발신번호를 불러오도록 수정
 		
 			//아이디 발신번호 리스트 불러오기.
-			List<String> resultSendPhonList = mjonMsgDataService.selectSendPhonNumList(userId);
-			List<String> resultPhonList = new ArrayList<String>();
-			MJUtil mjUtil = new MJUtil();
-			
-			for(String phone : resultSendPhonList) {
-				
-				resultPhonList.add(mjUtil.addDash(phone));
-				
+			List<MberManageVO> resultSendPhonList = mjonMsgDataService.selectSendPhonNumAndPhmLineTypeList(userId);
+			for(MberManageVO vo : resultSendPhonList) {
+				vo.setPhoneNumber(MJUtil.addDash(vo.getPhoneNumber()));
 			}
-			model.addAttribute("resultPhonList", resultPhonList);
+			model.addAttribute("resultPhonList", resultSendPhonList);
 		
 		}
 		
 
src/main/java/itn/let/mjo/msgdata/web/MsgAuthenticatedSendRestController.java (added)
+++ src/main/java/itn/let/mjo/msgdata/web/MsgAuthenticatedSendRestController.java
@@ -0,0 +1,52 @@
+package itn.let.mjo.msgdata.web;
+
+import java.time.LocalDateTime;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+
+import egovframework.rte.fdl.security.userdetails.util.EgovUserDetailsHelper;
+import itn.com.cmm.LoginVO;
+import itn.com.utl.fcc.service.EgovStringUtil;
+import itn.let.mail.service.StatusResponse;
+import itn.let.mjo.msg.service.MjonMsgVO;
+import itn.let.mjo.msgdata.service.MjonMsgDataService;
+import itn.let.mjo.msgdata.service.MsgSenderAuthService;
+
+/** 새 문자 발송 화면에서만 사용하는 발신번호 인증 필수 발송 API. */
+@RestController
+public class MsgAuthenticatedSendRestController {
+
+    @Resource(name = "MjonMsgDataService")
+    private MjonMsgDataService mjonMsgDataService;
+
+    @Resource(name = "MsgSenderAuthService")
+    private MsgSenderAuthService msgSenderAuthService;
+
+    @RequestMapping(value = "/web/mjon/msgdata/sendMsgDataAuthenticatedAjax_advc.do", method = RequestMethod.POST)
+    public ResponseEntity<StatusResponse> send(@RequestBody MjonMsgVO mjonMsgVO, HttpServletRequest request) throws Exception {
+        LoginVO loginVO = EgovUserDetailsHelper.isAuthenticated() ? (LoginVO) EgovUserDetailsHelper.getAuthenticatedUser() : null;
+        String userId = loginVO == null ? "" : EgovStringUtil.isNullToString(loginVO.getId());
+        if (StringUtils.isBlank(userId)) {
+            return ResponseEntity.ok().body(new StatusResponse(HttpStatus.UNAUTHORIZED, "로그인 후 이용해 주세요.", LocalDateTime.now()));
+        }
+
+        try {
+            if (!msgSenderAuthService.isVerified(userId, mjonMsgVO.getCallFrom(), request.getSession(false))) {
+                return ResponseEntity.ok().body(new StatusResponse(HttpStatus.PRECONDITION_FAILED, "발신번호 인증이 필요합니다.", "SENDER_AUTH_REQUIRED", LocalDateTime.now()));
+            }
+        } catch (IllegalArgumentException e) {
+            return ResponseEntity.ok().body(new StatusResponse(HttpStatus.BAD_REQUEST, e.getMessage(), LocalDateTime.now()));
+        }
+
+        return ResponseEntity.ok().body(mjonMsgDataService.sendMsgData_advc(mjonMsgVO, request));
+    }
+}
 
src/main/java/itn/let/mjo/msgdata/web/MsgSenderAuthRestController.java (added)
+++ src/main/java/itn/let/mjo/msgdata/web/MsgSenderAuthRestController.java
@@ -0,0 +1,63 @@
+package itn.let.mjo.msgdata.web;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+
+import egovframework.rte.fdl.security.userdetails.util.EgovUserDetailsHelper;
+import itn.com.cmm.LoginVO;
+import itn.com.utl.response.ApiResponse;
+import itn.let.mjo.msgdata.service.MsgSenderAuthService;
+import itn.let.mjo.msgdata.service.SenderAuthRequest;
+import itn.let.mjo.msgdata.service.SenderAuthResult;
+
+@RestController
+@RequestMapping("/web/mjon/msgdata/sender-auth")
+public class MsgSenderAuthRestController {
+
+    @Resource(name = "MsgSenderAuthService")
+    private MsgSenderAuthService msgSenderAuthService;
+
+    @RequestMapping(value = "/status.do", method = RequestMethod.POST)
+    public ApiResponse<SenderAuthResult> status(@RequestBody SenderAuthRequest authRequest, HttpSession session) {
+        try {
+            return ApiResponse.success(msgSenderAuthService.getStatus(getUserId(), authRequest.getCallFrom(), session));
+        } catch (Exception e) {
+            return ApiResponse.fail(e.getMessage());
+        }
+    }
+
+    @RequestMapping(value = "/code.do", method = RequestMethod.POST)
+    public ApiResponse<SenderAuthResult> sendCode(@RequestBody SenderAuthRequest authRequest, HttpSession session, HttpServletRequest request) {
+        try {
+            return ApiResponse.success(msgSenderAuthService.sendCode(getUserId(), authRequest.getCallFrom(), session, request), "인증번호가 발송되었습니다.");
+        } catch (Exception e) {
+            return ApiResponse.fail(e.getMessage());
+        }
+    }
+
+    @RequestMapping(value = "/verify.do", method = RequestMethod.POST)
+    public ApiResponse<SenderAuthResult> verify(@RequestBody SenderAuthRequest authRequest, HttpSession session) {
+        try {
+            return ApiResponse.success(msgSenderAuthService.verifyCode(getUserId(), authRequest.getCallFrom(), authRequest.getCode(), session), "발신번호 인증이 완료되었습니다.");
+        } catch (Exception e) {
+            return ApiResponse.fail(e.getMessage());
+        }
+    }
+
+    private String getUserId() {
+        if (!EgovUserDetailsHelper.isAuthenticated()) {
+            throw new IllegalStateException("로그인 후 이용해 주세요.");
+        }
+        LoginVO user = (LoginVO) EgovUserDetailsHelper.getAuthenticatedUser();
+        if (user == null || user.getId() == null || user.getId().isEmpty()) {
+            throw new IllegalStateException("로그인 정보를 확인할 수 없습니다.");
+        }
+        return user.getId();
+    }
+}
src/main/java/itn/let/mjo/msgsent/service/MjonMsgSentVO.java
--- src/main/java/itn/let/mjo/msgsent/service/MjonMsgSentVO.java
+++ src/main/java/itn/let/mjo/msgsent/service/MjonMsgSentVO.java
@@ -108,5 +108,8 @@
 	// 결과 리스트 select 할 떄
 	// TIMESTAMPDIFF(minute, DATE_FORMAT(B.REQ_DATE, '%Y-%m-%d %T'), DATE_FORMAT(NOW(), '%Y-%m-%d %T')) as diffMin
 	private int diffMin;
-	
+
+	private String apiAprvYn;
+	private String apiAprvDt;
+
 }
src/main/java/itn/let/schdlr/service/SchedulerUtil.java
--- src/main/java/itn/let/schdlr/service/SchedulerUtil.java
+++ src/main/java/itn/let/schdlr/service/SchedulerUtil.java
@@ -38,6 +38,7 @@
 import itn.let.mjo.pay.service.MjonPayVO;
 import itn.let.sts.com.StatsVO;
 import itn.let.sts.cst.service.EgovConectStatsService;
+import itn.let.uat.uia.service.SecondAuthMethodLogService;
 import itn.let.uss.umt.service.EgovUserManageService;
 import itn.let.utl.user.service.MjonNoticeSendUtil;
 import net.javacrumbs.shedlock.core.LockProvider;
@@ -87,6 +88,9 @@
 	
 	@Resource(name="MjonMsgDataDAO")
 	private MjonMsgDataDAO mjonMsgDataDAO;
+
+	@Resource(name = "secondAuthMethodLogService")
+	private SecondAuthMethodLogService secondAuthMethodLogService;
 	
 	@Resource(name="kakaoAlimTalkService")
 	private KakaoAlimTalkService kakaoAlimTalkService;
 
src/main/java/itn/let/sym/log/access/UserAccessLogService.java (added)
+++ src/main/java/itn/let/sym/log/access/UserAccessLogService.java
@@ -0,0 +1,5 @@
+package itn.let.sym.log.access;
+
+public interface UserAccessLogService {
+    void insertUserAccessLog(UserAccessLogVO userAccessLogVO) throws Exception;
+}
 
src/main/java/itn/let/sym/log/access/UserAccessLogVO.java (added)
+++ src/main/java/itn/let/sym/log/access/UserAccessLogVO.java
@@ -0,0 +1,18 @@
+package itn.let.sym.log.access;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@NoArgsConstructor
+public class UserAccessLogVO {
+    private String logId;
+    private String userId;
+    private String accessIp;
+    private String menuNm;
+    private String uri;
+    private String result;
+    private String frstRegistPnttm;
+}
 
src/main/java/itn/let/sym/log/access/service/UserAccessLogDAO.java (added)
+++ src/main/java/itn/let/sym/log/access/service/UserAccessLogDAO.java
@@ -0,0 +1,12 @@
+package itn.let.sym.log.access.service;
+
+import itn.com.cmm.service.impl.EgovComAbstractDAO;
+import itn.let.sym.log.access.UserAccessLogVO;
+import org.springframework.stereotype.Repository;
+
+@Repository("userAccessLogDAO")
+public class UserAccessLogDAO extends EgovComAbstractDAO {
+    public void insertUserAccessLog(UserAccessLogVO userAccessLogVO) throws Exception{
+        insert("userAccessLogDAO.insertUserAccessLog", userAccessLogVO);
+    }
+}
 
src/main/java/itn/let/sym/log/access/service/UserAccessLogServiceImpl.java (added)
+++ src/main/java/itn/let/sym/log/access/service/UserAccessLogServiceImpl.java
@@ -0,0 +1,24 @@
+package itn.let.sym.log.access.service;
+
+import egovframework.rte.fdl.cmmn.EgovAbstractServiceImpl;
+import egovframework.rte.fdl.idgnr.EgovIdGnrService;
+import itn.let.sym.log.access.UserAccessLogService;
+import itn.let.sym.log.access.UserAccessLogVO;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+
+@Service(value = "userAccessLogService")
+public class UserAccessLogServiceImpl  extends EgovAbstractServiceImpl implements UserAccessLogService {
+
+    @Resource(name = "userAccessLogDAO")
+    private UserAccessLogDAO userAccessLogDAO;
+
+    @Resource(name = "egovUserAccessLogIdGnrService")
+    private EgovIdGnrService idGenService;
+    @Override
+    public void insertUserAccessLog(UserAccessLogVO userAccessLogVO) throws Exception {
+        userAccessLogVO.setLogId(idGenService.getNextStringId());
+        userAccessLogDAO.insertUserAccessLog(userAccessLogVO);
+    }
+}
src/main/java/itn/let/sym/mnu/mpm/service/EgovMenuManageService.java
--- src/main/java/itn/let/sym/mnu/mpm/service/EgovMenuManageService.java
+++ src/main/java/itn/let/sym/mnu/mpm/service/EgovMenuManageService.java
@@ -170,5 +170,5 @@
 
 	MenuManageVO selectMenuManageUrl(MenuManageVO tempMenuManageVO) throws Exception;
 
-
+	String selectAnonymousMenuNmByUrl(String requestUri) throws Exception;
 }
(No newline at end of file)
src/main/java/itn/let/sym/mnu/mpm/service/impl/EgovMenuManageServiceImpl.java
--- src/main/java/itn/let/sym/mnu/mpm/service/impl/EgovMenuManageServiceImpl.java
+++ src/main/java/itn/let/sym/mnu/mpm/service/impl/EgovMenuManageServiceImpl.java
@@ -67,6 +67,10 @@
 		return menuManageDAO.selectMenuManage(vo);
 	}
 
+	public String selectAnonymousMenuNmByUrl(String uri) throws Exception {
+		return menuManageDAO.selectAnonymousMenuNmByUrl(uri);
+	}
+
 	/**
 	 * 만족도 관리 여부를 위한 메뉴 상세정보를 조회
 	 * @param vo ComDefaultVO
src/main/java/itn/let/sym/mnu/mpm/service/impl/MenuManageDAO.java
--- src/main/java/itn/let/sym/mnu/mpm/service/impl/MenuManageDAO.java
+++ src/main/java/itn/let/sym/mnu/mpm/service/impl/MenuManageDAO.java
@@ -39,6 +39,9 @@
 	public List<?> selectMenuManageList(ComDefaultVO vo) throws Exception{
 		return list("menuManageDAO.selectMenuManageList_D", vo);
 	}
+	public String selectAnonymousMenuNmByUrl(String uri) {
+		return (String) select("menuManageDAO.selectAnonymousMenuNmByUrl", uri);
+	}
 	
 	/**
 	 * 만족도 여부를 위한 메뉴목록을 조회
@@ -248,4 +251,6 @@
 	public MenuManageVO selectMenuManageUrl(MenuManageVO menuManageVO) throws Exception{
 		return (MenuManageVO)select("menuManageDAO.selectMenuManageUrl", menuManageVO);
 	}
+
+
 }
(No newline at end of file)
src/main/java/itn/let/sym/site/service/SiteManagerVO.java
--- src/main/java/itn/let/sym/site/service/SiteManagerVO.java
+++ src/main/java/itn/let/sym/site/service/SiteManagerVO.java
@@ -64,10 +64,39 @@
 	private String sessionTimeUserCdTxt;  //세션타임 사용자 코드 텍스트
 	private String sysMaintenance;	//점검중페이지 노출여부
 	private String sysMaintenanceIp;	//점검중페이지제외IP
-	
+	private String operationStartTime;	//운영시간 시작 (HH:mm, DB 기본값 : 09:30)
+	private String operationEndTime;	//운영시간 종료 (HH:mm, DB 기본값 : 18:30)
+	private String lunchStartTime;	//점심시간 시작 (HH:mm, DB 기본값 : 13:00)
+	private String lunchEndTime;	//점심시간 종료 (HH:mm, DB 기본값 : 14:00)
+
 	public static long getSerialversionuid() {
 		return serialVersionUID;
 	}
+
+	public String getOperationStartTime() {
+		return operationStartTime;
+	}
+	public void setOperationStartTime(String operationStartTime) {
+		this.operationStartTime = operationStartTime;
+	}
+	public String getOperationEndTime() {
+		return operationEndTime;
+	}
+	public void setOperationEndTime(String operationEndTime) {
+		this.operationEndTime = operationEndTime;
+	}
+	public String getLunchStartTime() {
+		return lunchStartTime;
+	}
+	public void setLunchStartTime(String lunchStartTime) {
+		this.lunchStartTime = lunchStartTime;
+	}
+	public String getLunchEndTime() {
+		return lunchEndTime;
+	}
+	public void setLunchEndTime(String lunchEndTime) {
+		this.lunchEndTime = lunchEndTime;
+	}
 	
 	public String getSiteId() {
 		return siteId;
src/main/java/itn/let/uat/uia/service/EgovLoginService.java
--- src/main/java/itn/let/uat/uia/service/EgovLoginService.java
+++ src/main/java/itn/let/uat/uia/service/EgovLoginService.java
@@ -121,4 +121,7 @@
     //비밀번호 실패 횟수 조회
     public LoginVO selectPassMiss(LoginVO vo) throws Exception;
 
+	LoginVO findMemberByStatus(String id, String password, String userIp, String statusCode) throws Exception;
+
+	String validateLoginResult(LoginVO loginVO, LoginVO resultVO, String userIp) throws Exception ;
 }
 
src/main/java/itn/let/uat/uia/service/SecondAuthLogService.java (added)
+++ src/main/java/itn/let/uat/uia/service/SecondAuthLogService.java
@@ -0,0 +1,21 @@
+package itn.let.uat.uia.service;
+
+import java.util.List;
+
+public interface SecondAuthLogService {
+    public void insertSecondAuthsLog(SecondAuthLogVO logVO) throws Exception;
+
+    public void deleteSecondAuthLog(SecondAuthLogVO logVO) throws Exception;
+
+    public SecondAuthLogVO selectSecondAuthLog(SecondAuthLogVO logVO) throws Exception;
+
+    public int selectSecondAuthLogCnt(SecondAuthLogVO logVO) throws Exception;
+
+    public List<SecondAuthLogVO> selectSecondAuthLogList(SecondAuthLogVO logVO) throws Exception;
+
+    public int selectFailureCountSinceReset(SecondAuthLogVO logVO) throws Exception;
+
+    public boolean isSecondAuthLocked(SecondAuthLogVO logVO) throws Exception;
+
+    public int selectSecondAuthLockRemainingSeconds(SecondAuthLogVO logVO) throws Exception;
+}
 
src/main/java/itn/let/uat/uia/service/SecondAuthLogVO.java (added)
+++ src/main/java/itn/let/uat/uia/service/SecondAuthLogVO.java
@@ -0,0 +1,33 @@
+package itn.let.uat.uia.service;
+
+import itn.com.utl.page.BasePageableVO;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@NoArgsConstructor
+public class SecondAuthLogVO extends BasePageableVO {
+    // log PK
+    private String logId;
+    // SUCCESS, FAIL
+    private String loginResult;
+    // 1차, 2차
+    private String authStep;
+    // TRY Login ID
+    private String tryLoginId;
+    private String reason;
+    // IP, UserAgent
+    private String accessIp;
+    private String userAgent;
+
+    // 2차 인증
+    private String msgId;
+    // 입력한 2차 인증번호
+    private String resultCode;
+    // 전화번호
+    private String receive;
+
+    private String frstRegistPnttm;
+}
 
src/main/java/itn/let/uat/uia/service/SecondAuthMethodLogConstants.java (added)
+++ src/main/java/itn/let/uat/uia/service/SecondAuthMethodLogConstants.java
@@ -0,0 +1,15 @@
+package itn.let.uat.uia.service;
+
+public final class SecondAuthMethodLogConstants {
+
+    public static final String ACTION_REGISTER = "REGISTER";
+    public static final String ACTION_UPDATE = "UPDATE";
+    public static final String ACTION_DELETE = "DELETE";
+
+    public static final String VERIFY_SMS = "SMS";
+    public static final String VERIFY_KMC = "KMC";
+    public static final String VERIFY_INFO_CHANGE = "INFO_CHANGE";
+
+    private SecondAuthMethodLogConstants() {
+    }
+}
 
src/main/java/itn/let/uat/uia/service/SecondAuthMethodLogService.java (added)
+++ src/main/java/itn/let/uat/uia/service/SecondAuthMethodLogService.java
@@ -0,0 +1,13 @@
+package itn.let.uat.uia.service;
+
+import java.util.List;
+
+public interface SecondAuthMethodLogService {
+    void insertSecondAuthMethodLog(SecondAuthMethodLogVO logVO) throws Exception;
+
+    int selectSecondAuthMethodLogCnt(SecondAuthMethodLogVO logVO) throws Exception;
+
+    List<SecondAuthMethodLogVO> selectSecondAuthMethodLogList(SecondAuthMethodLogVO logVO) throws Exception;
+
+    int deleteExpiredSecondAuthMethodLogs() throws Exception;
+}
 
src/main/java/itn/let/uat/uia/service/SecondAuthMethodLogVO.java (added)
+++ src/main/java/itn/let/uat/uia/service/SecondAuthMethodLogVO.java
@@ -0,0 +1,20 @@
+package itn.let.uat.uia.service;
+
+
+import itn.com.utl.page.BasePageableVO;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@NoArgsConstructor
+public class SecondAuthMethodLogVO extends BasePageableVO {
+    private String logId;
+    private String mberId;
+    private String actionType;
+    private String phoneNo;
+    private String verifyType;
+    private String accessIp;
+    private String frstRegistPnttm;
+}
src/main/java/itn/let/uat/uia/service/impl/EgovLoginServiceImpl.java
--- src/main/java/itn/let/uat/uia/service/impl/EgovLoginServiceImpl.java
+++ src/main/java/itn/let/uat/uia/service/impl/EgovLoginServiceImpl.java
@@ -7,6 +7,8 @@
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
 
+import itn.com.uss.olh.hpc.service.HackIpService;
+import itn.com.uss.olh.hpc.service.HackIpVO;
 import org.springframework.stereotype.Service;
 import org.springframework.ui.ModelMap;
 
@@ -26,12 +28,10 @@
 
 /**
  * 일반 로그인, 인증서 로그인을 처리하는 비즈니스 구현 클래스
- * @author 공통서비스 개발팀 박지욱
- * @since 2009.03.06
- * @version 1.0
- * @see
  *
- * <pre>
+ * @author 공통서비스 개발팀 박지욱
+ * @version 1.0
+ * @see <pre>
  * << 개정이력(Modification Information) >>
  *
  *   수정일      수정자          수정내용
@@ -40,291 +40,345 @@
  *  2011.08.31  JJY            경량환경 템플릿 커스터마이징버전 생성
  *
  *  </pre>
+ * @since 2009.03.06
  */
 @Service("loginService")
 public class EgovLoginServiceImpl extends EgovAbstractServiceImpl implements
         EgovLoginService {
 
-    @Resource(name="loginDAO")
+    @Resource(name = "loginDAO")
     private LoginDAO loginDAO;
 
     @Resource(name = "egovMjonLoginLogIdGnrService")
-	private EgovIdGnrService loginLogIdService;
-    
-    ///** EgovSndngMailRegistService */
-	//@Resource(name = "sndngMailRegistService")
-    //private EgovSndngMailRegistService sndngMailRegistService;
+    private EgovIdGnrService loginLogIdService;
 
-    
+    @Resource(name = "hackIpService")
+    private HackIpService hackIpService;
+
     /**
-	 * 일반 로그인을 처리한다
-	 * @param vo LoginVO
-	 * @return LoginVO
-	 * @exception Exception
-	 */
+     * 일반 로그인을 처리한다
+     *
+     * @param vo LoginVO
+     * @return LoginVO
+     * @throws Exception
+     */
     @Override
-	public LoginVO actionLogin(LoginVO vo) throws Exception {
+    public LoginVO actionLogin(LoginVO vo) throws Exception {
 
-    	// 1. 입력한 비밀번호를 암호화한다.
-    	String enpassword = EgovFileScrty.encryptPassword(vo.getPassword(), vo.getId());
-    	vo.setPassword(enpassword);
+        // 1. 입력한 비밀번호를 암호화한다.
+        String enpassword = EgovFileScrty.encryptPassword(vo.getPassword(), vo.getId());
+        vo.setPassword(enpassword);
 
-    	// 2. 아이디와 암호화된 비밀번호가 DB와 일치하는지 확인한다.
-    	LoginVO loginVO = loginDAO.actionLogin(vo);  //사용자 MJ_MEMBER_ADD 추가
+        // 2. 아이디와 암호화된 비밀번호가 DB와 일치하는지 확인한다.
+        LoginVO loginVO = loginDAO.actionLogin(vo);  //사용자 MJ_MEMBER_ADD 추가
 
-    	// 3. 결과를 리턴한다.
-    	if (loginVO != null && !loginVO.getId().equals("") && !loginVO.getPassword().equals("")) {
-    		return loginVO;
-    	} else {
-    		loginVO = new LoginVO();
-    	}
+        // 3. 결과를 리턴한다.
+        if (loginVO != null && !loginVO.getId().equals("") && !loginVO.getPassword().equals("")) {
+            return loginVO;
+        } else {
+            loginVO = new LoginVO();
+        }
 
-    	return loginVO;
+        return loginVO;
     }
-    
-    
+
+
     //관리자 사용자 정보  팝업에서 해당 사용자로 로그인 처리하는 서비스
     @Override
-	public LoginVO actionLoginAdmToUsr(LoginVO vo) throws Exception {
+    public LoginVO actionLoginAdmToUsr(LoginVO vo) throws Exception {
 
-    	// 1. 관리자가 사용자 계정으로 로그인하는 것으로 암호화된 비밀번호를 디비에서 조회해서 로그인 처리를 하므로 암호화가 필요 없다.
-    	//String enpassword = EgovFileScrty.encryptPassword(vo.getPassword(), vo.getId());
-    	//vo.setPassword(enpassword);
+        // 1. 관리자가 사용자 계정으로 로그인하는 것으로 암호화된 비밀번호를 디비에서 조회해서 로그인 처리를 하므로 암호화가 필요 없다.
+        //String enpassword = EgovFileScrty.encryptPassword(vo.getPassword(), vo.getId());
+        //vo.setPassword(enpassword);
 
-    	// 2. 아이디와 암호화된 비밀번호가 DB와 일치하는지 확인한다.
-    	LoginVO loginVO = loginDAO.actionLogin(vo);  //사용자 MJ_MEMBER_ADD 추가
+        // 2. 아이디와 암호화된 비밀번호가 DB와 일치하는지 확인한다.
+        LoginVO loginVO = loginDAO.actionLogin(vo);  //사용자 MJ_MEMBER_ADD 추가
 
-    	// 3. 결과를 리턴한다.
-    	if (loginVO != null && !loginVO.getId().equals("") && !loginVO.getPassword().equals("")) {
-    		return loginVO;
-    	} else {
-    		loginVO = new LoginVO();
-    	}
+        // 3. 결과를 리턴한다.
+        if (loginVO != null && !loginVO.getId().equals("") && !loginVO.getPassword().equals("")) {
+            return loginVO;
+        } else {
+            loginVO = new LoginVO();
+        }
 
-    	return loginVO;
+        return loginVO;
     }
-    
+
     @Override
-	public LoginVO actionSnsLogin(LoginVO vo) throws Exception {
-    	// 2. 아이디와 암호화된 비밀번호가 DB와 일치하는지 확인한다.
-    	LoginVO loginVO = loginDAO.actionLogin(vo);
+    public LoginVO actionSnsLogin(LoginVO vo) throws Exception {
+        // 2. 아이디와 암호화된 비밀번호가 DB와 일치하는지 확인한다.
+        LoginVO loginVO = loginDAO.actionLogin(vo);
 
-    	// 3. 결과를 리턴한다.
-    	if (loginVO != null && !loginVO.getId().equals("") && !loginVO.getPassword().equals("")) {
-    		return loginVO;
-    	} else {
-    		loginVO = new LoginVO();
-    	}
+        // 3. 결과를 리턴한다.
+        if (loginVO != null && !loginVO.getId().equals("") && !loginVO.getPassword().equals("")) {
+            return loginVO;
+        } else {
+            loginVO = new LoginVO();
+        }
 
-    	return loginVO;
-	}
-    
+        return loginVO;
+    }
+
 
     /**
-   	 * 등록된 SNS 수량을 조회한다.
-   	 * @param vo LoginVO
-   	 * @return LoginVO
-   	 * @exception Exception
-   	 */
-	@Override
-	public int selectSnsIdCount(LoginVO vo) throws Exception {
-		return  loginDAO.selectSnsIdCount(vo);
-	}
-	
-	/**
-   	 * 등록된 SNS 아이티를 조회 한다.
-   	 * @param vo LoginVO
-   	 * @return LoginVO
-   	 * @exception Exception
-   	 */
-	@Override
-	public LoginVO selectSnsId(LoginVO vo) throws Exception {
-		return  loginDAO.selectSnsId(vo);
-	}
-	
-	@Override
-	public void updateSnsLink(LoginVO vo) throws Exception {
-		loginDAO.updateSnsLink(vo);
-	}
-
-    /**
-	 * 아이디를 찾는다.
-	 * @param vo LoginVO
-	 * @return LoginVO
-	 * @exception Exception
-	 */
+     * 등록된 SNS 수량을 조회한다.
+     *
+     * @param vo LoginVO
+     * @return LoginVO
+     * @throws Exception
+     */
     @Override
-	public LoginVO searchId(LoginVO vo) throws Exception {
-
-    	// 1. 이름, 이메일주소가 DB와 일치하는 사용자 ID를 조회한다.
-    	LoginVO loginVO = loginDAO.searchId(vo);
-
-    	// 2. 결과를 리턴한다.
-    	if (loginVO != null && !loginVO.getId().equals("")) {
-    		return loginVO;
-    	} else {
-    		loginVO = new LoginVO();
-    	}
-
-    	return loginVO;
+    public int selectSnsIdCount(LoginVO vo) throws Exception {
+        return loginDAO.selectSnsIdCount(vo);
     }
 
     /**
-	 * 비밀번호를 찾는다.
-	 * @param vo LoginVO
-	 * @return boolean
-	 * @exception Exception
-	 */
+     * 등록된 SNS 아이티를 조회 한다.
+     *
+     * @param vo LoginVO
+     * @return LoginVO
+     * @throws Exception
+     */
     @Override
-	public boolean searchPassword(LoginVO vo) throws Exception {
-
-    	boolean result = true;
-
-    	// 1. 아이디, 이름, 이메일주소, 비밀번호 힌트, 비밀번호 정답이 DB와 일치하는 사용자 Password를 조회한다.
-    	LoginVO loginVO = loginDAO.searchPassword(vo);
-    	if (loginVO == null || loginVO.getPassword() == null || loginVO.getPassword().equals("")) {
-    		return false;
-    	}
-
-    	// 2. 임시 비밀번호를 생성한다.(영+영+숫+영+영+숫=6자리)
-    	String newpassword = "";
-    	for (int i = 1; i <= 6; i++) {
-    		// 영자
-    		if (i % 3 != 0) {
-    			newpassword += EgovStringUtil.getRandomStr('a', 'z');
-    		// 숫자
-    		} else {
-    			newpassword += EgovNumberUtil.getRandomNum(0, 9);
-    		}
-    	}
-
-    	// 3. 임시 비밀번호를 암호화하여 DB에 저장한다.
-    	LoginVO pwVO = new LoginVO();
-    	String enpassword = EgovFileScrty.encryptPassword(newpassword, vo.getId());
-    	pwVO.setId(vo.getId());
-    	pwVO.setPassword(enpassword);
-    	pwVO.setUserSe(vo.getUserSe());
-    	loginDAO.updatePassword(pwVO);
-
-    	// 4. 임시 비밀번호를 이메일 발송한다.(메일연동솔루션 활용)
-    	//SndngMailVO sndngMailVO = new SndngMailVO();
-    	//sndngMailVO.setDsptchPerson("webmaster");
-    	//sndngMailVO.setRecptnPerson(vo.getEmail());
-    	//sndngMailVO.setSj("[MOPAS] 임시 비밀번호를 발송했습니다.");
-    	//sndngMailVO.setEmailCn("고객님의 임시 비밀번호는 " + newpassword + " 입니다.");
-    	//sndngMailVO.setAtchFileId("");
-
-    	//result = sndngMailRegistService.insertSndngMail(sndngMailVO);
-
-    	return result;
+    public LoginVO selectSnsId(LoginVO vo) throws Exception {
+        return loginDAO.selectSnsId(vo);
     }
 
-	@Override
-	public LoginVO getUserAuth(LoginVO resultVO) throws Exception {
-		return loginDAO.getUserAuth(resultVO);
-	}
+    @Override
+    public void updateSnsLink(LoginVO vo) throws Exception {
+        loginDAO.updateSnsLink(vo);
+    }
 
-	@Override
-	public LoginVO siteActionLogin(LoginVO vo) throws Exception {
-		// 1. 입력한 비밀번호를 암호화한다.
-    	String enpassword = EgovFileScrty.encryptPassword(vo.getPassword(), vo.getId());
-    	vo.setPassword(enpassword);
+    /**
+     * 아이디를 찾는다.
+     *
+     * @param vo LoginVO
+     * @return LoginVO
+     * @throws Exception
+     */
+    @Override
+    public LoginVO searchId(LoginVO vo) throws Exception {
 
-    	// 2. 아이디와 암호화된 비밀번호가 DB와 일치하는지 확인한다.
-    	LoginVO loginVO = loginDAO.siteActionLogin(vo);
+        // 1. 이름, 이메일주소가 DB와 일치하는 사용자 ID를 조회한다.
+        LoginVO loginVO = loginDAO.searchId(vo);
 
-    	// 3. 결과를 리턴한다.
-    	if (loginVO != null && !loginVO.getId().equals("") && !loginVO.getPassword().equals("")) {
-    		return loginVO;
-    	} else {
-    		loginVO = new LoginVO();
-    	}
+        // 2. 결과를 리턴한다.
+        if (loginVO != null && !loginVO.getId().equals("")) {
+            return loginVO;
+        } else {
+            loginVO = new LoginVO();
+        }
 
-    	return loginVO;
-	}
-	
+        return loginVO;
+    }
+
+    /**
+     * 비밀번호를 찾는다.
+     *
+     * @param vo LoginVO
+     * @return boolean
+     * @throws Exception
+     */
+    @Override
+    public boolean searchPassword(LoginVO vo) throws Exception {
+
+        boolean result = true;
+
+        // 1. 아이디, 이름, 이메일주소, 비밀번호 힌트, 비밀번호 정답이 DB와 일치하는 사용자 Password를 조회한다.
+        LoginVO loginVO = loginDAO.searchPassword(vo);
+        if (loginVO == null || loginVO.getPassword() == null || loginVO.getPassword().equals("")) {
+            return false;
+        }
+
+        // 2. 임시 비밀번호를 생성한다.(영+영+숫+영+영+숫=6자리)
+        String newpassword = "";
+        for (int i = 1; i <= 6; i++) {
+            // 영자
+            if (i % 3 != 0) {
+                newpassword += EgovStringUtil.getRandomStr('a', 'z');
+                // 숫자
+            } else {
+                newpassword += EgovNumberUtil.getRandomNum(0, 9);
+            }
+        }
+
+        // 3. 임시 비밀번호를 암호화하여 DB에 저장한다.
+        LoginVO pwVO = new LoginVO();
+        String enpassword = EgovFileScrty.encryptPassword(newpassword, vo.getId());
+        pwVO.setId(vo.getId());
+        pwVO.setPassword(enpassword);
+        pwVO.setUserSe(vo.getUserSe());
+        loginDAO.updatePassword(pwVO);
+
+        // 4. 임시 비밀번호를 이메일 발송한다.(메일연동솔루션 활용)
+        //SndngMailVO sndngMailVO = new SndngMailVO();
+        //sndngMailVO.setDsptchPerson("webmaster");
+        //sndngMailVO.setRecptnPerson(vo.getEmail());
+        //sndngMailVO.setSj("[MOPAS] 임시 비밀번호를 발송했습니다.");
+        //sndngMailVO.setEmailCn("고객님의 임시 비밀번호는 " + newpassword + " 입니다.");
+        //sndngMailVO.setAtchFileId("");
+
+        //result = sndngMailRegistService.insertSndngMail(sndngMailVO);
+
+        return result;
+    }
+
+    @Override
+    public LoginVO getUserAuth(LoginVO resultVO) throws Exception {
+        return loginDAO.getUserAuth(resultVO);
+    }
+
+    @Override
+    public LoginVO siteActionLogin(LoginVO vo) throws Exception {
+        // 1. 입력한 비밀번호를 암호화한다.
+        String enpassword = EgovFileScrty.encryptPassword(vo.getPassword(), vo.getId());
+        vo.setPassword(enpassword);
+
+        // 2. 아이디와 암호화된 비밀번호가 DB와 일치하는지 확인한다.
+        LoginVO loginVO = loginDAO.siteActionLogin(vo);
+
+        // 3. 결과를 리턴한다.
+        if (loginVO != null && !loginVO.getId().equals("") && !loginVO.getPassword().equals("")) {
+            return loginVO;
+        } else {
+            loginVO = new LoginVO();
+        }
+
+        return loginVO;
+    }
+
     /**
      * 유저 권한 목록을 조회한다. (by USER_ID)
+     *
      * @param vo LoginVO
      * @return List<EgovMap>
-     * @exception Exception
+     * @throws Exception
      */
     @Override
     public List<EgovMap> selectAuthorities(LoginVO vo) throws Exception {
-    	List<EgovMap> authorities = (List<EgovMap>) loginDAO.selectAuthorities(vo);
-    	
-    	return authorities;
+        List<EgovMap> authorities = (List<EgovMap>) loginDAO.selectAuthorities(vo);
+
+        return authorities;
     }
 
-	@Override
-	public void updatePassMissPlus(LoginVO loginVO) throws Exception {
-		 loginDAO.updatePassMissPlus(loginVO);
-	}
-
-	@Override
-	public void updatePassMissReset(LoginVO loginVO) throws Exception {
-		loginDAO.updatePassMissReset(loginVO);
-	}
-
-	@Override
-	public void updateEmplyrSttusCode(LoginVO loginVO) throws Exception {
-		loginDAO.updateEmplyrSttusCode(loginVO);
-	}
-
-	@Override
-	public void insertActionLoginLog(LoginVO loginVO) throws Exception {
-		String loginLogId = loginLogIdService.getNextStringId();
-		loginVO.setUniqId(loginLogId);
-		loginDAO.insertActionLoginLog(loginVO);
-	}
-	
-	@Override
-	public int selectIPCnt(LoginVO vo) throws Exception {
-		return  loginDAO.selectIPCnt(vo);
-	}
-	
-	@Override
-	public LoginVO selectKMCInfo(LoginVO vo) throws Exception {
-		return  loginDAO.selectKMCInfo(vo);
-	}
-	
-	@Override
-	public void insertMberIp(LoginVO loginVO) throws Exception {
-		loginDAO.insertMberIp(loginVO);
-	}
-	
     @Override
-	public LoginVO actionLoginCmpChk(LoginVO vo) throws Exception {
-
-    	// 1. 입력한 비밀번호를 암호화한다.
-    	String enpassword = EgovFileScrty.encryptPassword(vo.getPassword(), vo.getId());
-    	vo.setPassword(enpassword);
-
-    	// 2. 아이디와 암호화된 비밀번호가 DB와 일치하는지 확인한다.
-    	LoginVO loginVO = loginDAO.actionLoginCmpChk(vo);  //사용자 MJ_MEMBER_ADD 추가
-
-    	// 3. 결과를 리턴한다.
-    	if (loginVO != null && !loginVO.getId().equals("") && !loginVO.getPassword().equals("")) {
-    		return loginVO;
-    	} else {
-    		loginVO = new LoginVO();
-    	}
-
-    	return loginVO;
+    public void updatePassMissPlus(LoginVO loginVO) throws Exception {
+        loginDAO.updatePassMissPlus(loginVO);
     }
-    
+
+    @Override
+    public void updatePassMissReset(LoginVO loginVO) throws Exception {
+        loginDAO.updatePassMissReset(loginVO);
+    }
+
+    @Override
+    public void updateEmplyrSttusCode(LoginVO loginVO) throws Exception {
+        loginDAO.updateEmplyrSttusCode(loginVO);
+    }
+
+    @Override
+    public void insertActionLoginLog(LoginVO loginVO) throws Exception {
+        String loginLogId = loginLogIdService.getNextStringId();
+        loginVO.setUniqId(loginLogId);
+        loginDAO.insertActionLoginLog(loginVO);
+    }
+
+    @Override
+    public int selectIPCnt(LoginVO vo) throws Exception {
+        return loginDAO.selectIPCnt(vo);
+    }
+
+    @Override
+    public LoginVO selectKMCInfo(LoginVO vo) throws Exception {
+        return loginDAO.selectKMCInfo(vo);
+    }
+
+    @Override
+    public void insertMberIp(LoginVO loginVO) throws Exception {
+        loginDAO.insertMberIp(loginVO);
+    }
+
+    @Override
+    public LoginVO actionLoginCmpChk(LoginVO vo) throws Exception {
+
+        // 1. 입력한 비밀번호를 암호화한다.
+        String enpassword = EgovFileScrty.encryptPassword(vo.getPassword(), vo.getId());
+        vo.setPassword(enpassword);
+
+        // 2. 아이디와 암호화된 비밀번호가 DB와 일치하는지 확인한다.
+        LoginVO loginVO = loginDAO.actionLoginCmpChk(vo);  //사용자 MJ_MEMBER_ADD 추가
+
+        // 3. 결과를 리턴한다.
+        if (loginVO != null && !loginVO.getId().equals("") && !loginVO.getPassword().equals("")) {
+            return loginVO;
+        } else {
+            loginVO = new LoginVO();
+        }
+
+        return loginVO;
+    }
+
     //비밀번호 실패 횟수 조회
     @Override
     public LoginVO selectPassMiss(LoginVO vo) throws Exception {
-    	return loginDAO.selectPassMiss(vo);
+        return loginDAO.selectPassMiss(vo);
     }
 
-	public Boolean paramChk(String patn, String param) {
-		Pattern pattern = Pattern.compile(patn);
-		Matcher matcher = pattern.matcher(param);
-		boolean b = matcher.matches();
-		return b;
-	}
+    @Override
+    public LoginVO findMemberByStatus(String id, String password, String userIp, String statusCode) throws Exception {
+        LoginVO loginVO = new LoginVO();
+        loginVO.setId(id);
+        loginVO.setIp(userIp);
+        loginVO.setPassword(password);
+        loginVO.setUserSe("GNR");
+        loginVO.setStatusAll("Y");
+        loginVO.setEmplyrSttusCode(statusCode);
 
+        return loginDAO.actionLoginCmpChk(loginVO);
+    }
+
+    public Boolean paramChk(String patn, String param) {
+        Pattern pattern = Pattern.compile(patn);
+        Matcher matcher = pattern.matcher(param);
+        boolean b = matcher.matches();
+        return b;
+    }
+
+    public String validateLoginResult(
+            LoginVO loginVO,
+            LoginVO resultVO,
+            String userIp
+    ) throws Exception {
+        if ("admin".equals(loginVO.getId())) return null;
+
+        LoginVO passMissVO = selectPassMiss(loginVO);
+
+        if (passMissVO == null) {
+            insertHackIp(loginVO.getId(), userIp);
+            return "아이디 또는 비밀번호가 올바르지 않습니다.";
+        }
+
+        int passMiss = passMissVO.getPassMiss();
+        String lockedMessage = "5회 이상 로그인에 실패하여 계정이 잠겼습니다. 비밀번호를 재설정해주세요.";
+        if (passMiss >= 5) return lockedMessage;
+        if (resultVO == null || resultVO.getId() == null) {
+            updatePassMissPlus(loginVO);
+            insertHackIp(loginVO.getId(), userIp);
+            int failureCount = passMiss + 1;
+            if (failureCount >= 5) return lockedMessage;
+            return "아이디 또는 비밀번호가 올바르지 않습니다. (남은 시도 " + (5 - failureCount) + "회)";
+        }
+
+        updatePassMissReset(loginVO);
+        return null;
+    }
+
+    private void insertHackIp(String userId, String userIp) throws Exception {
+        HackIpVO vo = new HackIpVO();
+        vo.setHackUserId(userId);
+        vo.setAccessIp(userIp);
+
+        hackIpService.insertHackIp(vo);
+    }
 
 }
src/main/java/itn/let/uat/uia/service/impl/EgovMberManageServiceImpl.java
--- src/main/java/itn/let/uat/uia/service/impl/EgovMberManageServiceImpl.java
+++ src/main/java/itn/let/uat/uia/service/impl/EgovMberManageServiceImpl.java
@@ -659,7 +659,25 @@
 	
 		return headerInfo;
 	}
-	
+
+	@Override
+	public boolean isCompanyRegistrationPendingOrRejected(String mberId) throws Exception {
+		MberManageVO condition = new MberManageVO();
+		condition.setMberId(mberId);
+
+		MberManageVO history = selectCmpUsrHst(condition);
+
+		if (history == null) {
+			return false;
+		}
+
+		if (!"01".equals(history.getHstType())) {
+			return false;
+		}
+
+		return "01".equals(history.getHstSttus()) || "03".equals(history.getHstSttus());
+	}
+
 	private long calculateEventLeftDay(String eventEndDateStr) {
 		if (StringUtil.isEmpty(eventEndDateStr)) return 0;
 		LocalDate today = LocalDate.now();
 
src/main/java/itn/let/uat/uia/service/impl/SecondAuthLogDAO.java (added)
+++ src/main/java/itn/let/uat/uia/service/impl/SecondAuthLogDAO.java
@@ -0,0 +1,41 @@
+package itn.let.uat.uia.service.impl;
+
+import itn.com.cmm.service.impl.EgovComAbstractDAO;
+import itn.let.uat.uia.service.SecondAuthLogVO;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository("secondAuthLogDAO")
+public class SecondAuthLogDAO extends EgovComAbstractDAO {
+    public void insertSecondAuthLog(SecondAuthLogVO logVO) {
+        insert("secondAuthLogDAO.insertSecondAuthLog", logVO);
+    }
+
+    public void deleteSecondAuthLog(SecondAuthLogVO logVO) {
+    }
+
+    public SecondAuthLogVO selectSecondAuthLog(SecondAuthLogVO logVO) {
+        return (SecondAuthLogVO) select("secondAuthLogDAO.selectSecondAuthLog", logVO);
+    }
+
+    public int selectSecondAuthLogCnt(SecondAuthLogVO logVO) {
+        return (Integer) select("secondAuthLogDAO.selectSecondAuthLogCnt", logVO);
+    }
+    @SuppressWarnings("unchecked")
+    public List<SecondAuthLogVO> selectSecondAuthLogList(SecondAuthLogVO logVO) {
+        return (List<SecondAuthLogVO>) list("secondAuthLogDAO.selectSecondAuthLogList", logVO);
+    }
+
+    public int selectFailureCountSinceReset(SecondAuthLogVO logVO) {
+        return (Integer) select("secondAuthLogDAO.selectFailureCountSinceReset", logVO);
+    }
+
+    public int selectActiveSecondAuthLockCount(SecondAuthLogVO logVO) {
+        return (Integer) select("secondAuthLogDAO.selectActiveSecondAuthLockCount", logVO);
+    }
+
+    public int selectSecondAuthLockRemainingSeconds(SecondAuthLogVO logVO) {
+        return (Integer) select("secondAuthLogDAO.selectSecondAuthLockRemainingSeconds", logVO);
+    }
+}
 
src/main/java/itn/let/uat/uia/service/impl/SecondAuthLogServiceImpl.java (added)
+++ src/main/java/itn/let/uat/uia/service/impl/SecondAuthLogServiceImpl.java
@@ -0,0 +1,72 @@
+package itn.let.uat.uia.service.impl;
+
+import egovframework.rte.fdl.cmmn.EgovAbstractServiceImpl;
+import egovframework.rte.fdl.idgnr.EgovIdGnrService;
+import itn.let.uat.uia.service.SecondAuthLogService;
+import itn.let.uat.uia.service.SecondAuthLogVO;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+@Service("secondAuthLogService")
+public class SecondAuthLogServiceImpl extends EgovAbstractServiceImpl implements SecondAuthLogService {
+    @Resource(name = "secondAuthLogDAO")
+    private SecondAuthLogDAO dao;
+
+    @Resource(name = "egovSecondAuthLogIdGnrService")
+    private EgovIdGnrService idGenService;
+
+    public void insertSecondAuthsLog(SecondAuthLogVO logVO) throws Exception{
+        logVO.setLogId(idGenService.getNextStringId());
+        dao.insertSecondAuthLog(logVO);
+    }
+
+    public void deleteSecondAuthLog(SecondAuthLogVO logVO) throws Exception{
+        dao.deleteSecondAuthLog(logVO);
+    }
+
+    public SecondAuthLogVO selectSecondAuthLog(SecondAuthLogVO logVO) throws Exception{
+        return dao.selectSecondAuthLog(logVO);
+    }
+
+    public int selectSecondAuthLogCnt(SecondAuthLogVO logVO) throws Exception{
+        return dao.selectSecondAuthLogCnt(logVO);
+    }
+
+    public List<SecondAuthLogVO> selectSecondAuthLogList(SecondAuthLogVO logVO) throws Exception{
+        List<SecondAuthLogVO> list = dao.selectSecondAuthLogList(logVO);
+        list.forEach(log -> log.setUserAgent(getDeviceType(log.getUserAgent())));
+
+        return list;
+    }
+
+    public int selectFailureCountSinceReset(SecondAuthLogVO logVO) throws Exception {
+        return dao.selectFailureCountSinceReset(logVO);
+    }
+
+    public boolean isSecondAuthLocked(SecondAuthLogVO logVO) throws Exception {
+        return dao.selectActiveSecondAuthLockCount(logVO) > 0;
+    }
+
+    public int selectSecondAuthLockRemainingSeconds(SecondAuthLogVO logVO) throws Exception {
+        return dao.selectSecondAuthLockRemainingSeconds(logVO);
+    }
+
+    private String getDeviceType(String userAgent) {
+        if (userAgent == null) {
+            return "UNKNOWN";
+        }
+
+        String ua = userAgent.toLowerCase();
+
+        if (ua.contains("mobile")
+                || ua.contains("android")
+                || ua.contains("iphone")
+                || ua.contains("ipad")) {
+            return "MOBILE";
+        }
+
+        return "PC";
+    }
+}
 
src/main/java/itn/let/uat/uia/service/impl/SecondAuthMethodLogDAO.java (added)
+++ src/main/java/itn/let/uat/uia/service/impl/SecondAuthMethodLogDAO.java
@@ -0,0 +1,27 @@
+package itn.let.uat.uia.service.impl;
+
+import itn.com.cmm.service.impl.EgovComAbstractDAO;
+import itn.let.uat.uia.service.SecondAuthMethodLogVO;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository("secondAuthMethodLogDAO")
+public class SecondAuthMethodLogDAO extends EgovComAbstractDAO {
+    public void insertSecondAuthMethodLog(SecondAuthMethodLogVO logVO) {
+        insert("secondAuthMethodLogDAO.insertSecondAuthMethodLog", logVO);
+    }
+
+    public int selectSecondAuthMethodLogCnt(SecondAuthMethodLogVO logVO) {
+        return (Integer) select("secondAuthMethodLogDAO.selectSecondAuthMethodLogCnt", logVO);
+    }
+
+    @SuppressWarnings("unchecked")
+    public List<SecondAuthMethodLogVO> selectSecondAuthMethodLogList(SecondAuthMethodLogVO logVO) {
+        return (List<SecondAuthMethodLogVO>) list("secondAuthMethodLogDAO.selectSecondAuthMethodLogList", logVO);
+    }
+
+    public int deleteExpiredSecondAuthMethodLogs() {
+        return delete("secondAuthMethodLogDAO.deleteExpiredSecondAuthMethodLogs");
+    }
+}
 
src/main/java/itn/let/uat/uia/service/impl/SecondAuthMethodLogServiceImpl.java (added)
+++ src/main/java/itn/let/uat/uia/service/impl/SecondAuthMethodLogServiceImpl.java
@@ -0,0 +1,35 @@
+package itn.let.uat.uia.service.impl;
+
+import egovframework.rte.fdl.cmmn.EgovAbstractServiceImpl;
+import itn.let.uat.uia.service.SecondAuthMethodLogService;
+import itn.let.uat.uia.service.SecondAuthMethodLogVO;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+@Service("secondAuthMethodLogService")
+public class SecondAuthMethodLogServiceImpl extends EgovAbstractServiceImpl implements SecondAuthMethodLogService {
+    @Resource(name = "secondAuthMethodLogDAO")
+    private SecondAuthMethodLogDAO secondAuthMethodLogDAO;
+
+    @Override
+    public void insertSecondAuthMethodLog(SecondAuthMethodLogVO logVO) throws Exception {
+        secondAuthMethodLogDAO.insertSecondAuthMethodLog(logVO);
+    }
+
+    @Override
+    public int selectSecondAuthMethodLogCnt(SecondAuthMethodLogVO logVO) throws Exception {
+        return secondAuthMethodLogDAO.selectSecondAuthMethodLogCnt(logVO);
+    }
+
+    @Override
+    public List<SecondAuthMethodLogVO> selectSecondAuthMethodLogList(SecondAuthMethodLogVO logVO) throws Exception {
+        return secondAuthMethodLogDAO.selectSecondAuthMethodLogList(logVO);
+    }
+
+    @Override
+    public int deleteExpiredSecondAuthMethodLogs() throws Exception {
+        return secondAuthMethodLogDAO.deleteExpiredSecondAuthMethodLogs();
+    }
+}
 
src/main/java/itn/let/uat/uia/support/SecondAuthSessionConstants.java (added)
+++ src/main/java/itn/let/uat/uia/support/SecondAuthSessionConstants.java
@@ -0,0 +1,12 @@
+package itn.let.uat.uia.support;
+
+public final class SecondAuthSessionConstants {
+
+	public static final String SEND_COUNT = "secondAuthSendCount";
+	public static final String LAST_SENT_AT = "secondAuthLastSentAt";
+	public static final int MAX_SEND_COUNT = 4; // 최초 발송 1회 + 재발송 3회
+	public static final long RESEND_INTERVAL_MILLIS = 30 * 1000L;
+
+	private SecondAuthSessionConstants() {
+	}
+}
src/main/java/itn/let/uat/uia/web/EgovLoginController.java
--- src/main/java/itn/let/uat/uia/web/EgovLoginController.java
+++ src/main/java/itn/let/uat/uia/web/EgovLoginController.java
@@ -30,6 +30,7 @@
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
+import itn.let.uat.uia.service.*;
 import org.apache.commons.lang.StringUtils;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.http.HttpResponse;
@@ -68,7 +69,6 @@
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 import NiceID.Check.CPClient;
-import egovframework.rte.fdl.cmmn.trace.LeaveaTrace;
 import egovframework.rte.fdl.property.EgovPropertyService;
 import egovframework.rte.fdl.security.userdetails.util.EgovUserDetailsHelper;
 import itn.com.cmm.ComDefaultCodeVO;
@@ -89,6 +89,7 @@
 import itn.let.cert.ip.service.MberCertLoginLogVO;
 import itn.let.cert.phone.service.CertPhoneService;
 import itn.let.cert.phone.service.MberCertPhoneVO;
+import itn.let.cert.phone.support.PhoneResetSessionConstants;
 import itn.let.cop.bbs.service.BoardVO;
 import itn.let.mjo.event.service.MjonEventService;
 import itn.let.mjo.event.service.MjonEventVO;
@@ -104,7 +105,6 @@
 import itn.let.mjo.pay.service.MjonPayService;
 import itn.let.sym.grd.service.MberGrdService;
 import itn.let.sym.grd.service.MberGrdVO;
-import itn.let.sym.log.clg.service.EgovLoginLogService;
 import itn.let.sym.log.clg.service.LoginLog;
 import itn.let.sym.mnu.mpm.service.MenuManageJTreeVO;
 import itn.let.sym.site.service.EgovSiteManagerService;
@@ -113,8 +113,7 @@
 import itn.let.sym.site.service.TermsVO;
 import itn.let.uat.uap.service.EgovLoginPolicyService;
 import itn.let.uat.uap.service.LoginPolicyVO;
-import itn.let.uat.uia.service.AuthCertVO;
-import itn.let.uat.uia.service.EgovLoginService;
+import itn.let.uat.uia.support.SecondAuthSessionConstants;
 import itn.let.uss.umt.service.EgovMberManageService;
 import itn.let.uss.umt.service.EgovUserManageService;
 import itn.let.uss.umt.service.MberManageConfigVO;
@@ -122,15 +121,13 @@
 import itn.let.uss.umt.service.UserDefaultVO;
 import itn.let.uss.umt.service.UserGuestVO;
 import itn.let.uss.umt.service.UserManageVO;
-import itn.let.utl.fcc.service.EgovCryptoUtil;
-import itn.let.utl.fcc.service.EgovStringUtil;
 import itn.let.utl.sim.service.EgovClntInfo;
 import itn.let.utl.user.service.MjonNoticeSendUtil;
 import itn.let.utl.user.service.MjonSmsCertSendUtil;
 
 /**
  * 일반 로그인, 인증서 로그인을 처리하는 컨트롤러 클래스
- * 
+ *
  * @author 공통서비스 개발팀 박지욱
  * @since 2009.03.06
  * @version 1.0
@@ -170,6 +167,9 @@
 	@Resource(name = "loginService")
 	private EgovLoginService loginService;
 
+	@Resource(name = "secondAuthLogService")
+	private SecondAuthLogService secondAuthLogService;
+
 	/** EgovMessageSource */
 	@Resource(name = "egovMessageSource")
 	EgovMessageSource egovMessageSource;
@@ -181,13 +181,6 @@
 	/** EgovPropertyService */
 	@Resource(name = "propertiesService")
 	protected EgovPropertyService propertiesService;
-
-	/** TRACE */
-	@Resource(name = "leaveaTrace")
-	LeaveaTrace leaveaTrace;
-
-	@Resource(name = "EgovLoginLogService")
-	private EgovLoginLogService loginLogService;
 
 	@Resource(name = "egovSiteManagerService")
 	private EgovSiteManagerService egovSiteManagerService;
@@ -218,44 +211,41 @@
 	@Resource(name = "MjonMsgDataService")
 	private MjonMsgDataService mjonMsgDataService;
 
-	@Resource(name = "egovCryptoUtil")
-	EgovCryptoUtil egovCryptoUtil;
-	
 	@Resource(name = "MjonMsgSentService")
     private MjonMsgSentService mjonMsgSentService;
 
 	/** 알림전송 Util */
 	@Resource(name = "mjonNoticeSendUtil")
 	private MjonNoticeSendUtil mjonNoticeSendUtil;
-	
+
 	@Resource(name = "MjonSmsCertSendUtil")
 	private MjonSmsCertSendUtil mjonSmsCertSendUtil;
-	
+
 	@Resource(name="MjonCommon")
 	private MjonCommon mjonCommon;
 
 	@Resource(name = "MjonEventService")
     private MjonEventService mjonEventService;
-	
+
 	@Resource(name = "mjonPayService")
-	private MjonPayService mjonPayService;    
-	
+	private MjonPayService mjonPayService;
+
 	@Resource(name = "mberGrdService")
 	MberGrdService mberGrdService;
 
 	@Resource(name = "CertPhoneService")
 	private CertPhoneService certPhoneService;
-	
+
 	@Resource(name = "CertLoginLogService")
 	private CertLoginLogService certLoginLogService;
-	
+
 	@Resource(name = "CertIpService")
 	private CertIpService certIpService;
-	
+
 	@Resource(name = "KmcCertChecker")
 	private KmcCertChecker kmcCertChecker;
-	
-	
+
+
 	private static final Logger logger = LoggerFactory.getLogger(MjonMsgDataController.class);
 
 	/* 문자고 사용자 로그인 시작 */
@@ -266,16 +256,16 @@
 		Boolean isAuthenticated = EgovUserDetailsHelper.isAuthenticated();
 		if (isAuthenticated)
 			return "redirect:/";
-		
+
 		// B선라인
 		String blineCode = request.getParameter("blineCode");
 		if (blineCode != null && blineCode.length() != 0){
 		}
 		else {
-			blineCode = "N";			
+			blineCode = "N";
 		}
 		model.addAttribute("blineCode", blineCode);
-		
+
 		return "web/login/usrSelectDept";
 	}
 
@@ -285,8 +275,7 @@
 			@ModelAttribute("mberManageVO") MberManageVO mberManageVO, TermsVO termsVO, ModelMap model)
 			throws Exception {
 		Boolean isAuthenticated = EgovUserDetailsHelper.isAuthenticated();
-		if (isAuthenticated)
-			return "redirect:/";
+		if (isAuthenticated) return "redirect:/";
 		//URL을 이용한 비정상적인 접근 차단
 
 		if("".equals(mberManageVO.getDept()) || mberManageVO.getDept() == null) {
@@ -306,16 +295,16 @@
 		// 나이스 본인인증
 		/*
 		 * String referer = (String) request.getHeader("REFERER");
-		 * 
+		 *
 		 * URL newURL = new URL(referer);
-		 * 
+		 *
 		 * String protocol = newURL.getProtocol() + "://"; String host =
 		 * newURL.getHost();
-		 * 
+		 *
 		 * String succUrl = protocol + host + "/web/user/login/niceCheckAjax.do"; // 성공시
 		 * 이동할 URL - String failUrl = protocol + host +
 		 * "/web/user/login/selectUserDept.do";
-		 * 
+		 *
 		 * mberManageVO.setNiceSuccUrl(succUrl); mberManageVO.setNiceFailUrl(failUrl);
 		 */
 		model.addAttribute("mberManageVO", mberManageVO);
@@ -323,22 +312,10 @@
 		// KMC 본인인증 데이터 불러오기
 		// 핸드폰인증만 사용할 시
 		if (joinSettingVO.getJoinCertType().equals("M")) {
-
-//			KmcCertChecker kmcCertCheck = new KmcCertChecker();
-//			
-//			String serverNm = request.getScheme() + "://" + request.getServerName();
-//			if(request.getServerPort() != 80
-//				&& request.getServerPort() != 443) {
-//				serverNm += ":" + request.getServerPort();
-//			}
-//
-//			AuthCertVO certVO = kmcCertCheck.authCertCheck(serverNm + "/web/cop/kmc/authRequestAjax.do");
-//
-			
 			MberCertPhoneVO mberCertPhoneVO = new MberCertPhoneVO();
 			mberCertPhoneVO.setTrUrl("/web/cop/kmc/authRequestAjax.do");
 			AuthCertVO certVO = kmcCertChecker.authCertCheck(mberCertPhoneVO, request);
-			
+
 			model.addAttribute("tr_cert", certVO.getTr_cert());
 			model.addAttribute("tr_url", certVO.getTr_url());
 			model.addAttribute("tr_add", certVO.getTr_add());
@@ -350,10 +327,10 @@
 		if (blineCode != null && blineCode.length() != 0){
 		}
 		else {
-			blineCode = "N";			
+			blineCode = "N";
 		}
 		model.addAttribute("blineCode", blineCode);
-		
+
 		return "web/login/usrCheckTerms";
 	}
 
@@ -368,16 +345,6 @@
 		ModelAndView modelAndView = new ModelAndView();
 		modelAndView.setViewName("jsonView");
 
-//		KmcCertChecker kmcCertCheck = new KmcCertChecker();
-
-		// kmc 본인인증 실/개발 서버 구분
-//		String serverNm = "";
-//		if (request.getServerName().contains("219.240.88.15") || request.getServerName().contains("localhost")) {
-//			serverNm = request.getScheme() + "://219.240.88.15:8095";
-//		} else {
-//			serverNm = request.getScheme() + "://www.munjaon.co.kr";
-//		}
-		
 		MberCertPhoneVO mberCertPhoneVO = new MberCertPhoneVO();
 		mberCertPhoneVO.setTrUrl("/web/cop/kmc/authRequestAjax.do");
 		mberCertPhoneVO.setName(mberManageVO.getMberNm());
@@ -385,20 +352,12 @@
 		mberCertPhoneVO.setGender(mberManageVO.getSexdstnCode());
 		mberCertPhoneVO.setNation(mberManageVO.getNationality());
 		mberCertPhoneVO.setBirthDay(mberManageVO.getBirth());
-		
+
 		AuthCertVO certVO = kmcCertChecker.authCertCheck(mberCertPhoneVO, request);
 
-//		AuthCertVO certVO = kmcCertCheck.authCertCheckTwo(serverNm + "/web/cop/kmc/authRequestAjax.do",
-//				mberManageVO.getMberNm(), mberManageVO.getMoblphonNo(), mberManageVO.getSexdstnCode(),
-//				mberManageVO.getNationality(), mberManageVO.getBirth());
 
 		System.out.println("++++++++++++ 세션체크222 ::: " + (MberManageVO) session.getAttribute("mberSession"));
 
-		/*
-		 * model.addAttribute("tr_cert", certVO.getTr_cert());
-		 * model.addAttribute("tr_url", certVO.getTr_url());
-		 * model.addAttribute("tr_add", certVO.getTr_add());
-		 */
 		modelAndView.addObject("tr_cert", certVO.getTr_cert());
 		modelAndView.addObject("tr_url", certVO.getTr_url());
 		modelAndView.addObject("tr_add", certVO.getTr_add());
@@ -412,8 +371,7 @@
 			@ModelAttribute("mberManageVO") MberManageVO mberManageVO, TermsVO termsVO, ModelMap model,
 			HttpSession session, JoinSettingVO joinSettingVO, RedirectAttributes redirectAttributes) throws Exception {
 		Boolean isAuthenticated = EgovUserDetailsHelper.isAuthenticated();
-		if (isAuthenticated)
-			return "redirect:/cmm/main/mainPage.do";
+		if (isAuthenticated) return "redirect:/cmm/main/mainPage.do";
 
 		//URL을 이용한 비정상적인 접근 차단
 		if("".equals(mberManageVO.getDept()) || mberManageVO.getDept() == null  || "".equals(mberManageVO.getMblDn()) || mberManageVO.getMblDn() == null) {
@@ -444,26 +402,6 @@
 
 		if (joinType.equals("M") || joinType.equals("A")) {
 
-			// 크롬 SameSite정책 방지 - 도메인이 다른 타사로 이동 시 크롬 정책에 의해 세션 유실이 일어나는 경우가 있는데, 이를 방지하기 위해
-			// samesite 보안을 none처리
-			/*
-			 * response.setHeader("Set-Cookie",
-			 * "AuthKmcCertVO=AuthKmcCertVO; Secure; SameSite=None"); //KMC 문자 본인인증 세션 정보
-			 * 받아오기 AuthCertVO certKmcVO = (AuthCertVO)
-			 * session.getAttribute("AuthKmcCertVO");
-			 * 
-			 * if(certKmcVO != null) {
-			 * 
-			 * mberManageVO.setNiceNm(certKmcVO.getCertName()); //본인인증 이름
-			 * mberManageVO.setMoblphonNo(certKmcVO.getCertPhone()); //본인인증 핸드폰
-			 * mberManageVO.setMblDn(certKmcVO.getCertDi()); //본인인증 중복가입방지 번호
-			 * mberManageVO.setNationality(certKmcVO.getCertNation());
-			 * mberManageVO.setBirthDay(certKmcVO.getBirthDay());
-			 * mberManageVO.setSexdstnCode(certKmcVO.getSexdstnCode());
-			 * 
-			 * }
-			 */
-
 		} else {
 
 			// ARS 전화 본인인증 세션 정보 받아오기
@@ -477,18 +415,6 @@
 
 		}
 
-		// 나이스 본인인증을 했을때 세션처리 후 데이터 저장해주기.
-		/*
-		 * MberManageVO VO = new MberManageVO(); if (null !=
-		 * request.getSession().getAttribute("userSearchVO")) { // 세션체크 VO =
-		 * ((MberManageVO) request.getSession().getAttribute("userSearchVO"));
-		 * mberManageVO.setNiceNm(VO.getNiceNm()); // 이름
-		 * mberManageVO.setSexdstnCode(VO.getSexdstnCode()); // 성별
-		 * mberManageVO.setMoblphonNo(VO.getMoblphonNo()); // 핸드폰번호
-		 * mberManageVO.setBirthDay(VO.getBirthDay()); // 생년월일
-		 * mberManageVO.setMblDn(VO.getMblDn()); // 모바일 인증키
-		 * session.removeAttribute("userSearchVO"); }
-		 */
 		model.addAttribute("mberManageVO", mberManageVO);
 
 		// B선라인
@@ -1012,9 +938,7 @@
 	@RequestMapping(value = "/web/user/login/usrJoinComplete.do")
 	public String selectUserComplete(@ModelAttribute("mberManageVO") MberManageVO mberManageVO, ModelMap model,
 			HttpSession session) throws Exception {
-
-		LoginVO loginVo = (LoginVO) session.getAttribute("LoginVO");
-		model.addAttribute("mberManageVO", loginVo);
+		model.addAttribute("mberManageVO", mberManageVO);
 
 		return "web/login/usrInserCompl";
 	}
@@ -1029,21 +953,6 @@
 
 		return "web/login/usrInserMemShipCompl";
 	}
-
-	/*
-	*//**
-		 * (관리자)로그인 화면으로 들어간다
-		 * 
-		 * @param vo
-		 *            - 로그인후 이동할 URL이 담긴 LoginVO
-		 * @return 로그인 페이지
-		 * @exception Exception
-		 *//*
-			 * @RequestMapping(value = "/uat/uia/egovLoginUsr.do") public String
-			 * loginUsrView(@ModelAttribute("loginVO") LoginVO loginVO, HttpServletRequest
-			 * request, HttpServletResponse response, ModelMap model) throws Exception {
-			 * return "uat/uia/EgovLoginUsr"; }
-			 */
 
 	/**
 	 * (관리자) 로그인 화면.
@@ -1071,63 +980,21 @@
 	public String userLogin(HttpServletRequest request, HttpServletResponse response, ModelMap model,
 			@RequestParam Map<String, Object> commandMap, HttpSession session) throws Exception {
 		Boolean isAuthenticated = EgovUserDetailsHelper.isAuthenticated();
-		if (isAuthenticated)
-			return "redirect:/";
+		if (isAuthenticated) return "redirect:/";
 
-		// 로그인필요 URL 호출하여 온경우
 		String beforeUrl = (String) commandMap.get("beforeUrl");
 		String message = (String) commandMap.get("message");
 		String goEventPay = (String) commandMap.get("goEventPay");
-		
-		/*
-		 * 	헤더에서 id/pw는 맞지만 보안로그인 설정 되어있을때 -> secure
-		 * 
-		 */
 
 		String headerLoginResult = (String) commandMap.get("headerLoginResult");
 		String id = (String) commandMap.get("id");
 		String pw = (String) commandMap.get("password");
-		
-		// sns 회원가입 key 값 설정
-//		String naverClientId = itnNaverClientId;
-//		String naverClientSecret = itnNaverClientSecret;
-//
-//		String kakaoRestApiKey = itnKakaoRestApiKey;
-//		String kakaoReturnUrl = itnKakaoReturnUrl;
 
 		// config 정보 가져오기
 		MberManageConfigVO mberConfigVO = new MberManageConfigVO();
 
 		List<MberManageConfigVO> mberManageCnfVO = mberManageService.selectMberCnf(mberConfigVO);
 
-		/*for (int i = 0; i < mberManageCnfVO.size(); i++) {
-			if (mberManageCnfVO.get(i).getIsSns().equals("Y")) {
-				if (mberManageCnfVO.get(i).getUseYn().equals("Y")) {
-					if (mberManageCnfVO.get(i).getConfigId().equals("snsNaver")) {
-						String redirectURI = URLEncoder.encode(itnNaverReturnUrl, "UTF-8");
-						SecureRandom random = new SecureRandom();
-						BigInteger state = new BigInteger(130, random);
-						String naver_url = "https://nid.naver.com/oauth2.0/authorize?response_type=code";
-						naver_url += "&client_id=" + itnNaverClientId;
-						naver_url += "&redirect_url=" + redirectURI;
-						naver_url += "&state=" + state;
-						mberManageCnfVO.get(i).setSiteUrl(naver_url);
-						mberManageCnfVO.get(i).setSiteIcon("/img/login_naver_icon.png");
-
-					} else if (mberManageCnfVO.get(i).getConfigId().equals("snsKakao")) {
-
-						String kakao_url = "https://kauth.kakao.com/oauth/authorize?response_type=code";
-						kakao_url += "&client_id=" + kakaoRestApiKey;
-						kakao_url += "&redirect_uri=" + kakaoReturnUrl;
-						mberManageCnfVO.get(i).setSiteUrl(kakao_url);
-						mberManageCnfVO.get(i).setSiteIcon("/img/login_kakao_icon.png");
-
-					} else if (mberManageCnfVO.get(i).getConfigId().equals("snsGoogle")) {
-
-					}
-				}
-			}
-		}*/
 		model.addAttribute("mberManageCnfVO", mberManageCnfVO);
 		model.addAttribute("beforeUrl", beforeUrl);
 		model.addAttribute("message", message);
@@ -1135,16 +1002,8 @@
 
 		// 공인 IP 구하기
 		String userIp = EgovClntInfo.getClntIP(request);
-		System.out.println("userIp :: "+userIp);
-		// java.util.Scanner s = new java.util.Scanner(new
-		// java.net.URL("https://api.ipify.org").openStream(),
-		// "UTF-8").useDelimiter("\\A");
-		// userIp = s.next();
-
 		model.addAttribute("userIp", userIp);
 		model.addAttribute("goEventPay", goEventPay);
-		
-		
 		if(StringUtil.isNotEmpty(headerLoginResult)) {
 			model.addAttribute("id_secure", id); 
 			model.addAttribute("pw_secure", pw);
@@ -1154,7 +1013,7 @@
 		
 		model.addAttribute("headerLoginResult", headerLoginResult);
 
-		return "web/login/EgovLoginGnrlUsr";
+		return "web/login/login";
 	}
 
 	/**
@@ -1811,323 +1670,130 @@
 			return "redirect:/";
 		}
 	}
-	
-	/**
-	  * @Method Name	: selectUserSttusAjax
-	  * @작성일 			: 2024. 3. 27.
-	  * @작성자			: WYH
-	  * @변경이력			: 
-	  * @Method 설명 		: 회원상태 조회 (Y:일반회원, B:이용정지 회원, N:탈퇴회원) 
-	  */
-	/*
-	@RequestMapping(value = { "/web/user/login/actionSecurityLoginAjax.do" })
-	public ModelAndView actionSecurityLoginAjax(@ModelAttribute("loginVO") LoginVO loginVO, HttpServletResponse response,
-			HttpServletRequest request, @RequestParam Map<String, Object> commandMap, ModelMap model,
-			HttpSession session, RedirectAttributes redirectAttributes) throws Exception {
-		ModelAndView modelAndView = new ModelAndView();
-		modelAndView.setViewName("jsonView");
-		
-	    String p_id		=	"";
-	    String p_pwd	=	"";
-		
-		try {
-			//id, pwd 로그인 하고 결과와 hp 번호를 돌려주면 됨
-			p_id	=	request.getParameter("id");
-			p_pwd	=	request.getParameter("password");
-			
-			
-			
-			String userId = mjonMsgVO.getUserId();
-			String userSttus = userManageService.selectUserStatusInfoCheck(userId);
-			
-			modelAndView.addObject("status", "success");
-			modelAndView.addObject("userSttus", userSttus);
-			
-		} catch (Exception e) {
-			modelAndView.addObject("status", "fail");
-			modelAndView.addObject("msg", "인증에 실패하였습니다.");
-		}
-		
-		return modelAndView;
-	}
-	*/
-	
+
 	@RequestMapping(value = "/web/user/login/actionSecurityLoginBeforeHpAjax.do")
-	public ModelAndView selectActionSecurityLoginWebBeforeHp(@ModelAttribute("loginVO") LoginVO loginVO, HttpServletResponse response,
-			HttpServletRequest request, @RequestParam Map<String, Object> commandMap, ModelMap model,
-			HttpSession session, RedirectAttributes redirectAttributes) throws Exception {
-		
-		
-		
-		ModelAndView modelAndView = new ModelAndView();
-		modelAndView.setViewName("jsonView");
-
+	public ModelAndView selectActionSecurityLoginWebBeforeHp(
+			@ModelAttribute("loginVO") LoginVO loginVO,
+			HttpServletRequest request,
+			@RequestParam Map<String, Object> commandMap,
+			HttpSession session
+	) throws Exception {
 		String userIp = EgovClntInfo.getClntIP(request);
-		HackIpVO hackIpVO = new HackIpVO();
-		hackIpVO.setAccessIp(userIp);
-		hackIpVO.setBlockYn("Y");
-		if (null != hackIpService.selectHackIpVO(hackIpVO)) {
-			String message = "차단된 아이피입니다. 관리자에게 문의해 주십시요.";
-			
-			modelAndView.addObject("status", "fail");
-			modelAndView.addObject("msg", message);
-			return modelAndView;
-			
+
+		if (hackIpService.isBlockedIp(userIp)) {
+			return fail("차단된 아이피입니다. 관리자에게 문의해 주십시요.");
 		}
-
-		/*ScriptEngineManager engine = new ScriptEngineManager();*/
-
 		loginVO.setUserSe("USR");
-		// 로그인 정보 check
 
-		if (loginVO.getId() == null || loginVO.getPassword() == null) {
-			// 회원가입 시 로그인 처리
-			LoginVO pLoginVO = (LoginVO) request.getAttribute("pLoginVO");
-			if (pLoginVO == null) {
-				// RSA 키 생성
-				initRsa(request);
-				/* return "redirect:/web/user/login/login.do"; */
-				//return "redirect:/";
-				
-				String message = "로그인 실패했습니다.";
-				
-				modelAndView.addObject("status", "fail");
-				modelAndView.addObject("msg", message);
-				return modelAndView;
-				
-			} else {
-				loginVO.setId(pLoginVO.getId());
-				loginVO.setPassword(pLoginVO.getPassword());
-				loginVO.setBeforeUrl(pLoginVO.getBeforeUrl());
-				loginVO.setMessage(pLoginVO.getMessage());
-			}
+		if (!resolveLoginInfo(loginVO, request)) {
+			initRsa(request);
+			return fail("로그인 실패했습니다.");
 		}
 
-		// 1. 일반 로그인 처리
 		PrivateKey privateKey = (PrivateKey) session.getAttribute(EgovLoginController.RSA_WEB_KEY);
 		if (privateKey == null) {
 			initRsa(request);
-			/*return "web/site/login/EgovLoginGnrlUsr";*/
-			String message = "아이디와 비밀번호를 확인해주세요.";
-			redirectAttributes.addFlashAttribute("message", message);
-			System.out.println("======================================================");
-			System.out.println("=               RSA privateKey is null               =");
-			System.out.println("======================================================");
-						
-			modelAndView.addObject("status", "fail");
-			modelAndView.addObject("msg", message);
-			return modelAndView;
-			
+			return fail("아이디 또는 비밀번호가 올바르지 않습니다.");
 		}
-
-		LoginVO resultVO;
-		// 복호화
-		String id = "";
+		String id;
+		String password;
 		try {
-			//아이디 복호화 후 소문자 변환
 			id = decryptRsa(privateKey, loginVO.getId()).toLowerCase();
+			password = decryptRsa(privateKey, loginVO.getPassword());
 		} catch (Exception e) {
-			initRsa(request);
-
-			String message = "로그인 실패했습니다.";
-			
-			modelAndView.addObject("status", "fail");
-			modelAndView.addObject("msg", message);
-			return modelAndView;
-
+			id = loginVO.getId();
+			password = loginVO.getPassword();
 		}
-		String password = decryptRsa(privateKey, loginVO.getPassword());
 		loginVO.setId(id);
-		loginVO.setIp(EgovClntInfo.getClntIP(request));
 		loginVO.setPassword(password);
+		loginVO.setIp(userIp);
 		loginVO.setUserSe("GNR");
-		loginVO.setStatusAll("Y"); // 신청대기중인 사용자까지 조회
-		
-		//기업회원 가입중인지 체크 - 기존 로그인 select는 MBER_STTUS = 'Y'가 하드코딩이라 로그인 전 기업회원 가입중인지 선 체크
-		//암호화 2번 방지를 위한 LoginVO 생성
-		LoginVO loginCmpChkVO = new LoginVO();
-		loginCmpChkVO.setId(id);
-		loginCmpChkVO.setIp(EgovClntInfo.getClntIP(request));
-		loginCmpChkVO.setPassword(password);
-		loginCmpChkVO.setUserSe("GNR");
-		loginCmpChkVO.setStatusAll("Y"); // 신청대기중인 사용자까지 조회
-		loginCmpChkVO.setEmplyrSttusCode("A"); // 신청대기중인 사용자까지 조회
-		LoginVO cmpHstResultVO = loginService.actionLoginCmpChk(loginCmpChkVO);
-		if(null != cmpHstResultVO.getId()) {
-			//기업회원 정보 마지막 내역 조회
-			MberManageVO mberManageVO = new MberManageVO();
-			mberManageVO.setMberId(loginVO.getId());
-			MberManageVO cmpChangeManageVO = mberManageService.selectCmpUsrHst(mberManageVO);
-			if(null != cmpChangeManageVO) {
-				if("01".equals(cmpChangeManageVO.getHstType())) { //가장 마지막 내역이 기업회원 가입일 시
-					if("01".equals(cmpChangeManageVO.getHstSttus()) || "03".equals(cmpChangeManageVO.getHstSttus()) ) { //가장 마지막 기업회원 가입이 신청 또는 반려 상태일 시
+		loginVO.setStatusAll("Y");
 
-						String message = "기업회원 신청 또는 반려상태입니다.";
-						modelAndView.addObject("returnType", "B");
-						modelAndView.addObject("mberId", loginVO.getId());
-						modelAndView.addObject("status", "fail");
-						modelAndView.addObject("msg", message);
-						return modelAndView;
-					}
-				}
-			}
-		}
-		//스팸처리 회원인지 mj_spam_member_info 해당 ID의 최근 1개 데이터 조회
-		LoginVO loginSmiChkVO = new LoginVO();
-		loginSmiChkVO.setId(id);
-		loginSmiChkVO.setIp(EgovClntInfo.getClntIP(request));
-		loginSmiChkVO.setPassword(password);
-		loginSmiChkVO.setUserSe("GNR");
-		loginSmiChkVO.setStatusAll("Y");		// 신청대기중인 사용자까지 조회
-		loginSmiChkVO.setEmplyrSttusCode("B");	// 회원상태 B : 회원 탈퇴정보
-		LoginVO smiResultVO = loginService.actionLoginCmpChk(loginSmiChkVO);
-		if(null != smiResultVO.getId()) {
-			modelAndView.addObject("returnType", "C");
-			modelAndView.addObject("mberId", loginVO.getId());
-			modelAndView.addObject("status", "fail");
-			return modelAndView;
-		}
-		resultVO = loginService.actionLogin(loginVO);
-		LoginVO passMissVO = loginService.selectPassMiss(loginVO);
-
-		String alertMessage = "";
-		if (!"admin".equals(loginVO.getId())) {
-			//아이디가 존재
-			if(passMissVO != null) {
-				// 로그인 실패 (
-				if (null == resultVO.getId() && passMissVO.getPassMiss() < 5 ) {
-					loginService.updatePassMissPlus(loginVO);
-					alertMessage = egovMessageSource.getMessage("fail.common.login");
-	
-					// 로그인 실패시 해킹의심 IP 테이블에 등록_아이디는 있지만 비밀번호가 틀린상황
-					/// HackIpVO hackIpVO = new HackIpVO();
-					hackIpVO.setHackUserId(loginVO.getId());
-					hackIpVO.setAccessIp( EgovClntInfo.getClntIP(request));
-					hackIpService.insertHackIp(hackIpVO);
-					if (loginVO.getBeforeUrl().toString().contains("/web/user/login/login.do")) {
-						String message = "로그인 실패했습니다.";
-						
-						modelAndView.addObject("status", "fail");
-						modelAndView.addObject("msg", message);
-						return modelAndView;
-					}
-	
-					String message = "로그인 실패했습니다.";
-					
-					modelAndView.addObject("status", "fail");
-					modelAndView.addObject("msg", message);
-					return modelAndView;
-					
-				} else {
-					if (passMissVO.getPassMiss() >= 5) {
-						//alertMessage = "패스워드가 5회 이상 틀려 로그인 할수 없습니다. 비밀번호를 재설정해주세요.";
-						String message = "패스워드가 5회 이상 틀려 로그인 할수 없습니다. 비밀번호를 재설정해주세요.";
-						
-						modelAndView.addObject("status", "fail");
-						modelAndView.addObject("msg", message);
-						return modelAndView;
-					} /*
-						 * else if("A".equals(resultVO.getEmplyrSttusCode())){ //신청상태 alertMessage =
-						 * "신청하신 아이디가 현제 승인대기 상" + "태입니다."; return goUserLoginPage(redirectAttributes,
-						 * request, model, alertMessage); }else
-						 * if("B".equals(resultVO.getEmplyrSttusCode())){ //신청상태 alertMessage =
-						 * "아이디가 차단상태가 되어 사용할수 없습니다."; return goUserLoginPage(redirectAttributes,
-						 * request, model, alertMessage); }
-						 */
-					loginService.updatePassMissReset(loginVO);
-				}
-			}
-			//아이디 미존재
-			else {
-				alertMessage = egovMessageSource.getMessage("fail.common.login");
-				
-				// 로그인 실패시 해킹의심 IP 테이블에 등록_아이디없고 비밀번호도 틀린상황
-				/// HackIpVO hackIpVO = new HackIpVO();
-				hackIpVO.setHackUserId(loginVO.getId());
-				hackIpVO.setAccessIp(EgovClntInfo.getClntIP(request));
-				hackIpService.insertHackIp(hackIpVO);
-				if (loginVO.getBeforeUrl().toString().contains("/web/user/login/login.do")) {
-					String message = "로그인 실패했습니다.";
-					
-					modelAndView.addObject("status", "fail");
-					modelAndView.addObject("msg", message);
-					return modelAndView;
-				}
-
-				String message = "로그인 실패했습니다.";
-				
-				modelAndView.addObject("status", "fail");
-				modelAndView.addObject("msg", message);
-				return modelAndView;
-			}
+		LoginVO companyMember = loginService.findMemberByStatus(id, password, userIp, "A");
+		if (companyMember != null && companyMember.getId() != null && mberManageService.isCompanyRegistrationPendingOrRejected(id)) {
+			return fail("기업회원 신청 또는 반려상태입니다.", "B", id);
 		}
 
-		
-		/* 일시 : 2023.07.26
-		 * 내용 : 로그인시 휴먼고객 redirect 기능 추가
-		 * 작업자 : 원영현 과장 
-		 */
-		
-		if(resultVO.getDormantYn().equals("Y") || resultVO.getDormantYn() == "Y") {
-			
-			String message = "휴면계정입니다.";
-			modelAndView.addObject("returnType", "A");
-			modelAndView.addObject("status", "fail");
-			modelAndView.addObject("msg", message);
-			return modelAndView;
+		LoginVO spamMember = loginService.findMemberByStatus(id, password, userIp, "B");
+		if (spamMember != null && spamMember.getId() != null) {
+			return fail(null, "C", id);
 		}
-		
-		String message = "로그인 성공했습니다.";
-		
-		
-		
-		
 
-		/* 일시 : 2024.11.18
-		 * 내용 : 다중 인증번호 조회
-		 * 작업자 : 이호영
-		 */
-		
-
-		List<MberCertPhoneVO> mberCertPhoneList = certPhoneService.selectMberCertPhoneList(resultVO.getId());
-		
-		
-		modelAndView.addObject("status", "success");
-		modelAndView.addObject("msg", message);
-		modelAndView.addObject("data", resultVO);
-		modelAndView.addObject("mberCertPhoneList", mberCertPhoneList);
-		
-		
-
-
-		/* 일시 : 2024.11.21
-		 * 내용 : 패스 인증 조회
-		 * 작업자 : 이호영
-		 */
-		String userId = loginVO.getId();
-		// lettngnrlmber TB에 secuLoginFlag 값이 Y면 인증 해야함
-		String secuLoginFlag = mberManageService.getSecuLoginFlag(userId);
-		
-		String passFlag = "Y";
-		if("Y".equals(secuLoginFlag)) {
-			// lettngnrlmber TB에 secuLoginFlag 값이 Y면
-			// IP가 등록 되어 있는지 확인
-			// 있으면 2차인증 안함
-			// 없으면 2차인증 진행
-	    	MberCertIpVO mberCertIpVO = new MberCertIpVO();
-	    	mberCertIpVO.setCertIp(userIp);
-	    	mberCertIpVO.setMberId(userId);
-	    	// ip가 있으면 true
-	    	if(!certIpService.checkCertIp(mberCertIpVO)) {
-	    		passFlag = "N";
-	    	}
+		LoginVO resultVO = loginService.actionLogin(loginVO);
+		String loginFailure = loginService.validateLoginResult(loginVO, resultVO, userIp);
+		if(loginFailure != null) {
+			ModelAndView failure = fail(loginFailure);
+			failure.addObject("locked", loginFailure.startsWith("5회 이상 로그인에 실패"));
+			return failure;
 		}
-		modelAndView.addObject("passFlag", passFlag);
-		
-		
-		return modelAndView;
-		
 
+		if("Y".equals(resultVO.getDormantYn())) {
+			return fail("휴면계정입니다.", "A", null);
+		}
+
+		List<MberCertPhoneVO> phoneList = certPhoneService.selectMberCertPhoneList(resultVO.getId());
+
+		session.setAttribute(PhoneResetSessionConstants.PENDING_USER_ID, resultVO.getId());
+		session.setAttribute(PhoneResetSessionConstants.PENDING_AT, System.currentTimeMillis());
+		resetSecondAuthSendState(session);
+
+		ModelAndView mav = new ModelAndView("jsonView");
+
+		mav.addObject("status", "success");
+		mav.addObject("msg", "로그인 성공했습니다.");
+		mav.addObject("data", loginVO);
+		mav.addObject("mberCertPhoneList", phoneList);
+
+		return mav;
+	}
+
+	private ModelAndView fail(String message) {
+		return fail(message, null, null);
+	}
+
+	private ModelAndView fail(
+			String message,
+			String returnType,
+			String memberId
+	) {
+		ModelAndView mav = new ModelAndView("jsonView");
+		mav.addObject("status", "fail");
+		mav.addObject("msg", message);
+
+		if (returnType != null) {
+			mav.addObject("returnType", returnType);
+		}
+
+		if (memberId != null) {
+			mav.addObject("mberId", memberId);
+		}
+
+		return mav;
+	}
+
+	private boolean resolveLoginInfo(
+			LoginVO loginVO,
+			HttpServletRequest request
+	) {
+
+		if (loginVO.getId() != null && loginVO.getPassword() != null) {
+			return true;
+		}
+
+		LoginVO previousLogin =
+				(LoginVO) request.getAttribute("pLoginVO");
+
+		if (previousLogin == null) {
+			return false;
+		}
+
+		loginVO.setId(previousLogin.getId());
+		loginVO.setPassword(previousLogin.getPassword());
+		loginVO.setBeforeUrl(previousLogin.getBeforeUrl());
+		loginVO.setMessage(previousLogin.getMessage());
+
+		return true;
 	}
 	
 	@RequestMapping(value = "/web/user/login/actionSecurityLogin_ori_00.do")
@@ -2267,27 +1933,20 @@
 						redirectAttributes.addFlashAttribute("fail", true);
 						return "redirect:/web/user/login/login.do";
 					}
-	
+
 					return goUserLoginPage(redirectAttributes, loginVO, request, model, alertMessage);
 				} else {
 					if (passMissVO.getPassMiss() >= 5) {
 						alertMessage = "패스워드가 5회 이상 틀려 로그인 할수 없습니다. 비밀번호를 재설정해주세요.";
 						return goUserPassMissPage(redirectAttributes, loginVO, request, model, alertMessage);
-					} /*
-						 * else if("A".equals(resultVO.getEmplyrSttusCode())){ //신청상태 alertMessage =
-						 * "신청하신 아이디가 현제 승인대기 상" + "태입니다."; return goUserLoginPage(redirectAttributes,
-						 * request, model, alertMessage); }else
-						 * if("B".equals(resultVO.getEmplyrSttusCode())){ //신청상태 alertMessage =
-						 * "아이디가 차단상태가 되어 사용할수 없습니다."; return goUserLoginPage(redirectAttributes,
-						 * request, model, alertMessage); }
-						 */
+					}
 					loginService.updatePassMissReset(loginVO);
 				}
 			}
 			//아이디 미존재
 			else {
 				alertMessage = egovMessageSource.getMessage("fail.common.login");
-				
+
 				// 로그인 실패시 해킹의심 IP 테이블에 등록_아이디없고 비밀번호도 틀린상황
 				/// HackIpVO hackIpVO = new HackIpVO();
 				hackIpVO.setHackUserId(loginVO.getId());
@@ -2440,13 +2099,59 @@
 	}
 
 
-	
+
+	@RequestMapping(value = "/web/user/login/verifySecondAuthAjax.do", method = RequestMethod.POST)
+	public ModelAndView verifySecondAuthAjax(
+			@RequestParam("hp") String phone,
+			@RequestParam("num") String verificationNumber,
+			@RequestParam(value = "msgId", required = false) String msgId,
+			HttpServletRequest request,
+			HttpSession session) throws Exception {
+		ModelAndView mav = new ModelAndView("jsonView");
+		String userId = getPendingSecurityUserId(session);
+		if (StringUtils.isBlank(userId)) {
+			mav.addObject("status", "fail");
+			mav.addObject("message", "로그인 유효시간이 만료되었습니다. 다시 로그인해주세요.");
+			return mav;
+		}
+
+		SecondAuthLogVO secondAuthLogVO = createSecondAuthLogVO(userId, msgId, request);
+		if (secondAuthLogService.isSecondAuthLocked(secondAuthLogVO)) {
+			return secondAuthLockedResponse(mav, secondAuthLogVO);
+		}
+
+		if (!verificationNumber.matches("^\\d{6}$") || !isRegisteredSecurityPhone(userId, phone)) {
+			return secondAuthFailureResponse(mav, secondAuthLogVO, session);
+		}
+
+		SendLogVO sendLogVO = new SendLogVO();
+		sendLogVO.setSendId(userId);
+		sendLogVO.setReceive(phone);
+		sendLogVO.setCheckNo(verificationNumber);
+
+		if (mjonMsgDataService.selectSysMsgLogCheck(sendLogVO) == null) {
+			return secondAuthFailureResponse(mav, secondAuthLogVO, session);
+		}
+
+		mav.addObject("status", "success");
+		return mav;
+	}
+
 	@RequestMapping(value = "/web/user/login/actionSecurityLoginAfterHp.do")
 	public String selectActionSecurityLoginWebAfterHp(@ModelAttribute("loginVO") LoginVO loginVO, HttpServletResponse response,
 			HttpServletRequest request, @RequestParam Map<String, Object> commandMap, ModelMap model,
 			HttpSession session, RedirectAttributes redirectAttributes) throws Exception {
-				
 		String userIp = EgovClntInfo.getClntIP(request);
+		String s_hp	= request.getParameter("hp");
+		String s_num = request.getParameter("num");
+		String msgId = request.getParameter("msgId");
+
+		SecondAuthLogVO secondAuthLogVO = new SecondAuthLogVO();
+		secondAuthLogVO.setAccessIp(userIp);
+		secondAuthLogVO.setUserAgent(request.getHeader("User-Agent"));
+		secondAuthLogVO.setAuthStep("SECONDARY");
+		secondAuthLogVO.setMsgId(msgId);
+
 		HackIpVO hackIpVO = new HackIpVO();
 		hackIpVO.setAccessIp(userIp);
 		hackIpVO.setBlockYn("Y");
@@ -2455,19 +2160,12 @@
 			redirectAttributes.addFlashAttribute("message", message);
 			return "redirect:/";
 		}
-
-		/*ScriptEngineManager engine = new ScriptEngineManager();*/
-
 		loginVO.setUserSe("USR");
-		// 로그인 정보 check
 
 		if (loginVO.getId() == null || loginVO.getPassword() == null) {
-			// 회원가입 시 로그인 처리
 			LoginVO pLoginVO = (LoginVO) request.getAttribute("pLoginVO");
 			if (pLoginVO == null) {
-				// RSA 키 생성
 				initRsa(request);
-				/* return "redirect:/web/user/login/login.do"; */
 				return "redirect:/";
 			} else {
 				loginVO.setId(pLoginVO.getId());
@@ -2476,12 +2174,11 @@
 				loginVO.setMessage(pLoginVO.getMessage());
 			}
 		}
-		
+
 		// 1. 일반 로그인 처리
 		PrivateKey privateKey = (PrivateKey) session.getAttribute(EgovLoginController.RSA_WEB_KEY);
 		if (privateKey == null) {
 			initRsa(request);
-			/*return "web/site/login/EgovLoginGnrlUsr";*/
 			String message = "아이디와 비밀번호를 확인해주세요.";
 			redirectAttributes.addFlashAttribute("message", message);
 			System.out.println("======================================================");
@@ -2494,11 +2191,10 @@
 		// 복호화
 		String id = "";
 		try {
-			//아이디 복호화 후 소문자 변환
 			id = decryptRsa(privateKey, loginVO.getId()).toLowerCase();
+			secondAuthLogVO.setTryLoginId(id);
 		} catch (Exception e) {
 			initRsa(request);
-			// return "redirect:/web/user/login/login.do";
 			if (loginVO.getBeforeUrl().toString().contains("/web/user/login/login.do")) {
 				redirectAttributes.addFlashAttribute("fail", true);
 				return "redirect:/web/user/login/login.do";
@@ -2506,48 +2202,39 @@
 			return "redirect:/";
 
 		}
-		
-		
+		String pendingUserId = getPendingSecurityUserId(session);
+		if (StringUtils.isBlank(pendingUserId) || !pendingUserId.equals(id)) {
+			redirectAttributes.addFlashAttribute("message", "로그인 유효시간이 만료되었습니다. 다시 로그인해주세요.");
+			return "redirect:/web/user/login/login.do";
+		}
+		if (secondAuthLogService.isSecondAuthLocked(secondAuthLogVO)) {
+			redirectAttributes.addFlashAttribute("message",
+					"인증 시도 5회 이상 실패하여 5분간 로그인이 제한됩니다. 잠시 후 다시 시도해주세요.");
+			return "redirect:/web/user/login/login.do";
+		}
 		//핸드폰 인증번호 확인
-		String s_hp	=	request.getParameter("hp");
-		String s_num	=	request.getParameter("num");
-		
-		System.out.println(s_hp);
-		System.out.println(s_num);
-		System.out.println("s_num==============");
-		
 		SendLogVO sendLogVO = new SendLogVO();
 		sendLogVO.setSendId(id);
 		sendLogVO.setReceive(s_hp);
 		sendLogVO.setCheckNo(s_num);
-		
-		String passFlag	=	request.getParameter("passFlag");
-		System.out.println("passFlag : "+ passFlag);
-		System.out.println("passFlag : "+ passFlag);
-		System.out.println("passFlag : "+ passFlag);
-		System.out.println("passFlag : "+ passFlag);
-		System.out.println("passFlag : "+ passFlag.toString());
-		System.out.println("StringUtils.isNotEmpty(passFlag) : "+ StringUtils.isNotEmpty(passFlag));
-		
-		if(!"Y".equals(passFlag)) {
-		
-			sendLogVO = mjonMsgDataService.selectSysMsgLogCheck(sendLogVO);
-			
-			if (sendLogVO==null) {
-				redirectAttributes.addFlashAttribute("fail2", true);
-				return "redirect:/web/user/login/login.do";
-			}		
-		
+
+		sendLogVO = mjonMsgDataService.selectSysMsgLogCheck(sendLogVO);
+		if (sendLogVO==null) {
+			recordSecondAuthFailure(secondAuthLogVO);
+			redirectAttributes.addFlashAttribute("fail2", true);
+			return "redirect:/web/user/login/login.do";
 		}
-		System.out.println("========================= : "+ passFlag);
-		
+		session.removeAttribute(PhoneResetSessionConstants.PENDING_USER_ID);
+		session.removeAttribute(PhoneResetSessionConstants.PENDING_AT);
+		resetSecondAuthSendState(session);
+
 		String password = decryptRsa(privateKey, loginVO.getPassword());
 		loginVO.setId(id);
 		loginVO.setIp(EgovClntInfo.getClntIP(request));
 		loginVO.setPassword(password);
 		loginVO.setUserSe("GNR");
 		loginVO.setStatusAll("Y"); // 신청대기중인 사용자까지 조회
-		
+
 		//기업회원 가입중인지 체크 - 기존 로그인 select는 MBER_STTUS = 'Y'가 하드코딩이라 로그인 전 기업회원 가입중인지 선 체크
 		//암호화 2번 방지를 위한 LoginVO 생성
 		LoginVO loginCmpChkVO = new LoginVO();
@@ -2567,8 +2254,6 @@
 				if("01".equals(cmpChangeManageVO.getHstType())) { //가장 마지막 내역이 기업회원 가입일 시
 					if("01".equals(cmpChangeManageVO.getHstSttus()) || "03".equals(cmpChangeManageVO.getHstSttus()) ) { //가장 마지막 기업회원 가입이 신청 또는 반려 상태일 시
 						redirectAttributes.addFlashAttribute("mberId", loginVO.getId());
-						//addFlashAttribute 일회성으로 인하여 membershipAttachDocBefore.do 이동 후
-						//membershipAttachDoc.do로 바로 submit하여 새로고침 시 정보 유지되게 끔 처리
 						return "redirect:/web/user/login/membershipAttachDocBefore.do";
 					}
 				}
@@ -2603,9 +2288,7 @@
 				if (null == resultVO.getId() && passMissVO.getPassMiss() < 5 ) {
 					loginService.updatePassMissPlus(loginVO);
 					alertMessage = egovMessageSource.getMessage("fail.common.login");
-	
-					// 로그인 실패시 해킹의심 IP 테이블에 등록_아이디는 있지만 비밀번호가 틀린상황
-					/// HackIpVO hackIpVO = new HackIpVO();
+
 					hackIpVO.setHackUserId(loginVO.getId());
 					hackIpVO.setAccessIp( EgovClntInfo.getClntIP(request));
 					hackIpService.insertHackIp(hackIpVO);
@@ -2613,29 +2296,20 @@
 						redirectAttributes.addFlashAttribute("fail", true);
 						return "redirect:/web/user/login/login.do";
 					}
-	
+
 					return goUserLoginPage(redirectAttributes, loginVO, request, model, alertMessage);
 				} else {
 					if (passMissVO.getPassMiss() >= 5) {
 						alertMessage = "패스워드가 5회 이상 틀려 로그인 할수 없습니다. 비밀번호를 재설정해주세요.";
 						return goUserPassMissPage(redirectAttributes, loginVO, request, model, alertMessage);
-					} /*
-						 * else if("A".equals(resultVO.getEmplyrSttusCode())){ //신청상태 alertMessage =
-						 * "신청하신 아이디가 현제 승인대기 상" + "태입니다."; return goUserLoginPage(redirectAttributes,
-						 * request, model, alertMessage); }else
-						 * if("B".equals(resultVO.getEmplyrSttusCode())){ //신청상태 alertMessage =
-						 * "아이디가 차단상태가 되어 사용할수 없습니다."; return goUserLoginPage(redirectAttributes,
-						 * request, model, alertMessage); }
-						 */
+					}
 					loginService.updatePassMissReset(loginVO);
 				}
 			}
 			//아이디 미존재
 			else {
 				alertMessage = egovMessageSource.getMessage("fail.common.login");
-				
-				// 로그인 실패시 해킹의심 IP 테이블에 등록_아이디없고 비밀번호도 틀린상황
-				/// HackIpVO hackIpVO = new HackIpVO();
+
 				hackIpVO.setHackUserId(loginVO.getId());
 				hackIpVO.setAccessIp(EgovClntInfo.getClntIP(request));
 				hackIpService.insertHackIp(hackIpVO);
@@ -2643,30 +2317,15 @@
 					redirectAttributes.addFlashAttribute("fail", true);
 					return "redirect:/web/user/login/login.do";
 				}
-
 				return goUserLoginPage(redirectAttributes, loginVO, request, model, alertMessage);
 			}
 		}
 
-		
-		/* 일시 : 2023.07.26
-		 * 내용 : 로그인시 휴먼고객 redirect 기능 추가
-		 * 작업자 : 원영현 과장 
-		 */
-		
 		if(resultVO.getDormantYn().equals("Y") || resultVO.getDormantYn() == "Y") {
-			
 			return "redirect:/web/user/humanPage.do";
 		}
-		
-		
-		
-		
+
 		boolean loginPolicyYn = true;
-
-		// 접속IP
-		// userIp = EgovClntInfo.getClntIP(request);
-
 		// resultVO.siteId 의 각 로그인 ip를 조회함
 		if (resultVO != null && resultVO.getId() != null && !resultVO.getId().equals("") && loginPolicyYn) {
 			// 세션에다 ip 등록
@@ -2676,7 +2335,7 @@
 			UsernamePasswordAuthenticationFilter springSecurity = null;
 			ApplicationContext act = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getSession().getServletContext());
 			Map<String, UsernamePasswordAuthenticationFilter> beans = act.getBeansOfType(UsernamePasswordAuthenticationFilter.class);
-			
+
 			if (beans.size() > 0) {
 				springSecurity = (UsernamePasswordAuthenticationFilter) beans.values().toArray()[0];
 				springSecurity.setUsernameParameter("egov_security_username");
@@ -2709,36 +2368,16 @@
 						httpSession.setAttribute("siteManageList", siteManageList);
 						httpSession.setAttribute("loginId", user.getId()); // 자동완성 공통으로 쓰기위해
 						httpSession.setAttribute("esntlId", user.getPassword());
-
-						// 최근검색어 조회 후 세션에 넣어줌
-						/*
-						 * UserManageVO tempUserManageVO = new UserManageVO();
-						 * tempUserManageVO.setEmplyrId(user.getId()); tempUserManageVO =
-						 * userManageService.selectRecentSearchWord(tempUserManageVO);
-						 * 
-						 * List<String> lisRecentSearch = new ArrayList<String>(); if(null !=
-						 * tempUserManageVO){ //ArrayList 로 세션 저장
-						 * Collections.addAll(lisRecentSearch,tempUserManageVO.getSearchWord().split(","
-						 * )) ; } httpSession.setAttribute("recentSearch", lisRecentSearch) ;
-						 */
 					}
 				}
-				/*
-				 * loginLog.setLoginId(uniqId); loginLog.setLoginIp(ip);
-				 * loginLog.setLoginMthd("I"); // 로그인:I, 로그아웃:O loginLog.setErrOccrrAt("N");
-				 * loginLog.setErrorCode(""); //로그인은 관리자, PC만 됨 loginLog.setUserAt("U"); //사용자
-				 * loginLog.setDeviceType("P"); //PC loginLog.setProgrmFileNm("adminLogin");
-				 * loginLog.setMenuNm("관리자로그인"); loginLogService.logInsertLoginLog(loginLog);
-				 */
 
 				loginVO.getId();
 				loginVO.getIp();
 				loginVO.setMessage("로그인 성공되었습니다.");
 				loginVO.setLoginYn("Y");
-				
+
 				loginService.insertActionLoginLog(loginVO);
-				// 인증 로그인 이력을 위한 insert
-				
+
 				MberCertLoginLogVO mberCertLoginLogVO = new MberCertLoginLogVO();
 				mberCertLoginLogVO.setLoginId(loginVO.getId());
 				mberCertLoginLogVO.setLoginIp(loginVO.getIp());
@@ -2751,9 +2390,9 @@
 			mberGrdVO.setMberId(loginVO.getId());
 	    	mberGrdVO.setAmt("0");
 	    	mberGrdVO.setMoid("");
-	   		mberGrdService.mberGrdSaveByUserNoHist(mberGrdVO);    		
+	   		mberGrdService.mberGrdSaveByUserNoHist(mberGrdVO);
 			// End
-	   		
+
 			// 이벤트 결제 바로가기 로직 체크 Start
 			String goEventPay = (String) commandMap.get("goEventPay");
 			System.out.println("goEventPay : " + goEventPay);
@@ -2764,7 +2403,7 @@
 				}
 			}
 			// End
-			
+
 			String beforeUrl = (String) commandMap.get("beforeUrl");
 
 			// 로그인 전 URL 호출 시
@@ -2776,14 +2415,13 @@
 				}
 			}
 
-			// 개인키 삭제
-			// 성공시 웹페이지 분기로 인해 session에 페이지 타입을 넣어준다.
 			request.getSession().setAttribute("pageType", "web");
-			
-			return "blank"; // 성공 시 페이지.. (redirect 불가)
 
-			// uat/uia/actionMain.do 실제 성공시 무조건 좌측 메소드로 감.
-			// return "forward:/web/main/mainPage.do";
+			secondAuthLogVO.setReason("인증번호 일치");
+			secondAuthLogVO.setResultCode(s_num);
+			secondAuthLogVO.setLoginResult("SUCCESS");
+			secondAuthLogService.insertSecondAuthsLog(secondAuthLogVO);
+			return "blank";
 		} else {
 			String message = egovMessageSource.getMessage("fail.common.login");
 			redirectAttributes.addFlashAttribute("beforeUrl", loginVO.getBeforeUrl());
@@ -2791,7 +2429,104 @@
 			return "redirect:/";
 		}
 	}
-	
+
+	private String getPendingSecurityUserId(HttpSession session) {
+		Object userId = session.getAttribute(PhoneResetSessionConstants.PENDING_USER_ID);
+		Object pendingAt = session.getAttribute(PhoneResetSessionConstants.PENDING_AT);
+		if (!(userId instanceof String) || !(pendingAt instanceof Long)) {
+			return null;
+		}
+		if (System.currentTimeMillis() - (Long) pendingAt > PhoneResetSessionConstants.TIMEOUT_MILLIS) {
+			session.removeAttribute(PhoneResetSessionConstants.PENDING_USER_ID);
+			session.removeAttribute(PhoneResetSessionConstants.PENDING_AT);
+			resetSecondAuthSendState(session);
+			return null;
+		}
+		return (String) userId;
+	}
+
+	private boolean isRegisteredSecurityPhone(String userId, String phone) throws Exception {
+		if (StringUtils.isBlank(phone)) {
+			return false;
+		}
+		List<MberCertPhoneVO> phoneList = certPhoneService.selectMberCertPhoneList(userId);
+		for (MberCertPhoneVO certPhone : phoneList) {
+			if (phone.equals(certPhone.getMbtlnum())) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	private SecondAuthLogVO createSecondAuthLogVO(
+			String userId, String msgId, HttpServletRequest request) throws Exception {
+		SecondAuthLogVO logVO = new SecondAuthLogVO();
+		logVO.setTryLoginId(userId);
+		logVO.setMsgId(msgId);
+		logVO.setAccessIp(EgovClntInfo.getClntIP(request));
+		logVO.setUserAgent(request.getHeader("User-Agent"));
+		logVO.setAuthStep("SECONDARY");
+		return logVO;
+	}
+
+	private int recordSecondAuthFailure(SecondAuthLogVO logVO) throws Exception {
+		logVO.setReason("인증번호 불일치");
+		logVO.setLoginResult("FAIL");
+		logVO.setResultCode("INVALID");
+		secondAuthLogService.insertSecondAuthsLog(logVO);
+
+		int failureCount = secondAuthLogService.selectFailureCountSinceReset(logVO);
+		if (failureCount >= 5) {
+			logVO.setReason("SECOND_AUTH_LOCK");
+			logVO.setLoginResult("FAIL");
+			logVO.setResultCode("LOCKED");
+			secondAuthLogService.insertSecondAuthsLog(logVO);
+		}
+		return failureCount;
+	}
+
+	private ModelAndView secondAuthFailureResponse(
+			ModelAndView mav, SecondAuthLogVO logVO, HttpSession session) throws Exception {
+		int failureCount = recordSecondAuthFailure(logVO);
+		if (failureCount >= 5) {
+			resetSecondAuthSendState(session);
+			return secondAuthLockedResponse(mav, logVO);
+		}
+		mav.addObject("status", "fail");
+		mav.addObject("remaining", 5 - failureCount);
+		mav.addObject("message", "인증번호가 일치하지 않습니다. 5회 실패 시 5분간 로그인이 제한됩니다. (남은 시도 "
+				+ (5 - failureCount) + "회)");
+		return mav;
+	}
+
+	private ModelAndView secondAuthLockedResponse(ModelAndView mav, SecondAuthLogVO logVO) throws Exception {
+		mav.addObject("status", "locked");
+		mav.addObject("locked", true);
+		mav.addObject("lockSeconds", Math.max(1,
+				secondAuthLogService.selectSecondAuthLockRemainingSeconds(logVO)));
+		mav.addObject("message", "인증 시도 5회 이상 실패하여 5분간 로그인이 제한됩니다. 잠시 후 다시 시도해주세요.");
+		return mav;
+	}
+
+	private void resetSecondAuthSendState(HttpSession session) {
+		session.removeAttribute(SecondAuthSessionConstants.SEND_COUNT);
+		session.removeAttribute(SecondAuthSessionConstants.LAST_SENT_AT);
+	}
+
+	private int getSecondAuthSendCount(HttpSession session) {
+		Object sendCount = session.getAttribute(SecondAuthSessionConstants.SEND_COUNT);
+		return sendCount instanceof Integer ? (Integer) sendCount : 0;
+	}
+
+	private long getSecondAuthResendRemainingMillis(HttpSession session) {
+		Object lastSentAt = session.getAttribute(SecondAuthSessionConstants.LAST_SENT_AT);
+		if (!(lastSentAt instanceof Long)) {
+			return 0;
+		}
+		long elapsed = System.currentTimeMillis() - (Long) lastSentAt;
+		return Math.max(0, SecondAuthSessionConstants.RESEND_INTERVAL_MILLIS - elapsed);
+	}
+
 	// 이벤트 결제 바로가기
 	public boolean goEventPay(LoginVO loginVO) throws Exception {
 		boolean goEventPay = false;
@@ -2801,16 +2536,16 @@
 		String bbsId = "BBSMSTR_000000000731";	//이벤트 게시판 번호
 		String nttSj = "[첫결제 이벤트] 첫 결제 시, 누구나 조건 없이 인생 최저가!";
 		BoardVO resultEvent = selectEventBoardInfo(bbsId, nttSj);
-		
+
 		if(resultEvent != null) {//이벤트 게시판의 이벤트 게시글 번호 셋팅
 			mjonEventVO.setEventNttId(Float.toString(resultEvent.getNttId()));
-		
+
 			// 회원 이름 조회
 			MberManageVO mberManageVO = new MberManageVO();
 			mberManageVO.setMberId(loginVO.getId());
 			String mberNm = mberManageService.selectMberNmByMberId(mberManageVO);
 			if (mberNm != null) {
-				loginVO.setName(mberNm);	
+				loginVO.setName(mberNm);
 			}
 
 			// Step 0. B선 전송사 이용고객 => 이벤트대상 제외처리
@@ -2844,24 +2579,24 @@
 				}
 			}
 		}
-		
+
 		return goEventPay;
 	}
-	
+
     //이벤트 게시글 정보 불러오기 함수
     public BoardVO selectEventBoardInfo(String bbsId, String nttSj) throws Exception{
-    	
+
 		BoardVO boardVO = new BoardVO();
 		boardVO.setBbsId(bbsId);
 		boardVO.setNttSj(nttSj);
 		BoardVO resultEvent = mjonEventService.selectEventInfoByNttSj(boardVO);
-		
+
 		return resultEvent;
     }
-    
+
     //이벤트 회원 정보 불러오기 함수
     public MjonEventVO selectEventMsgMberDefaultInfo(MjonEventVO mjonEventVO, float nttId, String bbsId, String mberId, String mberNm) throws Exception{
-    	
+
     	mjonEventVO.setEventNttId(Float.toString(nttId));
 		mjonEventVO.setEventBbsId(bbsId);
 		mjonEventVO.setMberId(mberId);
@@ -2909,59 +2644,11 @@
 			return "redirect:/";
 		}
 
-		/*
-		 * ScriptEngineManager engine = new ScriptEngineManager(); if
-		 * (loginVO.getSnsSite() != null) { int snsIdCnt =
-		 * loginService.selectSnsIdCount(loginVO); LoginVO snsInfo =
-		 * loginService.selectSnsId(loginVO); if (snsIdCnt == 0) { LoginVO snsLoginVO =
-		 * new LoginVO(); snsLoginVO.setSnsSite(loginVO.getSnsSite());
-		 * snsLoginVO.setSnsId(loginVO.getSnsId());
-		 * snsLoginVO.setName(loginVO.getName());
-		 * snsLoginVO.setSnsEmail(loginVO.getSnsEmail());
-		 * 
-		 * if (null != snsInfo) { snsLoginVO.setId(snsInfo.getId());
-		 * redirectAttributes.addFlashAttribute("loginVO", snsLoginVO); return
-		 * "redirect:/web/user/join/snsLink.do"; } else {
-		 * 
-		 * redirectAttributes.addFlashAttribute("loginVO", snsLoginVO); return
-		 * "redirect:/web/user/join/usrJoinIntro.do"; } } else {
-		 * loginVO.setId(snsInfo.getId()); loginVO.setPassword(snsInfo.getPassword()); }
-		 * 
-		 * }
-		 */
-
 		loginVO.setUserSe("USR");
-		// 로그인 정보 check
-
-		/*
-		 * if (loginVO.getId() == null || loginVO.getPassword() == null) { // 회원가입 시 로그인
-		 * 처리 LoginVO pLoginVO = (LoginVO) request.getAttribute("pLoginVO"); if
-		 * (pLoginVO == null) { // RSA 키 생성 initRsa(request); return
-		 * "redirect:/web/user/login/login.do"; return "redirect:/"; } else {
-		 * loginVO.setId(pLoginVO.getId()); loginVO.setPassword(pLoginVO.getPassword());
-		 * loginVO.setBeforeUrl(pLoginVO.getBeforeUrl());
-		 * loginVO.setMessage(pLoginVO.getMessage()); } }
-		 */
-
-		/*
-		 * // 1. 일반 로그인 처리 PrivateKey privateKey = (PrivateKey)
-		 * session.getAttribute(EgovLoginController.RSA_WEB_KEY); if (privateKey ==
-		 * null) { initRsa(request); return "web/site/login/EgovLoginGnrlUsr"; }
-		 */
 		LoginVO resultVO;
 		if (loginVO.getSnsSite() == null) {
 			// 복호화
 			String id = "";
-			/*
-			 * try { id = decryptRsa(privateKey, loginVO.getId()); } catch (Exception e) {
-			 * initRsa(request); //return "redirect:/web/user/login/login.do";
-			 * if(loginVO.getBeforeUrl().toString().contains("/web/user/login/login.do")) {
-			 * redirectAttributes.addFlashAttribute("fail", true); return
-			 * "redirect:/web/user/login/login.do"; } return "redirect:/";
-			 * 
-			 * }
-			 */
-			// String password = decryptRsa(privateKey, loginVO.getPassword());
 			loginVO.setId(loginVO.getId());
 			loginVO.setIp(EgovClntInfo.getClntIP(request));
 			loginVO.setPassword(loginVO.getPassword());
@@ -2991,14 +2678,7 @@
 					if (resultVO.getPassMiss() > 5) {
 						alertMessage = "패스워드가 5회 이상 틀려 로그인 할수 없습니다. 관리자에게 문의하시기 바랍니다.";
 						return goUserLoginPage(redirectAttributes, loginVO, request, model, alertMessage);
-					} /*
-						 * else if("A".equals(resultVO.getEmplyrSttusCode())){ //신청상태 alertMessage =
-						 * "신청하신 아이디가 현제 승인대기 상" + "태입니다."; return goUserLoginPage(redirectAttributes,
-						 * request, model, alertMessage); }else
-						 * if("B".equals(resultVO.getEmplyrSttusCode())){ //신청상태 alertMessage =
-						 * "아이디가 차단상태가 되어 사용할수 없습니다."; return goUserLoginPage(redirectAttributes,
-						 * request, model, alertMessage); }
-						 */
+					}
 					loginService.updatePassMissReset(loginVO);
 				}
 			}
@@ -3009,10 +2689,6 @@
 
 		boolean loginPolicyYn = true;
 
-		// 접속IP
-		// userIp = EgovClntInfo.getClntIP(request);
-
-		// resultVO.siteId 의 각 로그인 ip를 조회함
 		if (resultVO != null && resultVO.getId() != null && !resultVO.getId().equals("") && loginPolicyYn) {
 			// 세션에다 ip 등록
 			resultVO.setIp(userIp);
@@ -3191,34 +2867,6 @@
 			@RequestParam Map<String, Object> commandMap, ModelMap model, RedirectAttributes redirectAttributes)
 			throws Exception {
 
-		/*
-		 * // 해킹 의심 IP 차단상태의 경우 로그인 불가 String userIp = EgovClntInfo.getClntIP(request);
-		 * HackIpVO hackIpVO = new HackIpVO(); hackIpVO.setAccessIp(userIp);
-		 * hackIpVO.setBlockYn("Y"); if (null != hackIpService.selectHackIpVO(hackIpVO))
-		 * { String message = "차단된 아이피입니다. 관리자에게 문의해 주십시요.";
-		 * redirectAttributes.addFlashAttribute("message", message); return
-		 * "redirect:/"; }
-		 * 
-		 * ScriptEngineManager engine = new ScriptEngineManager(); if
-		 * (loginVO.getSnsSite() != null) { int snsIdCnt =
-		 * loginService.selectSnsIdCount(loginVO); LoginVO snsInfo =
-		 * loginService.selectSnsId(loginVO); if (snsIdCnt == 0) { LoginVO snsLoginVO =
-		 * new LoginVO(); snsLoginVO.setSnsSite(loginVO.getSnsSite());
-		 * snsLoginVO.setSnsId(loginVO.getSnsId());
-		 * snsLoginVO.setName(loginVO.getName());
-		 * snsLoginVO.setSnsEmail(loginVO.getSnsEmail());
-		 * 
-		 * if (null != snsInfo) { snsLoginVO.setId(snsInfo.getId());
-		 * redirectAttributes.addFlashAttribute("loginVO", snsLoginVO); return
-		 * "redirect:/web/user/join/snsLink.do"; } else {
-		 * 
-		 * redirectAttributes.addFlashAttribute("loginVO", snsLoginVO); return
-		 * "redirect:/web/user/join/usrJoinIntro.do"; } } else {
-		 * loginVO.setId(snsInfo.getId()); loginVO.setPassword(snsInfo.getPassword()); }
-		 * 
-		 * }
-		 */
-
     	String adminId = request.getParameter("adminId");
     	
 		String mberId = request.getParameter("mberId");
@@ -3258,6 +2906,7 @@
 			resultVO = loginService.actionLoginAdmToUsr(usrLoginVO);
 
 			String alertMessage = "";
+
 			if (!"admin".equals(usrLoginVO.getId())) {
 				if (null == resultVO.getId()) { // 로그인 실패
 					/* loginService.updatePassMissPlus(loginVO); */
@@ -3280,14 +2929,7 @@
 					if (resultVO.getPassMiss() > 5) {
 						alertMessage = "패스워드가 5회 이상 틀려 로그인 할수 없습니다. 관리자에게 문의하시기 바랍니다.";
 						return goUserLoginPage(redirectAttributes, usrLoginVO, request, model, alertMessage);
-					} /*
-						 * else if("A".equals(resultVO.getEmplyrSttusCode())){ //신청상태 alertMessage =
-						 * "신청하신 아이디가 현제 승인대기 상" + "태입니다."; return goUserLoginPage(redirectAttributes,
-						 * request, model, alertMessage); }else
-						 * if("B".equals(resultVO.getEmplyrSttusCode())){ //신청상태 alertMessage =
-						 * "아이디가 차단상태가 되어 사용할수 없습니다."; return goUserLoginPage(redirectAttributes,
-						 * request, model, alertMessage); }
-						 */
+					}
 					loginService.updatePassMissReset(usrLoginVO);
 				}
 			}
@@ -3658,37 +3300,56 @@
 	 */
 	@RequestMapping(value = { "/web/user/sendSysMsgDataAjax.do" })
 	public ModelAndView sendSysMsgDataAjax(@ModelAttribute("searchVO") MjonMsgVO mjonMsgVO,
-			RedirectAttributes redirectAttributes, ModelMap model, HttpServletRequest request) throws Exception {
+			RedirectAttributes redirectAttributes, ModelMap model, HttpServletRequest request,
+			HttpSession session,
+			@RequestParam(value = "securityLogin", required = false) String securityLogin) throws Exception {
 
 		ModelAndView modelAndView = new ModelAndView();
 		modelAndView.setViewName("jsonView");
+		boolean secondAuthRequest = "Y".equals(securityLogin);
+		if (secondAuthRequest) {
+			String pendingUserId = getPendingSecurityUserId(session);
+			if (StringUtils.isBlank(pendingUserId)
+					|| !pendingUserId.equals(mjonMsgVO.getUserId())
+					|| !isRegisteredSecurityPhone(pendingUserId, mjonMsgVO.getCallTo())) {
+				modelAndView.addObject("result", "fail");
+				modelAndView.addObject("message", "로그인 정보를 다시 확인해주세요.");
+				return modelAndView;
+			}
+
+			SecondAuthLogVO lockCheck = new SecondAuthLogVO();
+			lockCheck.setTryLoginId(pendingUserId);
+			if (secondAuthLogService.isSecondAuthLocked(lockCheck)) {
+				modelAndView.addObject("result", "fail");
+				modelAndView.addObject("locked", true);
+				modelAndView.addObject("lockSeconds", Math.max(1,
+						secondAuthLogService.selectSecondAuthLockRemainingSeconds(lockCheck)));
+				modelAndView.addObject("message", "인증 시도 5회 이상 실패하여 5분간 로그인이 제한됩니다. 잠시 후 다시 시도해주세요.");
+				return modelAndView;
+			}
+
+			int sendCount = getSecondAuthSendCount(session);
+			if (sendCount >= SecondAuthSessionConstants.MAX_SEND_COUNT) {
+				modelAndView.addObject("result", "fail");
+				modelAndView.addObject("message", "인증번호 재발송 가능 횟수를 초과했습니다.");
+				return modelAndView;
+			}
+			long remainingMillis = getSecondAuthResendRemainingMillis(session);
+			if (remainingMillis > 0) {
+				modelAndView.addObject("result", "fail");
+				modelAndView.addObject("message", "인증번호 재발송은 30초마다 가능합니다.");
+				modelAndView.addObject("retryAfter", (remainingMillis + 999) / 1000);
+				return modelAndView;
+			}
+		}
 
 		String resultSts = "0";
 		String resultBlockSts = "0";
-
-		// 문자 수신 번호 jsp에서 받아와야함!
-		/* mjonMsgVO.setCallTo(callTo); */
-
-		// System.out.println("mjonMsgVO.toString()");
-		// System.out.println(mjonMsgVO.toString());
-		// System.out.println("mjonMsgVO.toString()");
-
-		// 사용자 유효성 검사
-		// id, hp, nm
+		String resultMsg = null;
 		try {
-			
-
 			List<MberCertPhoneVO> mberCertPhoneList = certPhoneService.selectMberCertPhoneList(mjonMsgVO.getUserId());
-			
-			
-			
-			
-
 			boolean TorF = mberCertPhoneList.size() > 0 ? true : false;
-
-			
 			if (!TorF) {
-
 				modelAndView.addObject("message", "입력하신 아이디/이름/휴대폰 번호에 맞는 회원 정보가 없습니다.");
 				modelAndView.addObject("result", "fail-check");
 				modelAndView.addObject("resultSts", "0");
@@ -3703,10 +3364,9 @@
 			userManageVO.setEmplyrNm(mjonMsgVO.getSearchNm());
 			userManageVO.setMoblphonNo(mjonMsgVO.getCallTo());
 
-
 			List<UserManageVO> usedNmList = userManageService.selectUserIdAjax2(userManageVO);
 			if(!usedNmList.get(0).getMberSttus().equals("Y")) {
-				
+
 				String mberSttus = usedNmList.get(0).getMberSttus();
 				String message = "";
 
@@ -3719,32 +3379,23 @@
 				} else {
 					message = "회원 상태를 확인할 수 없습니다.";
 				}
-				
+
 				modelAndView.addObject("message", message);
 				modelAndView.addObject("result", "fail-check");
 				modelAndView.addObject("resultSts", "0");
 				modelAndView.addObject("resultBlockSts", "0");
 				return modelAndView;
 			}
-			
 		} catch (Exception e) {
-
 			logger.error(e.getMessage(), e);
-			// modelAndView.addObject("result", "fail");
-			// modelAndView.addObject("message", "기타 시스템 오류 : " + e.getMessage());
-
 		}
 
 		try {
-			
-			
-			
-			
 			MjonMsgReturnVO returnVO = mjonMsgDataService.sendSysMsgData(mjonMsgVO, request);
 
 			resultSts = returnVO.getSendMsgCnt();
 			resultBlockSts = returnVO.getSendMsgBlockCnt();
-
+			resultMsg = returnVO.getMsgGroupId();
 
 		} catch (Exception e) {
 
@@ -3755,11 +3406,19 @@
 		}
 
 		if (!resultSts.equals("0")) {
+			if (secondAuthRequest) {
+				int sendCount = getSecondAuthSendCount(session) + 1;
+				session.setAttribute(SecondAuthSessionConstants.SEND_COUNT, sendCount);
+				session.setAttribute(SecondAuthSessionConstants.LAST_SENT_AT, System.currentTimeMillis());
+				modelAndView.addObject("resendRemaining",
+						SecondAuthSessionConstants.MAX_SEND_COUNT - sendCount);
+			}
 
 			modelAndView.addObject("message", "문자 전송이 완료되었습니다.");
 			modelAndView.addObject("result", "success");
 			modelAndView.addObject("resultSts", resultSts);
 			modelAndView.addObject("resultBlockSts", resultBlockSts);
+			modelAndView.addObject("resultMsgId", resultMsg);
 
 		} else {
 
@@ -3772,10 +3431,10 @@
 
 		return modelAndView;
 	}
-	
+
 	/**
 	 * 이용정치 회원 주소록 다운로드 본인인증 시스템 문자발송
-	 * 
+	 *
 	 * @param searchVO
 	 * @param model
 	 * @return ""
@@ -3784,13 +3443,13 @@
 	@RequestMapping(value = { "/web/user/sendAddrRequestMsgDataAjax.do" })
 	public ModelAndView sendAddrRequestMsgDataAjax(@ModelAttribute("searchVO") MjonMsgVO mjonMsgVO,
 			RedirectAttributes redirectAttributes, ModelMap model) throws Exception {
-		
+
 		ModelAndView modelAndView = new ModelAndView();
 		modelAndView.setViewName("jsonView");
-		
+
 		String resultSts = "0";
 		String resultBlockSts = "0";
-		
+
 		// 비밀번호 찾기 문자내용
 		String contents = "주소록 요청 인증번호입니다. 인증번호는 : %code% 입니다.";
 		String code = "";
@@ -3802,18 +3461,18 @@
 			code = randomCode.append(Integer.toString(random.nextInt(10))).toString();
 		}
 		contents = contents.replace("%code%", code);
-		
+
 		mjonMsgVO.setSmsTxt(contents);
 		mjonMsgVO.setReserveYn("N");
 		// 시스템 문자발송 번호
 		mjonMsgVO.setCallFrom("15518011");
-		
+
 		try {
-			
+
 			mjonMsgVO.setUserId("system");
-			
+
 			String msgType = "4";
-			
+
 			// 전송사 전송 배분
 			MjonMsgVO pramVO = new MjonMsgVO();
 			List<MjonMsgVO> sendRateList = mjonMsgService.selectTodayAgentSendCntList(pramVO); // 전송사 전송 배분률 및 현재 전송건수
@@ -3823,7 +3482,7 @@
 				float totCnt = tmpVO.getTotCnt();
 				tmpVO.setSendRateInfo(Float.parseFloat(tmpVO.getSendRate()) - (cnt / totCnt * 100)); // 배분률 - 현재전송 비율
 			}
-			
+
 			String sendAgent = sendRateList.get(0).getAgentCode();
 			float maxRate = sendRateList.get(0).getSendRateInfo();
 			for (MjonMsgVO tmpVO : sendRateList) { // 배분률 대비 현재전송 비율이 가장 적은거 찾기
@@ -3832,24 +3491,24 @@
 					sendAgent = tmpVO.getAgentCode();
 				}
 			}
-			
+
 			mjonMsgVO.setMsgType("4");
-			mjonMsgVO.setMsgDiv("S");	// 단문	
-			
+			mjonMsgVO.setMsgDiv("S");	// 단문
+
     		// 문자타입별 대표전송사 정보
 			MjonMsgVO mjonMsgVO2 = new MjonMsgVO();
 			mjonMsgVO2 = mjonMsgService.selectRepMsgAgetnInfo(mjonMsgVO);
 			// 전송사 구분 코드 - 01 : 아이하트, 02 : 현대 퓨쳐넷, 03 : 아이엠오, 04 : 다우기술
-        	mjonMsgVO.setAgentCode(mjonMsgVO2.getAgentCode()); //전송사 선택			
+        	mjonMsgVO.setAgentCode(mjonMsgVO2.getAgentCode()); //전송사 선택
         	// 전송금액
 			mjonMsgVO.setTotPrice(mjonMsgVO2.getAgentPrice().toString());	//총금액
-			mjonMsgVO.setEachPrice(mjonMsgVO2.getAgentPrice().toString());	//한건 금액			
-			
+			mjonMsgVO.setEachPrice(mjonMsgVO2.getAgentPrice().toString());	//한건 금액
+
 			MjonMsgReturnVO returnVO = mjonMsgDataService.insertSysMsgDataInfo(mjonMsgVO);
-			
+
 			resultSts = returnVO.getSendMsgCnt();
 			resultBlockSts = returnVO.getSendMsgBlockCnt();
-			
+
 			// 시스템 발송 로그
 			SendLogVO sendLogVO = new SendLogVO();
 			// SendType 1:문자로 발송 2:이메일로 발송
@@ -3859,45 +3518,77 @@
 			sendLogVO.setReceive(mjonMsgVO.getCallTo());
 			sendLogVO.setCheckNo(code);
 			sendLogVO.setContents(contents);
-			
+
 			mjonMsgDataService.insertSysMsgLog(sendLogVO);
-			
+
 		} catch (Exception e) {
-			
+
 			logger.error(e.getMessage(), e);
 			modelAndView.addObject("result", "fail");
 			modelAndView.addObject("message", "기타 시스템 오류 : " + e.getMessage());
-			
+
 		}
-		
+
 		if (!resultSts.equals("0")) {
-			
+
 			modelAndView.addObject("message", "휴대폰으로 전송된 인증번호 5자리를 입력해주시기 바랍니다.");
 			modelAndView.addObject("certCode", code);
 			modelAndView.addObject("result", "success");
 			modelAndView.addObject("resultSts", resultSts);
 			modelAndView.addObject("resultBlockSts", resultBlockSts);
-			
+
 		} else {
-			
+
 			modelAndView.addObject("message", "문자 전송에 오류가 발생하였습니다.");
 			modelAndView.addObject("result", "fail");
 			modelAndView.addObject("resultSts", "0");
 			modelAndView.addObject("resultBlockSts", "0");
-			
+
 		}
-		
+
 		return modelAndView;
 	}
 
 	// 아이디 비밀번호 찾기 시스템 인증번호발송 조회
 	@RequestMapping(value = "/web/user/sendSysMsgSelectAjax.do")
-	public ModelAndView sendSysMsgSelectAjax(@ModelAttribute("SendLogVO") SendLogVO sendLogVO) throws Exception {
-
+	public ModelAndView sendSysMsgSelectAjax(
+			@ModelAttribute("SendLogVO") SendLogVO sendLogVO,
+			@RequestParam(value = "securityLogin", required = false) String securityLogin,
+			@RequestParam(value = "userId", required = false) String userId,
+			HttpSession session) throws Exception {
+		// log
 		ModelAndView modelAndView = new ModelAndView();
 		modelAndView.setViewName("jsonView");
 
 		try {
+			if ("Y".equals(securityLogin)) {
+				String pendingUserId = getPendingSecurityUserId(session);
+				if (StringUtils.isBlank(pendingUserId) || !pendingUserId.equals(userId)) {
+					modelAndView.addObject("compare", false);
+					modelAndView.addObject("message", "로그인 정보를 다시 확인해주세요.");
+					return modelAndView;
+				}
+				SecondAuthLogVO lockCheck = new SecondAuthLogVO();
+				lockCheck.setTryLoginId(pendingUserId);
+				if (secondAuthLogService.isSecondAuthLocked(lockCheck)) {
+					modelAndView.addObject("compare", false);
+					modelAndView.addObject("locked", true);
+					modelAndView.addObject("lockSeconds", Math.max(1,
+							secondAuthLogService.selectSecondAuthLockRemainingSeconds(lockCheck)));
+					modelAndView.addObject("message", "인증 시도 5회 이상 실패하여 5분간 로그인이 제한됩니다. 잠시 후 다시 시도해주세요.");
+					return modelAndView;
+				}
+				if (getSecondAuthSendCount(session) >= SecondAuthSessionConstants.MAX_SEND_COUNT) {
+					modelAndView.addObject("compare", false);
+					modelAndView.addObject("message", "인증번호 재발송 가능 횟수를 초과했습니다.");
+					return modelAndView;
+				}
+				long remainingMillis = getSecondAuthResendRemainingMillis(session);
+				modelAndView.addObject("compare", remainingMillis <= 0);
+				modelAndView.addObject("retryAfter", (remainingMillis + 999) / 1000);
+				modelAndView.addObject("status", "success");
+				return modelAndView;
+			}
 			sendLogVO = mjonMsgDataService.selectSysMsgLog(sendLogVO);
 			if (sendLogVO == null) {
 				modelAndView.addObject("compare", true);
@@ -3941,7 +3632,7 @@
 		System.out.println(mberManageVO.toString());
 		System.out.println("mberManageVO.toString()");
 
-		
+
 		// 사용자 유효성 검사
 		// id, hp, nm
 		try {
@@ -3951,18 +3642,18 @@
 			userManageVO.setEmplyrNm(mberManageVO.getMberNm());
 			userManageVO.setMoblphonNo(mberManageVO.getMoblphonNo());
 
-			boolean TorF = true; 
+			boolean TorF = true;
 			if("USR".equals(mberManageVO.getUserSe()))
 			{
 				TorF = userManageService.selectAdminIdAjax(userManageVO) > 0 ? true : false;
-				
+
 				mberManageVO.setMberNm("");
 				// 관리자 로그인 본인인증은 name이 null이여야함
-				
+
 			}
 			else
 			{
-				
+
 				/*
 				 * 일   시 : 2023.07.25
 				 * 담당자 : 원영현 과장 수정
@@ -3972,7 +3663,7 @@
 				userManageVO.setMoblphonNo("");
 				List<UserManageVO> usedNmList = new ArrayList<>();
 				usedNmList = userManageService.selectUserIdAjax2(userManageVO);
-				TorF = usedNmList.size() > 0 ? true : false; 
+				TorF = usedNmList.size() > 0 ? true : false;
 			}
 
 
@@ -4013,7 +3704,7 @@
 //				serverNm = request.getScheme() + "://www.munjaon.co.kr";
 //			}
 //			String authRUrl = serverNm + mberManageVO.getAuthRUrl();
-//			
+//
 //			AuthCertVO certVO = kmcCertCheck.authCertCheckThree(authRUrl, mberManageVO);
 //
 //
@@ -4021,7 +3712,7 @@
 			mberCertPhoneVO.setTrUrl("/web/cop/kmc/authRequestFindIdPwAjax.do");
 			mberCertPhoneVO.setName(mberManageVO.getMberNm());
 			mberCertPhoneVO.setPhoneNo(mberManageVO.getMoblphonNo());
-			
+
 			String plusInfo = "";
 			plusInfo += mberManageVO.getMberId();
 			if(StringUtils.isNotEmpty(mberManageVO.getPassword()))
@@ -4029,13 +3720,13 @@
 	    		plusInfo = plusInfo + ",,," + mberManageVO.getPassword();
 	    	}
 			mberCertPhoneVO.setPlusInfo(plusInfo);
-			
+
 			AuthCertVO certVO = kmcCertChecker.authCertCheck(mberCertPhoneVO, request);
-			
+
 			modelAndView.addObject("tr_cert", certVO.getTr_cert());
 			modelAndView.addObject("tr_url", certVO.getTr_url());
 			modelAndView.addObject("tr_add", certVO.getTr_add());
-			
+
 			modelAndView.addObject("result", "success");
 
 		} catch (Exception e) {
@@ -4096,24 +3787,24 @@
 			RedirectAttributes redirectAttributes, HttpSession session) throws Exception {
 
 		UserManageVO userManageVO = new UserManageVO();
-		
+
 //		KmcVO kmcRVO = loginService.kmcDecryption(request, kmcVO);
-		
+
 		//kmc 본인인증 결과처리
 		kmcVO = kmcCertChecker.authCertResult(request, response, model);
 		//kmc 본인인증 로그 처리
 		AuthCertVO certVO = kmcCertChecker.insertCertLog(kmcVO, "KMC_아이디패스워드찾기");
-		
+
 
 		userManageVO.setCrtfcDnValue(kmcVO.getDI());
 		userManageVO.setEmplyrNm(kmcVO.getName());
 		userManageVO.setEmplyrId(kmcVO.getPlusInfo().split(",,,")[0]);
 		userManageVO.setPassword(kmcVO.getPlusInfo().split(",,,")[1]);
-		
+
 		userManageService.updateCrtfcDnValue(userManageVO);
-		
+
 		try {
-		
+
 			String msg = "";
 			if (!findIdKmcAdmCheck(userManageVO)) {
 				msg = "일치하는 정보가 없습니다.";
@@ -4126,17 +3817,7 @@
 			e.printStackTrace();
 			// TODO: handle exception
 		}
-//		kmcRVO.setCertType("KMC_관리자미인증IP접속인증");
-//		// 디비 테이블에 저장하기
-//		
-//		try {
-//			mberManageService.insertCertInfoLogBn(kmcRVO);
-//			
-//		}  catch (Exception e) {
-//			System.out.println("!!!!!!!!!!!!!!!!!");
-//			e.printStackTrace();
-//			// TODO: handle exception
-//		}
+
 		
 		
 		model.addAttribute("kmcVO", kmcVO);
 
src/main/java/itn/let/uat/uia/web/EgovMyPageRestController.java (added)
+++ src/main/java/itn/let/uat/uia/web/EgovMyPageRestController.java
@@ -0,0 +1,258 @@
+package itn.let.uat.uia.web;
+
+import egovframework.rte.fdl.security.userdetails.util.EgovUserDetailsHelper;
+import egovframework.rte.ptl.mvc.tags.ui.pagination.PaginationInfo;
+import itn.com.cmm.LoginVO;
+import itn.com.cmm.service.EgovFileMngService;
+import itn.com.cmm.service.EgovFileMngUtil;
+import itn.com.cmm.service.FileVO;
+import itn.com.utl.page.PaginationUtil;
+import itn.com.utl.response.ApiResponse;
+import itn.com.utl.response.PageResponse;
+import itn.let.cert.phone.service.CertPhoneService;
+import itn.let.cert.phone.service.MberCertPhoneVO;
+import itn.let.mjo.mjocommon.MjonCommon;
+import itn.let.uat.uia.service.SecondAuthMethodLogService;
+import itn.let.uat.uia.service.SecondAuthMethodLogVO;
+import itn.let.mjo.msg.service.MjonMsgService;
+import itn.let.mjo.msg.service.MjEmployeeCertVO;
+import itn.let.mjo.msg.service.SendNumberStatusVO;
+import itn.let.sym.site.service.EgovSiteManagerService;
+import itn.let.sym.site.service.JoinSettingVO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.MultipartHttpServletRequest;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+@RestController
+public class EgovMyPageRestController {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(EgovMyPageRestController.class);
+
+    @Resource(name = "CertPhoneService")
+    private CertPhoneService certPhoneService;
+
+    @Resource(name = "secondAuthMethodLogService")
+    private SecondAuthMethodLogService secondAuthMethodLogService;
+
+    @Resource(name = "mjonMsgService")
+    private MjonMsgService mjonMsgService;
+
+    @Resource(name = "EgovFileMngUtil")
+    private EgovFileMngUtil fileUtil;
+
+    @Resource(name = "EgovFileMngService")
+    private EgovFileMngService fileMngService;
+
+    @Resource(name = "MjonCommon")
+    private MjonCommon mjonCommon;
+
+    @Resource(name = "egovSiteManagerService")
+    private EgovSiteManagerService egovSiteManagerService;
+
+    @RequestMapping(value = {"/web/user/send-number/status.do", "/web/user/selectSendNumberStatus.do"})
+    public ApiResponse<SendNumberStatusVO> selectSendNumberStatus() {
+        try {
+            LoginVO user = getAuthenticatedUser();
+            return ApiResponse.success(mjonMsgService.selectSendNumberStatus(user.getId()));
+        } catch (Exception e) {
+            return ApiResponse.fail(e.getMessage());
+        }
+    }
+
+    @RequestMapping(value = "/web/user/send-number/employee-cert/apply.do", method = RequestMethod.POST)
+    public ApiResponse<SendNumberStatusVO> applyEmployeeCert(
+            MultipartHttpServletRequest multiRequest) {
+        List<FileVO> savedFiles = null;
+        try {
+            LoginVO user = getAuthenticatedUser();
+            int applyCnt = parseApplyCnt(multiRequest.getParameter("applyCnt"));
+            if (!"Y".equals(multiRequest.getParameter("agreeYn"))) {
+                throw new IllegalArgumentException("증빙서류 수집·이용에 동의해 주세요.");
+            }
+
+            Map<String, MultipartFile> files = validateEmployeeCertFiles(multiRequest.getFileMap());
+            savedFiles = fileUtil.parseFileInf(files, "EMP_CERT_", 0, "", "", "");
+            String atchFileId = fileMngService.insertFileInfs(savedFiles);
+
+            MjEmployeeCertVO employeeCertVO = new MjEmployeeCertVO();
+            employeeCertVO.setMberId(user.getId());
+            employeeCertVO.setApplyCnt(applyCnt);
+            employeeCertVO.setAgreeYn("Y");
+            employeeCertVO.setAtchFileId(atchFileId);
+
+            SendNumberStatusVO status = mjonMsgService.applyEmployeeCert(employeeCertVO);
+            sendEmployeeCertSlackNotice(user);
+            return ApiResponse.success(status, "종사자 수 인증을 신청했습니다.");
+        } catch (Exception e) {
+            deleteSavedFiles(savedFiles);
+            return ApiResponse.fail(e.getMessage());
+        }
+    }
+
+    private void sendEmployeeCertSlackNotice(LoginVO user) {
+        try {
+            JoinSettingVO joinSettingVO = egovSiteManagerService.selectAdminNotiDetail();
+            if (joinSettingVO == null || !"Y".equals(joinSettingVO.getSlackNoti())) {
+                return;
+            }
+
+            String mberNm = user.getMberNm();
+            if (mberNm == null || mberNm.trim().isEmpty()) {
+                mberNm = user.getName();
+            }
+            if (mberNm == null) {
+                mberNm = "";
+            }
+
+            String message = "[문자온] 종사자 수 인증 요청 - "
+                    + mberNm + "(" + user.getId() + ")";
+            mjonCommon.sendSimpleSlackMsg(message);
+        } catch (Exception e) {
+            LOGGER.warn("종사자 수 인증 신청 Slack 알림 발송에 실패했습니다. mberId={}", user.getId(), e);
+        }
+    }
+
+    @RequestMapping(value = "/web/user/selectAuthPhoneNumber.do")
+    public ApiResponse<Map<String, Object>> selectAuthPhoneNumber() {
+        try {
+            LoginVO user = getAuthenticatedUser();
+            Map<String, Object> data = new HashMap<String, Object>();
+            data.put("registered", withAccountPhone(certPhoneService.selectMberCertPhoneList(user.getId()), user.getMoblphonNo()));
+            data.put("candidates", certPhoneService.selectEligibleCertPhoneList(user.getId()));
+            data.put("maxCount", "c".equalsIgnoreCase(user.getDept()) ? 4 : 3);
+            return ApiResponse.success(data);
+        } catch (Exception e) {
+            return ApiResponse.fail(e.getMessage());
+        }
+    }
+
+    @RequestMapping(value = "/web/user/selectAuthMethodChangeHistory.do")
+    public ApiResponse<PageResponse<SecondAuthMethodLogVO>> selectAuthMethodChangeHistory(
+            SecondAuthMethodLogVO logVO) {
+        try {
+            LoginVO user = getAuthenticatedUser();
+            logVO.setMberId(user.getId());
+
+            PaginationInfo paginationInfo = PaginationUtil.init(logVO);
+            List<SecondAuthMethodLogVO> list = secondAuthMethodLogService.selectSecondAuthMethodLogList(logVO);
+            int totalCount = secondAuthMethodLogService.selectSecondAuthMethodLogCnt(logVO);
+            paginationInfo.setTotalRecordCount(totalCount);
+
+            return ApiResponse.success(PageResponse.withList(list, paginationInfo));
+        } catch (Exception e) {
+            return ApiResponse.fail(e.getMessage());
+        }
+    }
+
+    private LoginVO getAuthenticatedUser() {
+        if (!EgovUserDetailsHelper.isAuthenticated()) {
+            throw new IllegalStateException("로그인 후 이용해 주세요.");
+        }
+
+        LoginVO user = (LoginVO) EgovUserDetailsHelper.getAuthenticatedUser();
+        if (user == null || user.getId() == null || user.getId().isEmpty()) {
+            throw new IllegalStateException("로그인 정보를 확인할 수 없습니다.");
+        }
+        return user;
+    }
+
+    private int parseApplyCnt(String applyCnt) {
+        try {
+            int value = Integer.parseInt(applyCnt);
+            if (value < 1) {
+                throw new NumberFormatException();
+            }
+            return value;
+        } catch (Exception e) {
+            throw new IllegalArgumentException("종사자 수를 1명 이상 입력해 주세요.");
+        }
+    }
+
+    private Map<String, MultipartFile> validateEmployeeCertFiles(Map<String, MultipartFile> requestFiles) {
+        Map<String, MultipartFile> files = new LinkedHashMap<String, MultipartFile>();
+        if (requestFiles != null) {
+            for (Map.Entry<String, MultipartFile> entry : requestFiles.entrySet()) {
+                MultipartFile file = entry.getValue();
+                if (file != null && !file.isEmpty()) {
+                    files.put(entry.getKey(), file);
+                }
+            }
+        }
+
+        if (files.isEmpty()) {
+            throw new IllegalArgumentException("종사자 수를 확인할 수 있는 증빙서류를 첨부해 주세요.");
+        }
+        if (files.size() > 5) {
+            throw new IllegalArgumentException("증빙서류는 최대 5개까지 첨부할 수 있습니다.");
+        }
+
+        long maxFileSize = 50L * 1024L * 1024L;
+        for (MultipartFile file : files.values()) {
+            String fileName = file.getOriginalFilename() == null
+                    ? "" : file.getOriginalFilename().toLowerCase(Locale.ROOT);
+            if (!(fileName.endsWith(".pdf") || fileName.endsWith(".png")
+                    || fileName.endsWith(".jpg") || fileName.endsWith(".jpeg"))) {
+                throw new IllegalArgumentException("PDF 또는 이미지(PNG, JPG, JPEG) 파일만 첨부할 수 있습니다.");
+            }
+            if (file.getSize() > maxFileSize) {
+                throw new IllegalArgumentException("파일당 최대 크기는 50MB입니다.");
+            }
+        }
+        return files;
+    }
+
+    private void deleteSavedFiles(List<FileVO> savedFiles) {
+        if (savedFiles == null) {
+            return;
+        }
+        for (FileVO fileVO : savedFiles) {
+            try {
+                fileMngService.deleteFmsFileInf(fileVO);
+            } catch (Exception ignored) {
+                // 파일 정리 실패가 원래 신청 오류 응답을 덮지 않도록 한다.
+            }
+        }
+    }
+
+    private List<MberCertPhoneVO> withAccountPhone(List<MberCertPhoneVO> phoneList, String accountPhone) {
+        List<MberCertPhoneVO> result = phoneList == null
+                ? new ArrayList<MberCertPhoneVO>()
+                : new ArrayList<MberCertPhoneVO>(phoneList);
+        String normalizedAccountPhone = normalizePhone(accountPhone);
+        if (normalizedAccountPhone.isEmpty()) {
+            return result;
+        }
+
+        boolean hasBasicPhone = false;
+        for (MberCertPhoneVO phone : result) {
+            if ("BASIC".equals(phone.getPhoneType())) {
+                hasBasicPhone = true;
+                break;
+            }
+        }
+        if (!hasBasicPhone) {
+            MberCertPhoneVO basicPhone = new MberCertPhoneVO();
+            basicPhone.setMbtlnum(normalizedAccountPhone);
+            basicPhone.setCertAlias("대표번호");
+            basicPhone.setPhoneType("BASIC");
+            result.add(0, basicPhone);
+        }
+        return result;
+    }
+
+    private String normalizePhone(String phoneNo) {
+        return phoneNo == null ? "" : phoneNo.replaceAll("[^0-9]", "");
+    }
+}
src/main/java/itn/let/uat/uia/web/EgovMypageController.java
--- src/main/java/itn/let/uat/uia/web/EgovMypageController.java
+++ src/main/java/itn/let/uat/uia/web/EgovMypageController.java
@@ -8,6 +8,7 @@
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -16,6 +17,10 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import itn.com.utl.page.PaginationUtil;
+import itn.let.uat.uia.service.SecondAuthLogService;
+import itn.let.uat.uia.service.SecondAuthLogVO;
+import itn.let.uat.uia.service.impl.SecondAuthLogDAO;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Controller;
@@ -184,7 +189,10 @@
 	
 	@Resource(name = "KmcCertChecker")
 	private KmcCertChecker kmcCertChecker;
-	
+
+	@Resource(name = "secondAuthLogService")
+	private SecondAuthLogService secondAuthLogService;
+
 	/**
 	 * 마이페이지 비밀번호 변경 탭
 	 */
@@ -1375,13 +1383,11 @@
 	public String sendNumberManage(
 		@ModelAttribute MberManageVO mberManageVO
 		, HttpServletRequest request
-		, ModelMap model, RedirectAttributes redirectAttributes) throws Exception {
-		/*LoginVO	loginVO = (LoginVO)EgovUserDetailsHelper.getAuthenticatedUser();*/
+		, ModelMap model, RedirectAttributes redirectAttributes
+	) throws Exception {
 		LoginVO loginVO = EgovUserDetailsHelper.isAuthenticated()? (LoginVO)EgovUserDetailsHelper.getAuthenticatedUser():null;
 		
 		if(loginVO == null) {
-			/*model.addAttribute("message", "문자고 서비스는 로그인 후 이용 가능합니다.");*/
-			//redirectAttributes.addFlashAttribute("message", "문자고 서비스는 로그인 후 이용 가능합니다.");
 			return "redirect:/web/user/login/login.do";
 		}
 		model.addAttribute("loginVO", loginVO);
@@ -1390,8 +1396,7 @@
 		mberManageVO.setMberId(loginVO.getId());
 		MberManageVO userInfo = userManageService.selectUserInfo(mberManageVO);
 		model.addAttribute("userInfo", userInfo);
-		
-		
+
 		return "web/user/sendNumberManage";
 	}
 	
@@ -1655,7 +1660,13 @@
 		ModelAndView modelAndView = new ModelAndView();
 		modelAndView.setViewName("jsonView");
 		try {
-			
+			LoginVO authenticatedUser = EgovUserDetailsHelper.isAuthenticated()
+					? (LoginVO) EgovUserDetailsHelper.getAuthenticatedUser() : null;
+			if (authenticatedUser == null) {
+				throw new IllegalStateException("로그인 후 이용해 주세요.");
+			}
+			mjonMsgVO.setUserId(authenticatedUser.getId());
+
 			//발신번호 차단 리스트 체크(아이디 상관없이 모든 번호에 대한 차단번호 체크 20220616)
 			int sendBlockNumberCheck = 0;
 			sendBlockNumberCheck = mjonMsgService.selectSendBlockPhonNumCheck(mjonMsgVO);
@@ -1677,6 +1688,14 @@
 				modelAndView.addObject("message", message);	
 				modelAndView.addObject("result", "fail");
 			}else {
+				MjPhoneMemberVO availabilityVO = new MjPhoneMemberVO();
+				availabilityVO.setUserId(authenticatedUser.getId());
+				availabilityVO.setPhoneNumber(mjonMsgVO.getCallFrom());
+				mjonMsgService.validateUserSendNumberAvailability(availabilityVO);
+				modelAndView.addObject("phmLineType", availabilityVO.getPhmLineType());
+				modelAndView.addObject("sendNumberStatus",
+						mjonMsgService.selectSendNumberStatus(authenticatedUser.getId()));
+
 				//System.out.println(mjonMsgVO.getPhmAuthType() + "  :  ***********************************************");
 				//if(mjonMsgVO.getPhmAuthType().equals("01") || mjonMsgVO.getPhmAuthType() == "01") { // phone
 					//System.out.println("휴대폰 인증 ***********************************************");
@@ -1723,7 +1742,8 @@
 				modelAndView.addObject("result", "success");
 			}
 		}catch (Exception e) {
-			modelAndView.addObject("message", "다시 시도 부탁드립니다.");
+			modelAndView.addObject("message", StringUtil.isNotEmpty(e.getMessage())
+					? e.getMessage() : "다시 시도 부탁드립니다.");
 			modelAndView.addObject("result", "fail");
 		} 
 		
@@ -1888,7 +1908,7 @@
 		mjPhoneMemberVO.setPhoneNumber(kmcVO.getPhoneNo());
 		mjPhoneMemberVO.setUserName(kmcVO.getName());
 		mjPhoneMemberVO.setAuthYn("Y");
-		mjonMsgService.insertSendNumber(mjPhoneMemberVO);
+		mjonMsgService.insertUserSendNumber(mjPhoneMemberVO);
 		
 		
 		model.addAttribute("kmcVO", kmcVO);
@@ -1917,7 +1937,7 @@
 			mjPhoneMemberVO.setPhoneNumber(certVO.getSentPhoneNumber()); // 발송 전화번호
 			mjPhoneMemberVO.setUserName(loginVO.getName());
 			mjPhoneMemberVO.setAuthYn("Y");
-			mjonMsgService.insertSendNumber(mjPhoneMemberVO);
+			mjonMsgService.insertUserSendNumber(mjPhoneMemberVO);
 			modelAndView.addObject("result", "success");
 			
 		}catch (Exception e) {
@@ -1947,9 +1967,36 @@
 
 		// 발신번호 서류관리 등록 [시작]
 		try {
+			if (loginVO == null) {
+				throw new IllegalStateException("로그인 후 이용해 주세요.");
+			}
+			certVO.setDept(loginVO.getDept());
+			String requestedPhoneNumber = StringUtil.isNotEmpty(certVO.getSendPhoneNumber())
+					? certVO.getSendPhoneNumber() : certVO.getSentPhoneNumber();
+
+			MjPhoneMemberVO availabilityVO = new MjPhoneMemberVO();
+			availabilityVO.setUserId(loginVO.getId());
+			availabilityVO.setPhoneNumber(requestedPhoneNumber);
+			mjonMsgService.validateUserSendNumberAvailability(availabilityVO);
+
+			MjonMsgVO numberCheckVO = new MjonMsgVO();
+			numberCheckVO.setUserId(loginVO.getId());
+			numberCheckVO.setCallFrom(requestedPhoneNumber);
+			if (mjonMsgService.selectSendBlockPhonNumCheck(numberCheckVO) != 0) {
+				throw new IllegalStateException("입력하신 발신번호는 차단된 발신번호입니다.");
+			}
+			if (mjonMsgService.selectSendPhonNumCheck(numberCheckVO) != 0) {
+				throw new IllegalStateException("이미 등록되어 있는 발신번호입니다.");
+			}
+
 			String atchFileId = "";
 			String isThumbFile = "";
-			final Map<String, MultipartFile> files = multiRequest.getFileMap();
+			boolean documentRequired = !("p".equalsIgnoreCase(loginVO.getDept())
+					&& "5".equals(certVO.getNameType()) && "01".equals(certVO.getCertType()))
+					&& !("c".equalsIgnoreCase(loginVO.getDept())
+					&& "2".equals(certVO.getNameType()) && "01".equals(certVO.getCertType()));
+			final Map<String, MultipartFile> files = validateSendNumberDocumentFiles(
+					multiRequest.getFileMap(), documentRequired);
 			if (!files.isEmpty()){
 				List<FileVO> result = fileUtil.parseFileInf(files, "CERT_", 0, atchFileId , "", isThumbFile);
 				atchFileId = fileMngService.insertFileInfs(result);
@@ -1970,10 +2017,7 @@
 			mjPhoneMemberVO.setPhmAuthType(certVO.getCertType());	//번호 인증 타입 01:휴대폰인증, 03: 서류인증
 			mjPhoneMemberVO.setFrstRegisterId(loginVO.getId());
 			mjPhoneMemberVO.setLastUpdusrId(loginVO.getId());
-			mjPhoneMemberVO.setPhoneNumber(certVO.getSentPhoneNumber()); // 발송 전화번호  
-			if(!"".equals(certVO.getSendPhoneNumber()) && certVO.getSendPhoneNumber() != null) { //230530 추가 - 고객이 인증 후 입력 한 발신번호를 수정하면, 수정 한 발신번호가 등록되어서 인증했을 경우 인증한 번호로 발신번호 등록하게끔 추가
-				mjPhoneMemberVO.setPhoneNumber(certVO.getSendPhoneNumber()); 
-			}
+			mjPhoneMemberVO.setPhoneNumber(requestedPhoneNumber); // 최종 확인된 발신번호
 			mjPhoneMemberVO.setUserName(loginVO.getName());
 			mjPhoneMemberVO.setSendPhoneNumber(certVO.getSendPhoneNumber()); // 확인  전화번호
 			mjPhoneMemberVO.setPhmValue(certVO.getPhmValue()); //비고
@@ -2004,7 +2048,7 @@
 			}else {
 				mjPhoneMemberVO.setAuthYn("H");
 			}
-			mjonMsgService.insertSendNumber(mjPhoneMemberVO);
+			mjonMsgService.insertUserSendNumber(mjPhoneMemberVO);
 			// 발신번호 등록 [끝]
 			
 			// 즉시승인이 아닌경우 관리자에게 이메일 발송
@@ -2016,10 +2060,12 @@
 			
 			modelAndView.addObject("result", "success");
 		}catch (Exception e) {
-			System.out.println("발신번호 등록 오류 아이디 : " + loginVO.getId());
+			System.out.println("발신번호 등록 오류 아이디 : "
+					+ (loginVO == null ? "" : loginVO.getId()));
 			System.out.println("발신번호 등록 오류 내용 : " + e.getMessage());
 			
-			modelAndView.addObject("message", "다시 시도 부탁드립니다.");
+			modelAndView.addObject("message", StringUtil.isNotEmpty(e.getMessage())
+					? e.getMessage() : "다시 시도 부탁드립니다.");
 			modelAndView.addObject("result", "fail");
 			
 			return modelAndView;
@@ -2298,120 +2344,62 @@
 	 * 회원탈퇴 본인인증 화면
 	 */
 	@RequestMapping(value="/web/user/mberSecureLogin.do")
-	public String secureLogin(@ModelAttribute("mberCertLoginLogVO") MberCertLoginLogVO mberCertLoginLogVO  
-			, ModelMap model, HttpServletRequest request, RedirectAttributes redirectAttributes) throws Exception {
+	public String secureLogin(@ModelAttribute("secondAuthLogVO") SecondAuthLogVO secondAuthLogVO, ModelMap model, HttpServletRequest request) throws Exception {
 		
 		LoginVO loginVO = EgovUserDetailsHelper.isAuthenticated()? (LoginVO)EgovUserDetailsHelper.getAuthenticatedUser():null;
 		if(loginVO == null) {
 			return "redirect:/web/user/login/login.do";
 		}
 
-		
-		try {
-			System.out.println("test Try +++ ");
-			
-		    // secuLoginFlag 값을 Model에 추가
-			if(mberCertLoginLogVO == null) {
-			    model.addAttribute("secuLoginFlag", "");
-			}else {
-			    model.addAttribute("secuLoginFlag", mberCertLoginLogVO.getSecuLoginFlag());
-			}
+		String loginId = loginVO.getId();
+		//로그인 권한정보 불러오기
+		String userId = EgovStringUtil.isNullToString(loginId);
 
-		} catch (Exception e) {
-			e.printStackTrace();
-			// TODO: handle exception
-		}
+		// 접속 IP
+		String cennectIP = EgovClntInfo.getClntIP(request);
+		model.addAttribute("cennectIP", cennectIP);
 
-			//로그인 권한정보 불러오기
-			String userId = loginVO == null ? "" : EgovStringUtil.isNullToString(loginVO.getId());
-			
-	
-			/** pageing */
-			PaginationInfo paginationInfo = new PaginationInfo();
-			paginationInfo.setCurrentPageNo(mberCertLoginLogVO.getPageIndex());
-			paginationInfo.setRecordCountPerPage(mberCertLoginLogVO.getPageUnit());
-			paginationInfo.setPageSize(mberCertLoginLogVO.getPageSize());
-	
-			mberCertLoginLogVO.setFirstIndex(paginationInfo.getFirstRecordIndex());
-			mberCertLoginLogVO.setLastIndex(paginationInfo.getLastRecordIndex());
-			mberCertLoginLogVO.setRecordCountPerPage(paginationInfo.getRecordCountPerPage());
-			if("".equals(mberCertLoginLogVO.getSearchSortCnd())){ //최초조회시 최신것 조회List
-				mberCertLoginLogVO.setSearchSortCnd("LOGIN_DT");
-				mberCertLoginLogVO.setSearchSortOrd("desc");
-			}
-	
-	
-			mberCertLoginLogVO.setLoginId(userId);
-	    	//전체 발송 리스트 불러오기
-	    	List<MberCertLoginLogVO> resultList = certLoginLogService.findAllCertLoginLogVO(mberCertLoginLogVO);
-	    	model.addAttribute("resultList", resultList);
-	    	model.addAttribute("resultListCnt", resultList.size());
-			
-	
-	    	paginationInfo.setTotalRecordCount( resultList.size()> 0 ? resultList.get(0).getTotCnt() : 0);
-	        model.addAttribute("paginationInfo", paginationInfo);
-			
-			
-			
-	
-	        // 접속 IP
-	    	String cennectIP = EgovClntInfo.getClntIP(request);
-	    	model.addAttribute("cennectIP", cennectIP);
-	
-	        // 접속 IP가 등록되어있는지 확인
-	    	MberCertIpVO mberCertIpVO = new MberCertIpVO();
-	    	mberCertIpVO.setCertIp(cennectIP);
-	    	mberCertIpVO.setMberId(userId);
-	    	model.addAttribute("chkcertRegIP", certIpService.checkRegCertIp(mberCertIpVO));
-			
-			
-			
-			
-	
-			{
-				/**
-				 * 보안인증 본인인증 기능추가
-				 * 20241120 원영현 과장 추가
-				 * */ 
-//				KmcCertChecker kmcCertCheck = new KmcCertChecker();
-//				
-//				//kmc 본인인증 실/개발 서버 구분
-//				String serverNm = "";
-//				if(request.getServerName().contains("192.168.0.60") || request.getServerName().contains("localhost")) {
-//					serverNm = request.getScheme() + "://192.168.0.60:8085";
-//				}else{
-//					serverNm = request.getScheme() + "://www.munjaon.co.kr";
-//				}
-//				
-//				System.out.println("@@@  serverNm  : "  + serverNm);
-				
-				MberCertPhoneVO mberCertPhoneVO = new MberCertPhoneVO();
-				mberCertPhoneVO.setTrUrl("/web/user/selectSecurityAuthn.do");
-				mberCertPhoneVO.setPlusInfo(loginVO.getId());
-				
-				AuthCertVO certVO = kmcCertChecker.authCertCheck(mberCertPhoneVO, request);
-				
-				model.addAttribute("certVO", certVO);
-			}
-			
-			MberManageVO mberManageVO = new MberManageVO();
-			mberManageVO.setMberId(loginVO.getId());
-			
-			model.addAttribute("mberManageVO", userManageService.selectUserInfo(mberManageVO));
-			
-			// 탭 하이라이트
-			model.addAttribute("pageTab", "mberSecureLogin");
-			
-	
-			//디비 테이블에 저장하기ㅣ
-			AuthCertVO authCertVO = new AuthCertVO();
-			authCertVO.setMberId(userId);
-			authCertVO.setCertType("secureLoginOFF");
-			String certLogRegDate = mberManageService.findTopByregDateFromCertLog(authCertVO);
-			
-	
-			// 탭 하이라이트
-			model.addAttribute("certLogRegDate", certLogRegDate);
+		// 접속 IP가 등록되어있는지 확인
+		MberCertIpVO mberCertIpVO = new MberCertIpVO();
+		mberCertIpVO.setCertIp(cennectIP);
+		mberCertIpVO.setMberId(userId);
+
+		MberCertPhoneVO mberCertPhoneVO = new MberCertPhoneVO();
+		mberCertPhoneVO.setTrUrl("/web/user/selectSecurityAuthn.do");
+		mberCertPhoneVO.setPlusInfo(loginId);
+
+		MberManageVO mberManageVO = new MberManageVO();
+		mberManageVO.setMberId(loginId);
+
+		//디비 테이블에 저장하기ㅣ
+		AuthCertVO authCertVO = new AuthCertVO();
+		authCertVO.setMberId(userId);
+		authCertVO.setCertType("secureLoginOFF");
+
+		/** pageing */
+		PaginationInfo paginationInfo = PaginationUtil.initWithSort(secondAuthLogVO, "LOG_ID", "DESC");
+
+		secondAuthLogVO.setTryLoginId(userId);
+		//전체 발송 리스트 불러오기
+		List<SecondAuthLogVO> resultList = secondAuthLogService.selectSecondAuthLogList(secondAuthLogVO);
+		int totCnt = secondAuthLogService.selectSecondAuthLogCnt(secondAuthLogVO);
+
+		paginationInfo.setTotalRecordCount(totCnt);
+
+		AuthCertVO certVO = kmcCertChecker.authCertCheck(mberCertPhoneVO, request);
+		String certLogRegDate = mberManageService.findTopByregDateFromCertLog(authCertVO);
+
+		model.addAttribute("resultList", resultList);
+		model.addAttribute("resultListCnt", resultList.size());
+		model.addAttribute("paginationInfo", paginationInfo);
+
+		model.addAttribute("chkcertRegIP", certIpService.checkRegCertIp(mberCertIpVO));
+		model.addAttribute("certVO", certVO);
+		model.addAttribute("mberManageVO", userManageService.selectUserInfo(mberManageVO));
+		// 탭 하이라이트
+		model.addAttribute("pageTab", "mberSecureLogin");
+		// 탭 하이라이트
+		model.addAttribute("certLogRegDate", certLogRegDate);
 			
 		return "web/user/mberSecureLogin";
 	}
@@ -2843,5 +2831,40 @@
 			return new ResponseEntity<>("fail", HttpStatus.OK);
 		}
 	}
-	
+
+	private Map<String, MultipartFile> validateSendNumberDocumentFiles(
+			Map<String, MultipartFile> requestFiles, boolean required) {
+		Map<String, MultipartFile> files = new LinkedHashMap<String, MultipartFile>();
+		if (requestFiles != null) {
+			for (Map.Entry<String, MultipartFile> entry : requestFiles.entrySet()) {
+				MultipartFile file = entry.getValue();
+				if (file != null && !file.isEmpty()) {
+					files.put(entry.getKey(), file);
+				}
+			}
+		}
+
+		if (required && files.isEmpty()) {
+			throw new IllegalArgumentException("필수 제출 서류를 첨부해 주세요.");
+		}
+		if (files.size() > 5) {
+			throw new IllegalArgumentException("첨부파일은 최대 5개까지 등록할 수 있습니다.");
+		}
+
+		long maxFileSize = 50L * 1024L * 1024L;
+		for (MultipartFile file : files.values()) {
+			String fileName = file.getOriginalFilename() == null
+					? "" : file.getOriginalFilename().toLowerCase(Locale.ROOT);
+			if (!(fileName.endsWith(".pdf") || fileName.endsWith(".png")
+					|| fileName.endsWith(".jpg") || fileName.endsWith(".jpeg"))) {
+				throw new IllegalArgumentException(
+						"PDF 또는 이미지(PNG, JPG, JPEG) 파일만 첨부할 수 있습니다.");
+			}
+			if (file.getSize() > maxFileSize) {
+				throw new IllegalArgumentException("파일당 최대 크기는 50MB입니다.");
+			}
+		}
+		return files;
+	}
+
 }
src/main/java/itn/let/uat/uia/web/EgovMypageRESTAPIController.java
--- src/main/java/itn/let/uat/uia/web/EgovMypageRESTAPIController.java
+++ src/main/java/itn/let/uat/uia/web/EgovMypageRESTAPIController.java
@@ -3,13 +3,19 @@
 import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 import javax.annotation.Resource;
 
+import egovframework.rte.ptl.mvc.tags.ui.pagination.PaginationInfo;
+import itn.let.mjo.msgsent.service.MjonMsgSentVO;
+import itn.let.mjo.reservmsg.service.MjonResvMsgVO;
 import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 import org.springframework.ui.Model;
 import org.springframework.ui.ModelMap;
+import org.springframework.web.bind.annotation.ModelAttribute;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.servlet.ModelAndView;
@@ -22,6 +28,7 @@
 import itn.let.mjo.apikey.service.ApiKeyMngService;
 import itn.let.mjo.apikey.service.ApiKeyVO;
 import itn.let.utl.user.service.SecuKeyUtil;
+import org.springframework.web.servlet.mvc.support.RedirectAttributes;
 
 /**
  * 일반 로그인, 인증서 로그인을 처리하는 컨트롤러 클래스
@@ -51,7 +58,7 @@
 	//api call info 정보
 	@Resource(name = "apiCallInfoMngService")
     private ApiCallInfoMngService apiCallInfoMngService;
-	
+
 	/**
 	 * 비밀번호 변경 페이지
 	 */
@@ -293,7 +300,106 @@
 		
 		return "web/api/examDown";
 	}
-	
-	
+
+	@RequestMapping(value="/web/api/approvalList.do")
+	public String approveList(@ModelAttribute("searchVO") MjonMsgSentVO mjonMsgSentVO, ModelMap model)
+			throws Exception {
+
+		LoginVO loginVO = EgovUserDetailsHelper.isAuthenticated()? (LoginVO)EgovUserDetailsHelper.getAuthenticatedUser():null;
+
+		if(loginVO == null) {
+			return "redirect:/web/user/login/login.do";
+		}
+		// 검색 리스트 불러오기
+		if(mjonMsgSentVO.getPageUnit() != 10) {
+			mjonMsgSentVO.setPageUnit(mjonMsgSentVO.getPageUnit());
+		}
+
+		model.addAttribute("loginVO", loginVO);
+		return "web/api/approvalList";
+	}
+
+	/**
+	 * API 발송내역관리 탭 리스트 화면
+	 * @param mjonMsgSentVO
+	 * @param model
+	 * @throws Exception
+	 */
+	@RequestMapping(value= {"/web/api/selectApiMsgSentListViewAjax.do"})
+	public String selectApiMsgSentListViewAjax(@ModelAttribute("searchVO") MjonMsgSentVO mjonMsgSentVO, ModelMap model) throws Exception{
+
+		try {
+			LoginVO loginVO = EgovUserDetailsHelper.isAuthenticated()? (LoginVO)EgovUserDetailsHelper.getAuthenticatedUser():null;
+			String userId = loginVO == null ? "" : EgovStringUtil.isNullToString(loginVO.getId());
+
+			mjonMsgSentVO.setUserId(userId);
+
+			/** pageing */
+			PaginationInfo paginationInfo = new PaginationInfo();
+			paginationInfo.setCurrentPageNo(mjonMsgSentVO.getPageIndex());
+			paginationInfo.setRecordCountPerPage(mjonMsgSentVO.getPageUnit());
+			paginationInfo.setPageSize(mjonMsgSentVO.getPageSize());
+
+			mjonMsgSentVO.setFirstIndex(paginationInfo.getFirstRecordIndex());
+			mjonMsgSentVO.setLastIndex(paginationInfo.getLastRecordIndex());
+			mjonMsgSentVO.setRecordCountPerPage(paginationInfo.getRecordCountPerPage());
+
+			//전체 발송 리스트 불러오기
+			Map<String, Object> resultMap = apiKeyMngService.selectApiMsgSentList(mjonMsgSentVO);
+			int totalCnt = apiKeyMngService.selectCountApiMsgSentList(mjonMsgSentVO);
+			resultMap.put("totalCnt", totalCnt);
+
+			model.addAttribute("resultAllSentList", resultMap.get("resultList"));
+			paginationInfo.setTotalRecordCount(totalCnt);
+			model.addAttribute("paginationInfo", paginationInfo);
+			model.addAttribute("totalRecordCount", paginationInfo.getTotalRecordCount());
+
+			model.addAttribute("mjonMsgSentVO", mjonMsgSentVO);
+
+		} catch (Exception e) {
+			e.printStackTrace();
+			// TODO: handle exception
+		}
+
+		return "web/api/ApiSentApprovalListAjax";
+	}
+
+	@RequestMapping(value= {"/web/api/updateApproveDataAjax.do"})
+	public ModelAndView updateApproveDataAjax(@ModelAttribute("searchVO") MjonMsgSentVO mjonMsgSentVO, ModelMap model) throws Exception{
+
+		ModelAndView modelAndView = new ModelAndView();
+		modelAndView.setViewName("jsonView");
+		try {
+
+			LoginVO loginVO = EgovUserDetailsHelper.isAuthenticated()? (LoginVO)EgovUserDetailsHelper.getAuthenticatedUser():null;
+			String userId = loginVO == null ? "" : EgovStringUtil.isNullToString(loginVO.getId());
+
+			if(!userId.equals("")) {
+				mjonMsgSentVO.setUserId(userId);
+			}else {
+				modelAndView.addObject("message", "로그인 실패");
+				modelAndView.addObject("result", "fail");
+
+				return modelAndView;
+			}
+
+			int resultSts = apiKeyMngService.updateApproveApiMsg(mjonMsgSentVO);
+
+			if(resultSts > 0) {
+				modelAndView.addObject("message", "성공");
+				modelAndView.addObject("result", "success");
+			}else {
+				modelAndView.addObject("message", "실패");
+				modelAndView.addObject("result", "fail");
+			}
+
+		} catch (Exception e) {
+			e.printStackTrace();
+			// TODO: handle exception
+		}
+
+		return modelAndView;
+
+	}
 	
 }
src/main/java/itn/let/uat/uia/web/KmcCertChecker.java
--- src/main/java/itn/let/uat/uia/web/KmcCertChecker.java
+++ src/main/java/itn/let/uat/uia/web/KmcCertChecker.java
@@ -47,6 +47,10 @@
 							) {
 		//url 세팅
 		mberCertPhoneVO = setMberCertPhoneVO(request, mberCertPhoneVO);
+		if (mberCertPhoneVO.getUrlCode() == null
+				|| mberCertPhoneVO.getUrlCode().trim().length() == 0) {
+			return new AuthCertVO();
+		}
 		
 		//kmc step 01 데이터
 		//reqNum은 최대 40byte 까지 사용 가능
@@ -155,8 +159,10 @@
 	    //-----------------------------------------------------------------------------------------------------------------
 		try{
 			// Parameter 수신 --------------------------------------------------------------------
-			api_token	   = request.getParameter("apiToken").trim();
-			api_certNum = request.getParameter("certNum");
+			String apiTokenParameter = request.getParameter("apiToken");
+			String certNumParameter = request.getParameter("certNum");
+			api_token = apiTokenParameter == null ? "" : apiTokenParameter.trim();
+			api_certNum = certNumParameter == null ? "" : certNumParameter.trim();
 
 			// 파라미터 유효성 검증
 			if( api_token.length() == 0 ){
@@ -319,7 +325,7 @@
 	            msgChk="Y";
 	        }
 
-			if(msgChk.equals("N")){
+			if(!"Y".equals(msgChk)){
 				goErrorPage("비정상적인 접근입니다.!!", request, response);
 				return kmcVO;
 			}
 
src/main/java/itn/let/uat/uia/web/SecondAuthLogRestController.java (added)
+++ src/main/java/itn/let/uat/uia/web/SecondAuthLogRestController.java
@@ -0,0 +1,37 @@
+package itn.let.uat.uia.web;
+
+import egovframework.rte.fdl.security.userdetails.util.EgovUserDetailsHelper;
+import egovframework.rte.ptl.mvc.tags.ui.pagination.PaginationInfo;
+import itn.com.cmm.LoginVO;
+import itn.com.utl.page.PaginationUtil;
+import itn.com.utl.response.ApiResponse;
+import itn.com.utl.response.PageResponse;
+import itn.let.uat.uia.service.SecondAuthLogService;
+import itn.let.uat.uia.service.SecondAuthLogVO;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+@RestController
+public class SecondAuthLogRestController {
+    @Resource(name = "secondAuthLogService")
+    private SecondAuthLogService secondAuthLogService;
+
+    @RequestMapping(value = "/web/log/selectSecondAuthLog.do")
+    public ApiResponse<PageResponse<SecondAuthLogVO>> selectLoginHistory(SecondAuthLogVO logVO) {
+        try {
+            LoginVO user = (LoginVO) EgovUserDetailsHelper.getAuthenticatedUser();
+            logVO.setTryLoginId(user.getId());
+            PaginationInfo paginationInfo = PaginationUtil.initWithSort(logVO, "FRST_REGIST_PNTTM", "desc");
+            List<SecondAuthLogVO> list = secondAuthLogService.selectSecondAuthLogList(logVO);
+            int totalCount = secondAuthLogService.selectSecondAuthLogCnt(logVO);
+            paginationInfo.setTotalRecordCount(totalCount);
+            PageResponse<SecondAuthLogVO> page = PageResponse.withAll(list, null, paginationInfo);
+            return ApiResponse.success(page);
+        } catch (Exception e) {
+            return ApiResponse.fail(e.getMessage());
+        }
+    }
+}
src/main/java/itn/let/uss/umt/service/EgovMberManageService.java
--- src/main/java/itn/let/uss/umt/service/EgovMberManageService.java
+++ src/main/java/itn/let/uss/umt/service/EgovMberManageService.java
@@ -256,4 +256,6 @@
 
 	//사용자 헤더 정보(단가, 이벤트 활성화 기간)
 	public MberManageVO selectMberHeaderInfo(String mberId) throws Exception;
+
+	public boolean isCompanyRegistrationPendingOrRejected(String mberId) throws Exception;
 }
(No newline at end of file)
src/main/java/itn/let/uss/umt/service/MberManageVO.java
--- src/main/java/itn/let/uss/umt/service/MberManageVO.java
+++ src/main/java/itn/let/uss/umt/service/MberManageVO.java
@@ -558,8 +558,26 @@
 	private int headerPicture3Cnt;	
 	private int headerKakaoAtCnt;	
 	private int headerKakaoFtCnt;	
-	private int headerFaxCnt;	
-	
+	private int headerFaxCnt;
+
+	public String getPhoneNumber() {
+		return phoneNumber;
+	}
+
+	public void setPhoneNumber(String phoneNumber) {
+		this.phoneNumber = phoneNumber;
+	}
+
+	public String getPhmLineType() {
+		return phmLineType;
+	}
+
+	public void setPhmLineType(String phmLineType) {
+		this.phmLineType = phmLineType;
+	}
+
+	private String phoneNumber;
+	private String phmLineType;
 	public String getSmsCode() {
 		return smsCode;
 	}
src/main/java/itn/let/uss/umt/service/UserDefaultVO.java
--- src/main/java/itn/let/uss/umt/service/UserDefaultVO.java
+++ src/main/java/itn/let/uss/umt/service/UserDefaultVO.java
@@ -2,6 +2,7 @@
 
 import java.io.Serializable;
 
+import itn.com.utl.page.PageableVO;
 import org.apache.commons.lang3.builder.ToStringBuilder;
 
 import lombok.Getter;
src/main/java/itn/web/MainController.java
--- src/main/java/itn/web/MainController.java
+++ src/main/java/itn/web/MainController.java
@@ -12,9 +12,6 @@
 import java.security.PublicKey;
 import java.security.spec.RSAPublicKeySpec;
 import java.text.SimpleDateFormat;
-import java.time.LocalDate;
-import java.time.format.DateTimeFormatter;
-import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -48,12 +45,11 @@
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
-import itn.com.cmm.RestResponse;
+import itn.let.mjo.apikey.service.ApiKeyMngService;
+import itn.let.mjo.msgsent.service.MjonMsgSentVO;
 import org.json.simple.JSONArray;
 import org.json.simple.JSONObject;
 import org.springframework.beans.factory.annotation.Value;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Controller;
 import org.springframework.ui.Model;
 import org.springframework.ui.ModelMap;
@@ -108,7 +104,6 @@
 import itn.let.mjo.addr.service.AddrTransHistService;
 import itn.let.mjo.addr.service.AddrTransHistVO;
 import itn.let.mjo.event.service.MjonEventService;
-import itn.let.mjo.event.service.MjonEventVO;
 import itn.let.mjo.kisa.service.KisaService;
 import itn.let.mjo.kisa.service.KisaVO;
 import itn.let.mjo.mjocommon.MjonForienIpChk;
@@ -133,7 +128,6 @@
 import itn.let.sym.prm.service.EgovProgrmManageService;
 import itn.let.sym.prm.service.ProgrmManageVO;
 import itn.let.sym.site.service.EgovSiteManagerService;
-import itn.let.sym.site.service.JoinSettingVO;
 import itn.let.sym.site.service.MetaTagVO;
 import itn.let.sym.site.service.SiteManagerVO;
 import itn.let.uss.umt.service.EgovMberManageService;
@@ -276,6 +270,9 @@
 	/** 메인팝업 service */
 	@Resource(name = "mainPopupManageService")
 	private MainPopupManageService mainPopupManageService;
+
+	@Resource(name = "apiKeyMngService")
+	private ApiKeyMngService apiKeyMngService;
 	
 	
 	@Value("#{globalSettings['Globals.email.host']}")
@@ -718,6 +715,18 @@
 			System.out.println("===================mainPopupList");
 			
 		}
+
+		{//API 미확인 발송 내역 안내 팝업
+			String userId = user == null ? "" : EgovStringUtil.isNullToString(user.getId());
+			int apiApprovalN = 0;
+			if(StringUtil.isNotEmpty(userId)) {
+				MjonMsgSentVO mjonMsgSentVO = new MjonMsgSentVO();
+				mjonMsgSentVO.setUserId(userId);
+				mjonMsgSentVO.setApiAprvYn("N");
+				apiApprovalN = apiKeyMngService.selectCountApiMsgSentList(mjonMsgSentVO);
+			}
+			model.addAttribute("apiApprovalN", apiApprovalN);
+		}
 		
 		return "web/main/mainPage";
 	}
src/main/resources/egovframework/egovProps/globals.properties
--- src/main/resources/egovframework/egovProps/globals.properties
+++ src/main/resources/egovframework/egovProps/globals.properties
@@ -98,3 +98,7 @@
 #MOBILE
 Globals.pay.kgm.mobile.mcSvcid=170622040674
 Globals.pay.kgm.mobile.payMode=00
+
+# GeoIP Country DB
+Globals.geoip.database.path=egovframework/geoip/GeoLite2-Country.mmdb
+
src/main/resources/egovframework/egovProps/globals_dev.properties
--- src/main/resources/egovframework/egovProps/globals_dev.properties
+++ src/main/resources/egovframework/egovProps/globals_dev.properties
@@ -122,4 +122,7 @@
 #valkey
 Globals.valkey.ip=192.168.0.60
 Globals.valkey.port=6379
-Globals.valkey.password=itntest123
(No newline at end of file)
+Globals.valkey.password=itntest123
+
+# GeoIP Country DB
+Globals.geoip.database.path=egovframework/geoip/GeoLite2-Country.mmdb
(No newline at end of file)
src/main/resources/egovframework/egovProps/globals_local.properties
--- src/main/resources/egovframework/egovProps/globals_local.properties
+++ src/main/resources/egovframework/egovProps/globals_local.properties
@@ -131,3 +131,6 @@
 Globals.valkey.ip=192.168.0.60
 Globals.valkey.port=6379
 Globals.valkey.password=itntest123
+
+# GeoIP Country DB
+Globals.geoip.database.path=egovframework/geoip/GeoLite2-Country.mmdb
(No newline at end of file)
src/main/resources/egovframework/egovProps/globals_prod.properties
--- src/main/resources/egovframework/egovProps/globals_prod.properties
+++ src/main/resources/egovframework/egovProps/globals_prod.properties
@@ -104,10 +104,13 @@
 Globals.pay.kgm.mobile.payMode=10
 
 #Slack
-Globals.slack.hooks.url=https://hooks.slack.com/services/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
+Globals.slack.hooks.url=https://hooks.slack.com/services/B0C1F1ZGKGV/not0LrYlEzcnS3uUw1C0tlzt
 Globals.slack.channel.name=\uD14C\uC2A4\uD2B8-\uBB38\uC790\uACE0-message
 
 #valkey
-Globals.valkey.ip=1.201.116.193
+Globals.valkey.ip=10.12.107.9
 Globals.valkey.port=6379
 Globals.valkey.password=itntest123
+
+# GeoIP Country DB
+Globals.geoip.database.path=egovframework/geoip/GeoLite2-Country.mmdb
(No newline at end of file)
 
src/main/resources/egovframework/geoip/GeoLite2-Country.mmdb (Binary) (added)
+++ src/main/resources/egovframework/geoip/GeoLite2-Country.mmdb
Binary file is not shown
src/main/resources/egovframework/spring/com/context-idgen.xml
--- src/main/resources/egovframework/spring/com/context-idgen.xml
+++ src/main/resources/egovframework/spring/com/context-idgen.xml
@@ -2407,6 +2407,23 @@
 		<property name="fillChar" value="0" />
 	</bean>
 	
+	<!-- 종사자 수 인증 이력 ID Generation Strategy Config -->
+	<bean name="egovEmployeeCertHstryIdGnrService"
+		class="egovframework.rte.fdl.idgnr.impl.EgovTableIdGnrServiceImpl"
+		destroy-method="destroy">
+		<property name="dataSource" ref="dataSource" />
+		<property name="strategy" ref="employeeCertHstryIdStrategy" />
+		<property name="blockSize" value="10" />
+		<property name="table" value="IDS" />
+		<property name="tableName" value="EMP_CERT_HSTRY_ID" />
+	</bean>
+	<bean name="employeeCertHstryIdStrategy"
+		class="egovframework.rte.fdl.idgnr.impl.strategy.EgovIdGnrStrategyImpl">
+		<property name="prefix" value="ECHID_" />
+		<property name="cipers" value="14" />
+		<property name="fillChar" value="0" />
+	</bean>
+
 	<!-- 문자고 전화번호 ID Generation  Strategy Config -->
 	<bean name="egovPhoneMemberIdGnrService"
 		class="egovframework.rte.fdl.idgnr.impl.EgovTableIdGnrServiceImpl"
@@ -2944,4 +2961,45 @@
         <property name="fillChar" value="0" />
     </bean>
 		
+
+    <!-- 사용자 접근 로그 ID Generation Strategy Config -->
+    <bean name="egovSecondAuthLogIdGnrService"
+          class="egovframework.rte.fdl.idgnr.impl.EgovTableIdGnrServiceImpl"
+          destroy-method="destroy">
+
+        <property name="dataSource" ref="dataSource" />
+        <property name="strategy" ref="secondAuthLogStrategy" />
+
+        <property name="blockSize" value="12" />
+        <property name="table" value="IDS" />
+        <property name="tableName" value="SECOND_AUTH_LOG_ID" />
+    </bean>
+
+    <bean name="secondAuthLogStrategy"
+          class="egovframework.rte.fdl.idgnr.impl.strategy.EgovIdGnrStrategyImpl">
+
+        <property name="prefix" value="SAL_" />
+        <property name="cipers" value="12" />
+        <property name="fillChar" value="0" />
+    </bean>
+
+    <bean name="egovUserAccessLogIdGnrService"
+          class="egovframework.rte.fdl.idgnr.impl.EgovTableIdGnrServiceImpl"
+          destroy-method="destroy">
+
+        <property name="dataSource" ref="dataSource" />
+        <property name="strategy" ref="userAccessLogStrategy" />
+
+        <property name="blockSize" value="12" />
+        <property name="table" value="IDS" />
+        <property name="tableName" value="USER_ACCESS_LOG_ID" />
+    </bean>
+
+    <bean name="userAccessLogStrategy"
+          class="egovframework.rte.fdl.idgnr.impl.strategy.EgovIdGnrStrategyImpl">
+
+        <property name="prefix" value="UAL_" />
+        <property name="cipers" value="12" />
+        <property name="fillChar" value="0" />
+    </bean>
 </beans>
 
src/main/resources/egovframework/sqlmap/config/mysql/sql-map-config-mysql-sym-log-access.xml (added)
+++ src/main/resources/egovframework/sqlmap/config/mysql/sql-map-config-mysql-sym-log-access.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE sqlMapConfig PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"
+    "http://www.ibatis.com/dtd/sql-map-config-2.dtd">
+
+<sqlMapConfig>
+	<sqlMap resource="egovframework/sqlmap/let/sym/log/access/UserAccessLog_SQL_Mysql.xml"/>
+</sqlMapConfig>
src/main/resources/egovframework/sqlmap/config/mysql/sql-map-config-mysql-uat.xml
--- src/main/resources/egovframework/sqlmap/config/mysql/sql-map-config-mysql-uat.xml
+++ src/main/resources/egovframework/sqlmap/config/mysql/sql-map-config-mysql-uat.xml
@@ -4,4 +4,6 @@
 
 <sqlMapConfig>
 	<sqlMap resource="egovframework/sqlmap/let/uat/uia/EgovLoginUsr_SQL_Mysql.xml"/>
+	<sqlMap resource="egovframework/sqlmap/let/uat/uia/SecondAuthLog_SQL_Mysql.xml"/>
+	<sqlMap resource="egovframework/sqlmap/let/uat/uia/SecondAuthMethodLog_SQL_Mysql.xml"/>
 </sqlMapConfig>
src/main/resources/egovframework/sqlmap/let/cert/MjonCertPhone_SQL_mysql.xml
--- src/main/resources/egovframework/sqlmap/let/cert/MjonCertPhone_SQL_mysql.xml
+++ src/main/resources/egovframework/sqlmap/let/cert/MjonCertPhone_SQL_mysql.xml
@@ -14,6 +14,7 @@
 			MBTLNUM AS mbtlnum
 			, '-' AS certMemo
 			, '대표번호' AS certAlias
+			, 'BASIC' AS phoneType
 			, '-' AS frstRegistPnttm
 		FROM 
 		    lettngnrlmber
@@ -23,11 +24,85 @@
 			MBTLNUM AS mbtlnum
 			, CERT_MEMO AS certMemo
 			, CERT_ALIAS AS certAlias
+			, 'ADDITIONAL' AS phoneType
 			, FRST_REGIST_PNTTM AS frstRegistPnttm
 		FROM 
 		    lettngnrlmber_cert_phone
 		WHERE MBER_ID = #userId#
 		AND CERT_USE_YN = 'Y'
+	</select>
+
+	<select id="mberCertPhoneVO.selectEligibleCertPhoneList" parameterClass="String" resultClass="mberCertPhoneVO">
+		SELECT DISTINCT
+			REPLACE(A.PHONE_NUMBER, '-', '') AS mbtlnum,
+			'승인 발신번호' AS certAlias,
+			'CANDIDATE' AS phoneType
+		FROM MJ_PHONE_MEMBER A
+		WHERE A.USER_ID = #userId#
+		  AND A.PHM_TYPE = '01'
+		  AND A.AUTH_YN = 'Y'
+		  AND A.DEL_FLAG = 'N'
+		  AND REPLACE(A.PHONE_NUMBER, '-', '') LIKE '01%'
+		  AND REPLACE(A.PHONE_NUMBER, '-', '') NOT REGEXP '[^0-9]'
+		  AND CHAR_LENGTH(REPLACE(A.PHONE_NUMBER, '-', '')) IN (10, 11)
+		  AND NOT EXISTS (
+			SELECT 1 FROM LETTNGNRLMBER M
+			WHERE M.MBER_ID = A.USER_ID
+			  AND REPLACE(M.MBTLNUM, '-', '') = REPLACE(A.PHONE_NUMBER, '-', '')
+		  )
+		  AND NOT EXISTS (
+			SELECT 1 FROM LETTNGNRLMBER_CERT_PHONE P
+			WHERE P.MBER_ID = A.USER_ID
+			  AND P.CERT_USE_YN = 'Y'
+			  AND REPLACE(P.MBTLNUM, '-', '') = REPLACE(A.PHONE_NUMBER, '-', '')
+		  )
+		ORDER BY mbtlnum
+	</select>
+
+	<select id="mberCertPhoneVO.countRegisteredAuthPhone" parameterClass="mberCertPhoneVO" resultClass="int">
+		SELECT COUNT(*)
+		FROM (
+			SELECT REPLACE(MBTLNUM, '-', '') AS PHONE_NO
+			FROM LETTNGNRLMBER
+			WHERE MBER_ID = #mberId#
+			UNION ALL
+			SELECT REPLACE(MBTLNUM, '-', '') AS PHONE_NO
+			FROM LETTNGNRLMBER_CERT_PHONE
+			WHERE MBER_ID = #mberId#
+			  AND CERT_USE_YN = 'Y'
+		) P
+		WHERE P.PHONE_NO = REPLACE(#mbtlnum#, '-', '')
+	</select>
+
+	<select id="mberCertPhoneVO.countEligibleCertPhone" parameterClass="mberCertPhoneVO" resultClass="int">
+		SELECT COUNT(*)
+		FROM MJ_PHONE_MEMBER A
+		WHERE A.USER_ID = #mberId#
+		  AND A.PHM_TYPE = '01'
+		  AND A.AUTH_YN = 'Y'
+		  AND A.DEL_FLAG = 'N'
+		  AND REPLACE(A.PHONE_NUMBER, '-', '') = REPLACE(#mbtlnum#, '-', '')
+		  AND REPLACE(A.PHONE_NUMBER, '-', '') LIKE '01%'
+		  AND REPLACE(A.PHONE_NUMBER, '-', '') NOT REGEXP '[^0-9]'
+		  AND CHAR_LENGTH(REPLACE(A.PHONE_NUMBER, '-', '')) IN (10, 11)
+		  AND NOT EXISTS (
+			SELECT 1 FROM LETTNGNRLMBER M
+			WHERE M.MBER_ID = A.USER_ID
+			  AND REPLACE(M.MBTLNUM, '-', '') = REPLACE(A.PHONE_NUMBER, '-', '')
+		  )
+		  AND NOT EXISTS (
+			SELECT 1 FROM LETTNGNRLMBER_CERT_PHONE P
+			WHERE P.MBER_ID = A.USER_ID
+			  AND P.CERT_USE_YN = 'Y'
+			  AND REPLACE(P.MBTLNUM, '-', '') = REPLACE(A.PHONE_NUMBER, '-', '')
+		  )
+	</select>
+
+	<select id="mberCertPhoneVO.countAdditionalCertPhone" parameterClass="String" resultClass="int">
+		SELECT COUNT(*)
+		FROM LETTNGNRLMBER_CERT_PHONE
+		WHERE MBER_ID = #userId#
+		  AND CERT_USE_YN = 'Y'
 	</select>
 	
 	
@@ -73,6 +148,7 @@
 					, MBTLNUM
 					, CERT_ALIAS
 					, CERT_MEMO
+					, CERT_USE_YN
 					, FRST_REGIST_PNTTM
 					, FRST_REGISTER_ID
 				)
@@ -82,9 +158,15 @@
 					, #mbtlnum#
 					, #certAlias#
 					, #certMemo#
+					, 'Y'
 					, now()
 					, #frstRegisterId#
 				)
+		ON DUPLICATE KEY UPDATE
+			CERT_ALIAS = VALUES(CERT_ALIAS),
+			CERT_MEMO = VALUES(CERT_MEMO),
+			CERT_USE_YN = 'Y',
+			LAST_UPDUSR_ID = VALUES(FRST_REGISTER_ID)
 	</insert>
 	
 	
@@ -163,7 +245,13 @@
 		       );
 	
 	</insert>
+
+	<update id="certPhoneDAO.resetMemberPhone" parameterClass="mberCertPhoneVO">
+		UPDATE LETTNGNRLMBER
+		SET MBTLNUM = #mbtlnum#
+		WHERE MBER_ID = #mberId#
+	</update>
 	
 	
 	
-</sqlMap>
(No newline at end of file)
+</sqlMap>
src/main/resources/egovframework/sqlmap/let/mjo/apikey/ApiKeyMng_SQL_Mysql.xml
--- src/main/resources/egovframework/sqlmap/let/mjo/apikey/ApiKeyMng_SQL_Mysql.xml
+++ src/main/resources/egovframework/sqlmap/let/mjo/apikey/ApiKeyMng_SQL_Mysql.xml
@@ -4,6 +4,7 @@
 <sqlMap namespace="ApiKeyMng">
 
     <typeAlias  alias="apiKeyVO" type = "itn.let.mjo.apikey.service.ApiKeyVO"/>
+	<typeAlias  alias="mjonMsgSentVO" type="itn.let.mjo.msgsent.service.MjonMsgSentVO"/>
 
     <select id="ApiKeyMngDAO.selectMberApiKeyList" parameterClass="apiKeyVO" resultClass="apiKeyVO" remapResults="true">
 	SELECT
@@ -211,5 +212,129 @@
 		and b.USE_YN  != 'N'
 	LIMIT 1
 
-    </select>			
+    </select>
+
+
+	<select id="ApiKeyMngDAO.selectApiMsgSentList" parameterClass="mjonMsgSentVO" resultClass="mjonMsgSentVO">
+		/* MjonMsgSentDAO.selectApiMsgSentList */
+		SELECT
+		B.USER_ID as userId
+		, B.MSG_GROUP_ID as msgGroupId
+		, B.MSG_GROUP_CNT as msgGroupCnt
+		, B.SMS_TXT as smsTxt
+		, B.SUBJECT as subject
+		, B.SUBJECT_CHK_YN as subjectChkYn
+		, B.FILE_CNT as fileCnt
+		, CAST(DATE_FORMAT(B.REQ_DATE, '%Y-%m-%d %H:%i') AS CHAR) AS reqDate
+		, (
+		CASE
+		WHEN B.DELAY_YN = 'Y' AND B.DELAY_COMPLETE_YN = 'N' THEN DATE_ADD(B.REQ_DATE, INTERVAL -30 MINUTE)
+		ELSE B.REQ_DATE
+		END
+		) AS delayOrgTime
+		, B.MSG_TYPE as msgType
+		, B.DELAY_YN as delayYn
+		, B.DELAY_COMPLETE_YN as delayCompleteYn
+		, B.API_APRV_YN as apiAprvYn
+		, CAST(DATE_FORMAT(B.API_APRV_DT, '%Y-%m-%d %H:%i') AS CHAR) AS apiAprvDt
+		FROM MJ_MSG_DATA A
+		JOIN MJ_MSG_GROUP_DATA B ON A.MSG_GROUP_ID = B.MSG_GROUP_ID
+		WHERE
+		(B.DEL_FLAG = 'N' OR B.DEL_FLAG IS NULL)
+		AND A.DEL_FLAG = 'N'
+		AND B.USER_ID = #userId#
+		<isEmpty property="msgType">
+			AND B.MSG_TYPE in ('4', '6', '8')
+		</isEmpty>
+		<isNotEmpty property="msgType">
+			<isEqual property="msgType" compareValue="M" >
+				AND B.MSG_TYPE in ('4', '6')
+			</isEqual>
+			<isEqual property="msgType" compareValue="K" >
+				AND B.MSG_TYPE in ('8')
+			</isEqual>
+		</isNotEmpty>
+		<isNotEmpty property="apiAprvYn">
+			<isEqual property="apiAprvYn" compareValue="N" >
+				AND (B.API_APRV_YN = 'N' or B.API_APRV_YN is null)
+			</isEqual>
+			<isEqual property="apiAprvYn" compareValue="Y" >
+				AND B.API_APRV_YN = 'Y'
+			</isEqual>
+		</isNotEmpty>
+		AND (B.REQ_DATE >= DATE_SUB(NOW(), INTERVAL 1 YEAR) AND B.REQ_DATE >= '2025-08-21') /* todo 임시날짜 */
+		AND B.SEND_KIND = 'A'
+		AND
+		(
+		(A.AGENT_CODE = '01' AND A.RSLT_CODE = '100' AND A.RSLT_CODE2 = '0')
+		OR (A.AGENT_CODE = '02' AND A.RSLT_CODE = '0')
+		OR (A.AGENT_CODE = '03' AND A.RSLT_CODE IN ('100', '101', '110', '800'))
+		OR (A.AGENT_CODE = '04' AND A.RSLT_CODE IN ('4100', '6600', '7000'))
+		OR (A.AGENT_CODE = '05' AND A.RSLT_CODE IN ('1000', '1001'))
+		OR (A.AGENT_CODE = '07' AND A.RSLT_CODE IN ('6', '1000'))
+		OR (A.AGENT_CODE = '08' AND A.RSLT_CODE IN ('1000', '1001'))
+		OR (A.AGENT_CODE = '09' AND A.RSLT_CODE IN ('1000', '1001'))
+		)
+		GROUP BY B.MSG_GROUP_ID
+		ORDER BY 1=1, B.REQ_DATE desc
+		LIMIT  #recordCountPerPage# OFFSET #firstIndex#
+	</select>
+
+	<select id="ApiKeyMngDAO.selectCountApiMsgSentList" parameterClass="mjonMsgSentVO" resultClass="int">
+		/* MjonMsgSentDAO.countAllApiMsgSentList */
+		select
+		COUNT(DISTINCT B.MSG_GROUP_ID) as totalGroupCount
+		from
+		MJ_MSG_DATA A
+		join MJ_MSG_GROUP_DATA B on
+		A.MSG_GROUP_ID = B.MSG_GROUP_ID
+		WHERE
+		(B.DEL_FLAG = 'N' OR B.DEL_FLAG IS NULL)
+		AND A.DEL_FLAG = 'N'
+		AND B.USER_ID = #userId#
+		<isEmpty property="msgType">
+			AND B.MSG_TYPE in ('4', '6', '8')
+		</isEmpty>
+		<isNotEmpty property="msgType">
+			<isEqual property="msgType" compareValue="M" >
+				AND B.MSG_TYPE in ('4', '6')
+			</isEqual>
+			<isEqual property="msgType" compareValue="K" >
+				AND B.MSG_TYPE in ('8')
+			</isEqual>
+		</isNotEmpty>
+		<isNotEmpty property="apiAprvYn">
+			<isEqual property="apiAprvYn" compareValue="N" >
+				AND (B.API_APRV_YN = 'N' or B.API_APRV_YN is null)
+			</isEqual>
+			<isEqual property="apiAprvYn" compareValue="Y" >
+				AND B.API_APRV_YN = 'Y'
+			</isEqual>
+		</isNotEmpty>
+		AND (B.REQ_DATE >= DATE_SUB(NOW(), INTERVAL 1 YEAR) AND B.REQ_DATE >= '2025-08-21') /* todo 임시날짜 */
+		AND B.SEND_KIND = 'A'
+		AND
+		(
+		(A.AGENT_CODE = '01' AND A.RSLT_CODE = '100' AND A.RSLT_CODE2 = '0')
+		OR (A.AGENT_CODE = '02' AND A.RSLT_CODE = '0')
+		OR (A.AGENT_CODE = '03' AND A.RSLT_CODE IN ('100', '101', '110', '800'))
+		OR (A.AGENT_CODE = '04' AND A.RSLT_CODE IN ('4100', '6600', '7000'))
+		OR (A.AGENT_CODE = '05' AND A.RSLT_CODE IN ('1000', '1001'))
+		OR (A.AGENT_CODE = '07' AND A.RSLT_CODE IN ('6', '1000'))
+		OR (A.AGENT_CODE = '08' AND A.RSLT_CODE IN ('1000', '1001'))
+		OR (A.AGENT_CODE = '09' AND A.RSLT_CODE IN ('1000', '1001'))
+		)
+	</select>
+
+	<update id="ApiKeyMngDAO.updateApproveApiMsg" parameterClass="mjonMsgSentVO">
+		UPDATE MJ_MSG_GROUP_DATA
+		SET API_APRV_DT = now()
+		, API_APRV_YN = #apiAprvYn#
+		WHERE
+		USER_ID = #userId#
+		<iterate prepend="AND MSG_GROUP_ID IN" open="(" close=")" conjunction="," property="msgGroupIdList">
+			#msgGroupIdList[]#
+		</iterate>
+	</update>
+
 </sqlMap>
src/main/resources/egovframework/sqlmap/let/mjo/block/Block_SQL_Mysql.xml
--- src/main/resources/egovframework/sqlmap/let/mjo/block/Block_SQL_Mysql.xml
+++ src/main/resources/egovframework/sqlmap/let/mjo/block/Block_SQL_Mysql.xml
@@ -3,6 +3,7 @@
 <sqlMap namespace="Block">
     <typeAlias  alias="egovMap" type="egovframework.rte.psl.dataaccess.util.EgovMap"/>
     <typeAlias  alias="blockVO" type="itn.let.mjo.block.service.MjonBlockVO"/>
+	<typeAlias  alias="geoIpBlockVO" type="itn.let.mjo.block.service.GeoIpBlockLogVO"/>
 
 	<!-- 차단 목록 조회 -->
 	<select id="MjonBlockDAO.selectBlockList" parameterClass="blockVO" resultClass="blockVO">
@@ -225,6 +226,27 @@
 	<!-- 주소록 삭제 -->
 	<delete id="MjonBlockDAO.deleteBlockAddr" parameterClass="blockVO">
 		DELETE FROM MJ_ADDR	WHERE ADDR_PHONE_NO IN (SELECT PHONE FROM MJ_BLOCK WHERE BLOCK_ID = #blockId#)
-	</delete>		
+	</delete>
+
+	<!-- 해외접근 IP 차단 로그 등록 -->
+	<insert id="MjonBlockDAO.insertGeoIpBlockLog" parameterClass="geoIpBlockVO">
+		INSERT INTO MJ_ACCESS_BLOCK_LOG (
+			ACCESS_IP,
+			REQUEST_URI,
+			COUNTRY_CODE,
+			COUNTRY_NAME,
+			BLOCK_YN
+		) VALUES (
+			#accessIp#,
+			#requestUri#,
+			#countryCode#,
+			#countryName#,
+			#blockYn#
+		)
+	</insert>
+
+	<select id="MjonBlockDAO.selectGeoIpBlockYn" resultClass="String">
+		select ACCESS_IP_BLOCK_YN from SITEMANAGER
+	</select>
 			
 </sqlMap>
src/main/resources/egovframework/sqlmap/let/mjo/kakao/KakaoSent_SQL_Mysql.xml
--- src/main/resources/egovframework/sqlmap/let/mjo/kakao/KakaoSent_SQL_Mysql.xml
+++ src/main/resources/egovframework/sqlmap/let/mjo/kakao/KakaoSent_SQL_Mysql.xml
@@ -1453,12 +1453,12 @@
 						AND  B.SMS_TXT like CONCAT('%', #searchKeyword#, '%')
 					</isEqual>
 					<isEqual property="searchCondition" compareValue="4" >
-						AND EXISTS (
-							SELECT 1 FROM MJ_MSG_DATA MD
-							WHERE MD.MSG_GROUP_ID = B.MSG_GROUP_ID
-								AND MD.DEL_FLAG = 'N'
-								AND REPLACE(MD.CALL_TO, '-', '') LIKE CONCAT('%', REPLACE(#searchKeyword#, '-', ''), '%')
-						)
+						 AND EXISTS (
+						     SELECT 1 FROM MJ_MSG_DATA MD
+						     WHERE MD.MSG_GROUP_ID = B.MSG_GROUP_ID
+						       AND MD.DEL_FLAG = 'N'
+						       AND REPLACE(MD.CALL_TO, '-', '') LIKE CONCAT('%', REPLACE(#searchKeyword#, '-', ''), '%')
+						 )
 					</isEqual>
 				</isNotEmpty>
 				GROUP BY B.MSG_GROUP_ID
src/main/resources/egovframework/sqlmap/let/msg/MjonMsgData_SQL_mysql.xml
--- src/main/resources/egovframework/sqlmap/let/msg/MjonMsgData_SQL_mysql.xml
+++ src/main/resources/egovframework/sqlmap/let/msg/MjonMsgData_SQL_mysql.xml
@@ -21,6 +21,8 @@
 	<typeAlias  alias="mjonSwearWordVO" type="itn.let.mjo.msg.service.MjonSwearWordVO"/>
 	<typeAlias  alias="mjonMsgResvVO" type="itn.let.mjo.reservmsg.service.MjonResvMsgVO"/>
 	<typeAlias  alias="mjonMsgStatVO" type="itn.let.mjo.msg.service.MjonMsgStatVO"/>
+	<typeAlias  alias="sendNumberStatusVO" type="itn.let.mjo.msg.service.SendNumberStatusVO"/>
+	<typeAlias  alias="mjEmployeeCertVO" type="itn.let.mjo.msg.service.MjEmployeeCertVO"/>
 	<typeAlias  alias="mjonMsgStatVO" type="itn.let.mjo.msg.service.MjonMsgStatVO"/>
 	
 	
@@ -1622,6 +1624,7 @@
 			, LM.MANAGER_NM				AS managerNm
 			, (SELECT FN_GETNAMEBYID(APH.ADM_ID) FROM MJ_ADM_PROC_HSTRY APH WHERE A.PHM_ID = APH.PHM_ID ORDER BY APH.ADM_PROC_HSTRY_SEQ DESC LIMIT 1 ) AS admNm
 			, A.OWNER_NAME				AS ownerName
+			, A.PHM_LINE_TYPE			AS phmLineType
 		FROM
 			MJ_PHONE_MEMBER A
 			INNER JOIN LETTNGNRLMBER LM
@@ -1733,6 +1736,139 @@
 		</isNotEmpty>
 		LIMIT  #recordCountPerPage# OFFSET #firstIndex#	
 	</select>
+
+	<insert id="mjonMsgDAO.insertEmployeeCertBase" parameterClass="mjEmployeeCertVO">
+	/* mjonMsgDAO.insertEmployeeCertBase */
+		INSERT IGNORE INTO MJ_EMPLOYEE_CERT (
+			MBER_ID
+			, CERT_CNT
+			, LAST_HSTRY_SEQ
+			, AUTH_HSTRY_SEQ
+			, LAST_AUTH_PNTTM
+			, FRST_REGIST_PNTTM
+			, FRST_REGISTER_ID
+			, LAST_UPDT_PNTTM
+			, LAST_UPDUSR_ID
+		) VALUES (
+			#mberId#
+			, NULL
+			, NULL
+			, NULL
+			, NULL
+			, NOW()
+			, #frstRegisterId#
+			, NOW()
+			, #lastUpdusrId#
+		)
+	</insert>
+
+	<select id="mjonMsgDAO.selectEmployeeCertForUpdate" parameterClass="mjEmployeeCertVO" resultClass="mjEmployeeCertVO">
+	/* mjonMsgDAO.selectEmployeeCertForUpdate */
+		SELECT
+			EC.MBER_ID AS mberId
+			, EC.CERT_CNT AS certCnt
+			, EC.LAST_HSTRY_SEQ AS lastHstrySeq
+			, EC.AUTH_HSTRY_SEQ AS authHstrySeq
+			, EH.AUTH_YN AS authYn
+		FROM MJ_EMPLOYEE_CERT EC
+		LEFT JOIN MJ_EMPLOYEE_CERT_HSTRY EH
+			ON EC.MBER_ID = EH.MBER_ID
+			AND EC.LAST_HSTRY_SEQ = EH.EMPLOYEE_CERT_HSTRY_SEQ
+		WHERE EC.MBER_ID = #mberId#
+		FOR UPDATE
+	</select>
+
+	<insert id="mjonMsgDAO.insertEmployeeCertHstry" parameterClass="mjEmployeeCertVO">
+	/* mjonMsgDAO.insertEmployeeCertHstry */
+		INSERT INTO MJ_EMPLOYEE_CERT_HSTRY (
+			EMPLOYEE_CERT_HSTRY_SEQ
+			, MBER_ID
+			, APPLY_CNT
+			, AUTH_YN
+			, REJECT_CODE
+			, REJECT_REASON
+			, ATCH_FILE_ID
+			, AGREE_YN
+			, AGREE_PNTTM
+			, FRST_REGIST_PNTTM
+			, FRST_REGISTER_ID
+			, LAST_UPDT_PNTTM
+			, LAST_UPDUSR_ID
+		) VALUES (
+			#employeeCertHstrySeq#
+			, #mberId#
+			, #applyCnt#
+			, 'N'
+			, NULL
+			, NULL
+			, #atchFileId#
+			, 'Y'
+			, NOW()
+			, NOW()
+			, #frstRegisterId#
+			, NOW()
+			, #lastUpdusrId#
+		)
+	</insert>
+
+	<update id="mjonMsgDAO.updateEmployeeCertLastHstry" parameterClass="mjEmployeeCertVO">
+	/* mjonMsgDAO.updateEmployeeCertLastHstry */
+		UPDATE MJ_EMPLOYEE_CERT
+		SET LAST_HSTRY_SEQ = #employeeCertHstrySeq#
+			, LAST_UPDT_PNTTM = NOW()
+			, LAST_UPDUSR_ID = #lastUpdusrId#
+		WHERE MBER_ID = #mberId#
+	</update>
+
+	<select id="mjonMsgDAO.selectSendNumberStatus" parameterClass="sendNumberStatusVO" resultClass="sendNumberStatusVO">
+	/* mjonMsgDAO.selectSendNumberStatus */
+		SELECT
+			U.USER_ID AS userId
+			, LM.DEPT AS dept
+			, LM.NATIONALITY AS nationality
+			, IFNULL(P.MOBILE_CNT, 0) AS mobileCnt
+			, CASE
+				WHEN LOWER(IFNULL(LM.DEPT, 'p')) = 'c' THEN 4
+				WHEN IFNULL(LM.NATIONALITY, '0') != '0' THEN 2
+				ELSE 3
+			  END AS mobileMaxCnt
+			, IFNULL(P.WIRED_CNT, 0) AS wiredCnt
+			, CASE
+				WHEN LOWER(IFNULL(LM.DEPT, 'p')) = 'c' THEN EC.CERT_CNT
+				ELSE 5
+			  END AS wiredMaxCnt
+			, EC.CERT_CNT AS certCnt
+			, EH.EMPLOYEE_CERT_HSTRY_SEQ AS employeeCertHstrySeq
+			, EH.APPLY_CNT AS applyCnt
+			, EH.AUTH_YN AS authYn
+			, CD.CODE_NM AS authYnTxt
+			, EH.REJECT_CODE AS rejectCode
+			, EH.REJECT_REASON AS rejectReason
+			, DATE_FORMAT(EH.FRST_REGIST_PNTTM, '%Y-%m-%d %H:%i') AS applyPnttm
+		FROM (SELECT #userId# AS USER_ID) U
+		LEFT JOIN LETTNGNRLMBER LM
+			ON U.USER_ID = LM.MBER_ID
+		LEFT JOIN (
+			SELECT
+				USER_ID
+				, SUM(CASE WHEN PHM_LINE_TYPE = 'M' THEN 1 ELSE 0 END) AS MOBILE_CNT
+				, SUM(CASE WHEN PHM_LINE_TYPE IN ('L', 'R') THEN 1 ELSE 0 END) AS WIRED_CNT
+			FROM MJ_PHONE_MEMBER
+			WHERE USER_ID = #userId#
+				AND PHM_TYPE = '01'
+				AND DEL_FLAG = 'N'
+			GROUP BY USER_ID
+		) P ON U.USER_ID = P.USER_ID
+		LEFT JOIN MJ_EMPLOYEE_CERT EC
+			ON U.USER_ID = EC.MBER_ID
+		LEFT JOIN MJ_EMPLOYEE_CERT_HSTRY EH
+			ON EC.MBER_ID = EH.MBER_ID
+			AND EC.LAST_HSTRY_SEQ = EH.EMPLOYEE_CERT_HSTRY_SEQ
+		LEFT JOIN LETTCCMMNDETAILCODE CD
+			ON CD.CODE_ID = 'ITN028'
+			AND CD.USE_AT = 'Y'
+			AND EH.AUTH_YN = CD.CODE
+	</select>
 	
 	<select id="mjonMsgDAO.selectSendPhoneNumberByPhmId" parameterClass="String" resultClass="mjPhoneMemberVO">
 		
@@ -1749,6 +1885,7 @@
 		       PHM_VALUE AS phmValue,
 		       NAME_TYPE AS nameType,
 		       DEPT AS dept,
+		       PHM_LINE_TYPE AS phmLineType,
 		       REJECT_REASON AS rejectReason
 		FROM   mj_phone_member a
 		WHERE  a.PHM_ID = #phmId#
@@ -1792,8 +1929,9 @@
            	A.PHM_DENY_TYPE 	AS phmDenyType ,
         	F.CODE_NM          	AS phmDenyTypeTxt,
          	A.REJECT_REASON		AS rejectReason,
-         	A.NAME_TYPE			AS nameType,
-         	LM.DEPT				AS dept,
+			A.NAME_TYPE			AS nameType,
+			A.PHM_LINE_TYPE		AS phmLineType,
+			LM.DEPT				AS dept,
          	LM.MBTLNUM			AS mbtlNum
   	 	FROM MJ_PHONE_MEMBER A
 		INNER JOIN LETTNGNRLMBER LM
@@ -1856,6 +1994,7 @@
 			, OWNER_NAME
 			, CI
 			, BIRTH_DAY
+			, PHM_LINE_TYPE
 		)
 		VALUES
 		(
@@ -1878,6 +2017,7 @@
 			, #ownerName#
 			, #CI#
 			, #birthDay#
+			, #phmLineType#
 		)
 	</insert>
 	
@@ -2117,7 +2257,20 @@
 		</iterate>
 	
 	</delete>
-	
+
+	<select id="mjonMsgDAO.selectSendPhonNumAndPhmLineTypeList" parameterClass="String" resultClass="mberManageVO">
+		/* mjonMsgDAO.selectSendPhonNumAndPhmLineTypeList */
+		SELECT
+		       PHONE_NUMBER  AS phoneNumber
+		     , PHM_LINE_TYPE AS phmLineType
+		FROM   MJ_PHONE_MEMBER
+		WHERE  USER_ID  = #userId#
+		  AND    AUTH_YN  = 'Y'
+		  AND    PHM_TYPE = '01'
+		ORDER BY BASE_YN DESC
+
+	</select>
+
 	<select id="mjonMsgDAO.selectSendPhonNumList" parameterClass="String" resultClass="String">
 
 	/* mjonMsgDAO.selectSendPhonNumList */
@@ -3819,7 +3972,7 @@
 		and b.MBTLNUM=a.RECEIVE
 		and a.CHECK_NO=#checkNo#
 		
-		and DATE_FORMAT(a.FRST_SEND_PNTTM, '%Y%m%d%H%i%s') > DATE_FORMAT( date_add(now(),interval -300 second), '%Y%m%d%H%i%s')
+		and DATE_FORMAT(a.FRST_SEND_PNTTM, '%Y%m%d%H%i%s') > DATE_FORMAT( date_add(now(),interval -180 second), '%Y%m%d%H%i%s')
 		
 		ORDER BY a.FRST_SEND_PNTTM DESC, a.SEND_ID DESC
 		LIMIT    1
 
src/main/resources/egovframework/sqlmap/let/sym/log/access/UserAccessLog_SQL_Mysql.xml (added)
+++ src/main/resources/egovframework/sqlmap/let/sym/log/access/UserAccessLog_SQL_Mysql.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd">
+
+<sqlMap namespace="userAccessLogVO">
+
+    <typeAlias alias="userAccessLogVO" type="itn.let.sym.log.access.UserAccessLogVO"/>
+    <resultMap id="userAccessLogResultMap" class="userAccessLogVO">
+        <result property="logId" column="LOG_ID"/>
+        <result property="userId" column="USER_ID"/>
+        <result property="accessIp" column="ACCESS_IP"/>
+        <result property="uri" column="URI"/>
+        <result property="menuNm" column="MENU_NM"/>
+        <result property="result" column="RESULT"/>
+        <result property="frstRegistPnttm" column="FRST_REGIST_PNTTM"/>
+    </resultMap>
+    <insert id="userAccessLogDAO.insertUserAccessLog" parameterClass="userAccessLogVO">
+
+        INSERT INTO USER_ACCESS_LOG (
+                    LOG_ID,
+                    USER_ID,
+                    ACCESS_IP,
+                    URI,
+                    MENU_NM,
+                    RESULT,
+                    FRST_REGIST_PNTTM
+        ) VALUES (
+                   #logId#,
+                   #userId#,
+                   #accessIp#,
+                   #uri#,
+                   #menuNm#,
+                   #result#,
+                   CURRENT_TIMESTAMP
+        )
+
+    </insert>
+</sqlMap>(No newline at end of file)
src/main/resources/egovframework/sqlmap/let/sym/mnu/mpm/EgovMenuManage_SQL_Mysql.xml
--- src/main/resources/egovframework/sqlmap/let/sym/mnu/mpm/EgovMenuManage_SQL_Mysql.xml
+++ src/main/resources/egovframework/sqlmap/let/sym/mnu/mpm/EgovMenuManage_SQL_Mysql.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://www.ibatis.com/dtd/sql-map-config-2.dtd">
+<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd">
 
 <sqlMap namespace="MenuManage">
 	<typeAlias  alias="egovMap" type="egovframework.rte.psl.dataaccess.util.EgovMap"/>
@@ -99,6 +99,21 @@
 			</isNotEmpty>
 			LIMIT 1
 	</select>
+
+	<!-- log 용 Menu 조회 -->
+	<select id="menuManageDAO.selectAnonymousMenuNmByUrl" parameterClass="string" resultClass="string">
+			SELECT A.MENU_NM
+			FROM LETTNMENUINFO A
+				 INNER JOIN LETTNMENUCREATDTLS B
+							ON A.MENU_NO = B.MENU_NO
+			     INNER JOIN LETTNPROGRMLIST C
+							ON A.PROGRM_FILE_NM = C.PROGRM_FILE_NM
+			WHERE B.AUTHOR_CODE = 'ROLE_ANONYMOUS'
+		  	AND C.URL = #value#
+		  	AND A.MENU_NM IS NOT NULL
+		  	AND TRIM(A.MENU_NM) != ''
+    		LIMIT 1
+	</select>
 	
 	<!-- 만족도 관리를 위한 조회 -->
 	<select id="menuManageDAO.selectMenuManage_Satis" parameterClass="menuManageVO" resultClass="menuManageVO">
src/main/resources/egovframework/sqlmap/let/sym/site/EgovSiteManage_SQL_Mysql.xml
--- src/main/resources/egovframework/sqlmap/let/sym/site/EgovSiteManage_SQL_Mysql.xml
+++ src/main/resources/egovframework/sqlmap/let/sym/site/EgovSiteManage_SQL_Mysql.xml
@@ -50,8 +50,12 @@
             C.CODE_DC               AS sessionTimeCdText,
             D.CODE_DC           	AS sessionTimeUserCdTxt,
             A.SYS_MAINTENANCE		AS sysMaintenance,
-            A.SYS_MAINTENANCE_IP	AS sysMaintenanceIp
-		FROM SITEMANAGER A 
+            A.SYS_MAINTENANCE_IP	AS sysMaintenanceIp,
+            A.OPERATION_START_TIME	AS operationStartTime,
+            A.OPERATION_END_TIME	AS operationEndTime,
+            A.LUNCH_START_TIME		AS lunchStartTime,
+            A.LUNCH_END_TIME		AS lunchEndTime
+		FROM SITEMANAGER A
 		LEFT JOIN LETTNFILEDETAIL B ON A.FAVICON_FILE_ID = B.ATCH_FILE_ID
 		LEFT JOIN ( 
         	SELECT CODE_NM , CODE , CODE_DC
@@ -66,7 +70,7 @@
             AND     CODE_ID = 'ITN014'
         )
         D ON A.SESSION_TIME_USER_CD = D.CODE
-        
+        LIMIT 1
     </select>
     
     <insert id="siteManagerDAO.insertSiteManager" parameterClass="siteManagerVO" >
 
src/main/resources/egovframework/sqlmap/let/uat/uia/SecondAuthLog_SQL_Mysql.xml (added)
+++ src/main/resources/egovframework/sqlmap/let/uat/uia/SecondAuthLog_SQL_Mysql.xml
@@ -0,0 +1,213 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd">
+
+<sqlMap namespace="secondAuthLogDAO">
+
+    <typeAlias alias="secondAuthLogVO" type="itn.let.uat.uia.service.SecondAuthLogVO"/>
+
+    <resultMap id="secondAuthLogResult" class="secondAuthLogVO">
+        <result property="logId" column="LOG_ID"/>
+        <result property="loginResult" column="LOGIN_RESULT"/>
+        <result property="authStep" column="AUTH_STEP"/>
+        <result property="tryLoginId" column="TRY_LOGIN_ID"/>
+        <result property="reason" column="REASON"/>
+        <result property="accessIp" column="ACCESS_IP"/>
+        <result property="userAgent" column="USER_AGENT"/>
+        <result property="msgId" column="MSG_ID"/>
+        <result property="resultCode" column="RESULT_CODE"/>
+        <result property="frstRegistPnttm" column="FRST_REGIST_PNTTM"/>
+        <result property="receive" column="RECEIVE"/>
+    </resultMap>
+
+    <insert id="secondAuthLogDAO.insertSecondAuthLog" parameterClass="secondAuthLogVO">
+
+        INSERT INTO SECOND_AUTH_LOG (LOG_ID,
+                                     LOGIN_RESULT,
+                                     AUTH_STEP,
+                                     TRY_LOGIN_ID,
+                                     REASON,
+                                     ACCESS_IP,
+                                     USER_AGENT,
+                                     MSG_ID,
+                                     RESULT_CODE,
+                                     FRST_REGIST_PNTTM)
+        VALUES (#logId#,
+                #loginResult#,
+                #authStep#,
+                #tryLoginId#,
+                #reason#,
+                #accessIp#,
+                #userAgent#,
+                #msgId#,
+                #resultCode#,
+                CURRENT_TIMESTAMP)
+
+    </insert>
+
+    <delete id="secondAuthLogDAO.deleteSecondAuthLog" parameterClass="secondAuthLogVO">
+        DELETE
+        FROM SECOND_AUTH_LOG
+        WHERE LOG_ID = #logId#
+    </delete>
+
+    <select id="secondAuthLogDAO.selectSecondAuthLog" parameterClass="secondAuthLogVO" resultMap="secondAuthLogResult">
+        SELECT LOG_ID,
+               LOGIN_RESULT,
+               AUTH_STEP,
+               TRY_LOGIN_ID,
+               REASON,
+               ACCESS_IP,
+               USER_AGENT,
+               MSG_ID,
+               RESULT_CODE,
+               FRST_REGIST_PNTTM
+        FROM SECOND_AUTH_LOG
+        WHERE LOG_ID = #logId#
+
+    </select>
+
+    <select id="secondAuthLogDAO.selectSecondAuthLogCnt" parameterClass="secondAuthLogVO"
+            resultClass="java.lang.Integer">
+
+        SELECT COUNT(*)
+        FROM SECOND_AUTH_LOG
+        WHERE 1 = 1
+
+        <isNotEmpty property="logId">
+            AND LOG_ID = #logId#
+        </isNotEmpty>
+
+        <isNotEmpty property="loginResult">
+            AND LOGIN_RESULT = #loginResult#
+        </isNotEmpty>
+
+        <isNotEmpty property="authStep">
+            AND AUTH_STEP = #authStep#
+        </isNotEmpty>
+
+        <isNotEmpty property="tryLoginId">
+            AND TRY_LOGIN_ID = #tryLoginId#
+        </isNotEmpty>
+
+        <isNotEmpty property="reason">
+            AND REASON = #reason#
+        </isNotEmpty>
+
+        <isNotEmpty property="accessIp">
+            AND ACCESS_IP = #accessIp#
+        </isNotEmpty>
+
+        <isNotEmpty property="msgId">
+            AND MSG_ID = #msgId#
+        </isNotEmpty>
+
+        <isNotEmpty property="resultCode">
+            AND RESULT_CODE = #resultCode#
+        </isNotEmpty>
+
+    </select>
+
+    <select id="secondAuthLogDAO.selectSecondAuthLogList" parameterClass="secondAuthLogVO" resultMap="secondAuthLogResult">
+        SELECT
+        A.LOG_ID,
+        CASE WHEN A.LOGIN_RESULT = 'SUCCESS' THEN '성공'
+             WHEN A.LOGIN_RESULT = 'FAIL' THEN '실패'
+             ELSE A.LOGIN_RESULT
+        END AS LOGIN_RESULT,
+        A.AUTH_STEP,
+        A.TRY_LOGIN_ID,
+        A.REASON,
+        A.ACCESS_IP,
+        A.USER_AGENT,
+        A.MSG_ID,
+        A.RESULT_CODE,
+        S.RECEIVE AS RECEIVE,
+        DATE_FORMAT(A.FRST_REGIST_PNTTM, '%Y-%m-%d %H:%i:%s') AS FRST_REGIST_PNTTM
+        FROM SECOND_AUTH_LOG A
+        LEFT JOIN MJ_SYSTEM_SEND S
+        ON S.SEND_ID = A.MSG_ID
+        WHERE 1 = 1
+        AND TRY_LOGIN_ID = #tryLoginId#
+        <isNotEmpty property="loginResult">
+            AND A.LOGIN_RESULT = #loginResult#
+        </isNotEmpty>
+        <isNotEmpty property="searchStartDate">
+            AND A.FRST_REGIST_PNTTM <![CDATA[ >= ]]> CONCAT(#searchStartDate#, ' 00:00:00')
+        </isNotEmpty>
+        <isNotEmpty property="searchEndDate">
+            AND A.FRST_REGIST_PNTTM <![CDATA[ < ]]> DATE_ADD(CONCAT(#searchEndDate#, ' 00:00:00'), INTERVAL 1 DAY)
+        </isNotEmpty>
+        <isNotEmpty property="searchKeyword">
+            <isEqual property="searchCondition" compareValue="accessIp">
+                AND ACCESS_IP LIKE CONCAT('%', #searchKeyword#, '%')
+            </isEqual>
+            <isEqual property="searchCondition" compareValue="receive">
+                AND REPLACE(S.RECEIVE, '-', '')
+                LIKE CONCAT('%', REPLACE(#searchKeyword#, '-', ''), '%')
+            </isEqual>
+        </isNotEmpty>
+        <isNotEmpty property="searchSortCnd">
+            ORDER BY $searchSortCnd$
+            <isNotEmpty property="searchSortOrd">
+                $searchSortOrd$
+            </isNotEmpty>
+        </isNotEmpty>
+
+        LIMIT #firstIndex#, #recordCountPerPage#
+    </select>
+
+    <select id="secondAuthLogDAO.selectFailureCountSinceReset" parameterClass="secondAuthLogVO"
+            resultClass="java.lang.Integer">
+        SELECT COUNT(*)
+        FROM SECOND_AUTH_LOG A
+        WHERE A.TRY_LOGIN_ID = #tryLoginId#
+          AND A.AUTH_STEP = 'SECONDARY'
+          AND A.LOGIN_RESULT = 'FAIL'
+		  AND A.REASON <![CDATA[ <> ]]> 'SECOND_AUTH_LOCK'
+		  AND A.FRST_REGIST_PNTTM <![CDATA[ > ]]> DATE_SUB(NOW(), INTERVAL 10 MINUTE)
+          AND A.FRST_REGIST_PNTTM <![CDATA[ > ]]>
+              COALESCE(
+                  (
+                      SELECT MAX(B.FRST_REGIST_PNTTM)
+                      FROM SECOND_AUTH_LOG B
+                      WHERE B.TRY_LOGIN_ID = #tryLoginId#
+                        AND B.AUTH_STEP = 'SECONDARY'
+						AND (B.LOGIN_RESULT = 'SUCCESS' OR B.REASON = 'SECOND_AUTH_LOCK')
+                  ),
+                  '1970-01-01 00:00:00'
+              )
+    </select>
+
+    <select id="secondAuthLogDAO.selectActiveSecondAuthLockCount" parameterClass="secondAuthLogVO"
+            resultClass="java.lang.Integer">
+        SELECT COUNT(*)
+        FROM SECOND_AUTH_LOG
+        WHERE TRY_LOGIN_ID = #tryLoginId#
+          AND AUTH_STEP = 'SECONDARY'
+		  AND LOGIN_RESULT = 'FAIL'
+		  AND REASON = 'SECOND_AUTH_LOCK'
+          AND FRST_REGIST_PNTTM <![CDATA[ > ]]> DATE_SUB(NOW(), INTERVAL 5 MINUTE)
+    </select>
+
+    <select id="secondAuthLogDAO.selectSecondAuthLockRemainingSeconds" parameterClass="secondAuthLogVO"
+            resultClass="java.lang.Integer">
+        SELECT COALESCE(
+                   GREATEST(
+                       0,
+                       TIMESTAMPDIFF(
+                           SECOND,
+                           NOW(),
+                           DATE_ADD(MAX(FRST_REGIST_PNTTM), INTERVAL 5 MINUTE)
+                       )
+                   ),
+                   0
+               )
+        FROM SECOND_AUTH_LOG
+        WHERE TRY_LOGIN_ID = #tryLoginId#
+          AND AUTH_STEP = 'SECONDARY'
+          AND LOGIN_RESULT = 'FAIL'
+          AND REASON = 'SECOND_AUTH_LOCK'
+          AND FRST_REGIST_PNTTM <![CDATA[ > ]]> DATE_SUB(NOW(), INTERVAL 5 MINUTE)
+    </select>
+
+</sqlMap>
 
src/main/resources/egovframework/sqlmap/let/uat/uia/SecondAuthMethodLog_SQL_Mysql.xml (added)
+++ src/main/resources/egovframework/sqlmap/let/uat/uia/SecondAuthMethodLog_SQL_Mysql.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd">
+
+<sqlMap namespace="secondAuthMethodLogDAO">
+
+    <typeAlias alias="secondAuthMethodLogVO" type="itn.let.uat.uia.service.SecondAuthMethodLogVO"/>
+
+    <resultMap id="secondAuthMethodLogResult" class="secondAuthMethodLogVO">
+        <result property="logId" column="LOG_ID"/>
+        <result property="mberId" column="MBER_ID"/>
+        <result property="actionType" column="ACTION_TYPE"/>
+        <result property="phoneNo" column="PHONE_NO"/>
+        <result property="verifyType" column="VERIFY_TYPE"/>
+        <result property="accessIp" column="ACCESS_IP"/>
+        <result property="frstRegistPnttm" column="FRST_REGIST_PNTTM"/>
+    </resultMap>
+
+    <insert id="secondAuthMethodLogDAO.insertSecondAuthMethodLog" parameterClass="secondAuthMethodLogVO">
+        INSERT INTO SECOND_AUTH_METHOD_LOG (
+            MBER_ID,
+            ACTION_TYPE,
+            PHONE_NO,
+            VERIFY_TYPE,
+            ACCESS_IP,
+            FRST_REGIST_PNTTM
+        ) VALUES (
+            #mberId#,
+            #actionType#,
+            #phoneNo#,
+            #verifyType#,
+            #accessIp#,
+            CURRENT_TIMESTAMP
+        )
+    </insert>
+
+    <select id="secondAuthMethodLogDAO.selectSecondAuthMethodLogCnt"
+            parameterClass="secondAuthMethodLogVO"
+            resultClass="java.lang.Integer">
+        SELECT COUNT(*)
+        FROM SECOND_AUTH_METHOD_LOG
+        WHERE MBER_ID = #mberId#
+          AND FRST_REGIST_PNTTM <![CDATA[ >= ]]> DATE_SUB(NOW(), INTERVAL 1 YEAR)
+        <isNotEmpty property="actionType">
+            AND ACTION_TYPE = #actionType#
+        </isNotEmpty>
+        <isNotEmpty property="searchStartDate">
+            AND FRST_REGIST_PNTTM <![CDATA[ >= ]]> CONCAT(#searchStartDate#, ' 00:00:00')
+        </isNotEmpty>
+        <isNotEmpty property="searchEndDate">
+            AND FRST_REGIST_PNTTM <![CDATA[ < ]]> DATE_ADD(CONCAT(#searchEndDate#, ' 00:00:00'), INTERVAL 1 DAY)
+        </isNotEmpty>
+    </select>
+
+    <select id="secondAuthMethodLogDAO.selectSecondAuthMethodLogList"
+            parameterClass="secondAuthMethodLogVO"
+            resultMap="secondAuthMethodLogResult">
+        SELECT LOG_ID,
+               MBER_ID,
+               ACTION_TYPE,
+               PHONE_NO,
+               VERIFY_TYPE,
+               ACCESS_IP,
+               DATE_FORMAT(FRST_REGIST_PNTTM, '%Y-%m-%d %H:%i:%s') AS FRST_REGIST_PNTTM
+        FROM SECOND_AUTH_METHOD_LOG
+        WHERE MBER_ID = #mberId#
+          AND FRST_REGIST_PNTTM <![CDATA[ >= ]]> DATE_SUB(NOW(), INTERVAL 1 YEAR)
+        <isNotEmpty property="actionType">
+            AND ACTION_TYPE = #actionType#
+        </isNotEmpty>
+        <isNotEmpty property="searchStartDate">
+            AND FRST_REGIST_PNTTM <![CDATA[ >= ]]> CONCAT(#searchStartDate#, ' 00:00:00')
+        </isNotEmpty>
+        <isNotEmpty property="searchEndDate">
+            AND FRST_REGIST_PNTTM <![CDATA[ < ]]> DATE_ADD(CONCAT(#searchEndDate#, ' 00:00:00'), INTERVAL 1 DAY)
+        </isNotEmpty>
+        ORDER BY FRST_REGIST_PNTTM DESC, LOG_ID DESC
+        LIMIT #firstIndex#, #recordCountPerPage#
+    </select>
+
+    <delete id="secondAuthMethodLogDAO.deleteExpiredSecondAuthMethodLogs">
+        DELETE FROM SECOND_AUTH_METHOD_LOG
+        WHERE FRST_REGIST_PNTTM <![CDATA[ < ]]> DATE_SUB(NOW(), INTERVAL 1 YEAR)
+    </delete>
+
+</sqlMap>
src/main/resources/egovframework/sqlmap/let/uss/umt/EgovUserManage_SQL_Mysql.xml
--- src/main/resources/egovframework/sqlmap/let/uss/umt/EgovUserManage_SQL_Mysql.xml
+++ src/main/resources/egovframework/sqlmap/let/uss/umt/EgovUserManage_SQL_Mysql.xml
@@ -1,6 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://www.ibatis.com/dtd/sql-map-config-2.dtd">
-
+<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd">
 <sqlMap namespace="User">
 
     <typeAlias  alias="egovMap"      type = "egovframework.rte.psl.dataaccess.util.EgovMap"/>
@@ -1216,7 +1215,7 @@
 			, A.EMAIL_YN			AS emailYN
 			, A.SMS_YN				AS smsYN
 			, A.SECU_LOGIN_FLAG     AS secuLoginFlag
-			
+
 		FROM
 			LETTNGNRLMBER A
 		WHERE 1=1
src/main/webapp/WEB-INF/config/egovframework/springmvc/egov-interceptor-servlet.xml
--- src/main/webapp/WEB-INF/config/egovframework/springmvc/egov-interceptor-servlet.xml
+++ src/main/webapp/WEB-INF/config/egovframework/springmvc/egov-interceptor-servlet.xml
@@ -8,6 +8,19 @@
         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
     
 	<interceptors>
+		<!-- 해외 IP 차단 -->
+		<interceptor>
+			<mapping path="/**"/>
+			<exclude-mapping path="/cmm/**"/>
+			<exclude-mapping path="/js/**"/>
+			<exclude-mapping path="/css/**"/>
+			<exclude-mapping path="/images/**"/>
+			<exclude-mapping path="/publish/**"/>
+			<exclude-mapping path="/favicon.ico"/>
+
+			<beans:bean class="itn.com.cmm.interceptor.GeoIpBlockInterceptor"/>
+		</interceptor>
+
 	    <interceptor>
 	        <mapping path="/web/**"/>
 	        <exclude-mapping path="/web/main/mainPage.do"/>
@@ -29,6 +42,16 @@
 	    	<mapping path="/web/user/login/**"/>
 	    	<beans:bean class="itn.com.cmm.interceptor.ipCheck.IPCheckInterceptor"/>
 	    </interceptor> -->
+		<interceptor>
+			<mapping path="/**"/>
+			<exclude-mapping path="/cmm/**"/>
+			<exclude-mapping path="/js/**"/>
+			<exclude-mapping path="/css/**"/>
+			<exclude-mapping path="/images/**"/>
+			<exclude-mapping path="/publish/**"/>
+
+			<beans:bean class="itn.com.cmm.interceptor.UserAccessLogInterceptor"/>
+		</interceptor>
 	</interceptors>
 
 </beans:beans>
(No newline at end of file)
src/main/webapp/WEB-INF/decorators.xml
--- src/main/webapp/WEB-INF/decorators.xml
+++ src/main/webapp/WEB-INF/decorators.xml
@@ -131,6 +131,7 @@
       <pattern>/*Ajax.do</pattern>
       <pattern>/*Ajax_advc.do</pattern>
       <pattern>/*AjaxView.do</pattern>
+	  <pattern>/web/cert/resetPhone.do</pattern><!-- KMC 휴대폰번호 변경 callback -->
       <pattern>/itn/mail/intSendMailForm.do</pattern>
       <pattern>/uss/umt/user/EgovGnrlselectedUserView.do</pattern>
       <pattern>/uss/umt/user/EgovGnrlselectedUserViewPopup.do</pattern>
@@ -193,4 +194,4 @@
 	<decorator name="weblayout" page="/WEB-INF/jsp/layout/siteCheck.jsp">
 		<pattern>*</pattern>
 	</decorator>
-</decorators>
(No newline at end of file)
+</decorators>
src/main/webapp/WEB-INF/jsp/layout/adminLayout.jsp
--- src/main/webapp/WEB-INF/jsp/layout/adminLayout.jsp
+++ src/main/webapp/WEB-INF/jsp/layout/adminLayout.jsp
@@ -21,8 +21,10 @@
 		<script type="text/javascript" src="<c:url value='/js/EgovCalPopup.js'/>"></script>
 		<c:if test="${!empty loginId}">
 			<!-- 자동완성 START-->
-			<link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
-			<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
+			<!-- <link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
+			<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script> -->
+			<link rel="stylesheet" href="/publish/css/jquery-ui.css">
+			<script type="text/javascript" src="/publish/js/jquery-ui.min.js"></script>
 			<script src="<c:url value='/js/recent_search.js' />"></script>
 			<!-- 자동완성 END -->
 		</c:if>
src/main/webapp/WEB-INF/jsp/layout/include/defaultResource.jsp
--- src/main/webapp/WEB-INF/jsp/layout/include/defaultResource.jsp
+++ src/main/webapp/WEB-INF/jsp/layout/include/defaultResource.jsp
@@ -22,7 +22,7 @@
 		<meta name="theme-color" content="#ffffff">
 		<meta property="og:image" content="/publish/images/kakao.jpg">
 		
-		<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@100;300;400;500;700;900&display=swap">
+		<!-- <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@100;300;400;500;700;900&display=swap"> -->
 		<link rel="stylesheet" href="/publish/css/reset.css">
 		<link rel="stylesheet" href="/publish/css/jquery.mCustomScrollbar.css">
 		<link rel="stylesheet" href="/publish/css/common.css">
@@ -81,7 +81,8 @@
 				<script type="text/javascript" src="/js/fileupload/jquery.iframe-transport.js"></script>
 				<script type="text/javascript" src="/js/fileupload/jquery.fileupload.js"></script>
 		
-		<script type="text/javascript" src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
+		<!-- <script type="text/javascript" src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script> -->
+		<script type="text/javascript" src="/publish/js/jquery-ui.min.js"></script>
 		<script type="text/javascript" src="/js/MJUtill.js?date=202401291830"></script>
 		
 		<!-- Uncaught TypeError: e.widget is not a function로 인해 가장 마지막에 선언_이준호_220510 -->
src/main/webapp/WEB-INF/jsp/layout/popWebLayout.jsp
--- src/main/webapp/WEB-INF/jsp/layout/popWebLayout.jsp
+++ src/main/webapp/WEB-INF/jsp/layout/popWebLayout.jsp
@@ -11,7 +11,7 @@
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <title>문자고</title>
 
-    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@100;300;400;500;700;900&display=swap" rel="stylesheet">
+    <!-- <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@100;300;400;500;700;900&display=swap" rel="stylesheet"> -->
     <link rel="stylesheet" href="/publish/css/reset.css">
     <link rel="stylesheet" href="/publish/css/jquery.mCustomScrollbar.css">
     <link rel="stylesheet" href="/publish/css/common.css">
 
src/main/webapp/WEB-INF/jsp/web/api/ApiSentApprovalListAjax.jsp (added)
+++ src/main/webapp/WEB-INF/jsp/web/api/ApiSentApprovalListAjax.jsp
@@ -0,0 +1,220 @@
+<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="ui" uri="http://egovframework.gov/ctl/ui"%>
+<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+<%@ taglib prefix="ec" uri="/WEB-INF/tld/ecnet_tld.tld"%>
+<%@ page import="itn.com.cmm.LoginVO" %>
+<script src="/src/main/webapp/publish/js/content.js"></script>
+<script src="/src/main/webapp/publish/js/popupLayer.js"></script>
+<script type="text/javascript">
+
+function fn_sentDetailView(msgGroupId, msgType) {
+	$("#searchForm #msgGroupId").val(msgGroupId);
+	let url =  "/web/mjon/msgsent/msgSentDetailView.do";
+	if(msgType == "8"){
+		url = "/web/kakao/sent/selectKakaoSentDetailView.do";
+	}
+
+	$("#searchForm").attr("action", url);
+	$("#searchForm").submit();
+}
+
+
+function fnApprove(msgGroupId){
+
+	var form = document.detailForm;
+	var loginVO = '${LoginVO}';
+	form.msgGroupIdList.value = msgGroupId;
+
+	if(loginVO == "" || loginVO == null){
+		alert("로그인 후 이용이 가능합니다.");
+		return false;
+	}
+
+	var data = new FormData(form);
+
+	url = "/web/api/updateApproveDataAjax.do";
+
+	$.ajax({
+		type: "POST",
+		url: url,
+		data: data,
+		dataType:'json',
+		async: true,
+		processData: false,
+		contentType: false,
+		cache: false,
+		success: function (returnData, status) {
+			if(status == 'success'){
+				if("fail"==returnData.result){
+
+					alert(returnData.message);
+					return false;
+				}
+				linkPage(1);
+			} else if(status== 'fail'){
+				alert(returnData.message);
+			}
+		},
+		error: function (e) {
+			alert("승인이 실패하였습니다."); console.log("ERROR : ", e);
+		}
+	});
+
+}
+
+
+</script>
+	<div class="list_info">
+		<p>총 <span class="c_e40000"><c:out value="${totalRecordCount}"/></span>건</p>
+		<div>
+			<select id="pageUnitS" class="selType2">
+				<option value="10" <c:if test="${paginationInfo.recordCountPerPage == '10'}">selected</c:if> >10개보기</option>
+				<option value="20" <c:if test="${paginationInfo.recordCountPerPage == '20'}">selected</c:if> >20개보기</option>
+				<option value="30" <c:if test="${paginationInfo.recordCountPerPage == '30'}">selected</c:if> >30개보기</option>
+				<option value="100" <c:if test="${paginationInfo.recordCountPerPage == '100'}">selected</c:if> >100개보기</option>
+			</select>
+		</div>
+	</div>
+
+	<div class="tb_wrap">
+		<table class="tType4">
+			<colgroup>
+				<col style="widht:40px;"/>
+				<col style="width:15%;"/>
+				<col style="width:8%;"/>
+				<col style="width:40%;"/>
+				<col style="width:8%;"/>
+				<col style="width:8%;"/>
+				<col style="width:15%"/>
+			</colgroup>
+
+			<thead>
+			<tr>
+				<th>
+					<label for="allCheck" class="label">전체 선택</label>
+					<input type="checkbox" id="allCheck" name="allCheck">
+				</th>
+				<th>발송일시</th>
+				<th>발송구분</th>
+				<th>내용</th>
+				<th>발송건수</th>
+				<th>승인상태</th>
+				<th>관리</th>
+			</tr>
+			</thead>
+			<tbody>
+			<c:choose>
+				<c:when test="${not empty resultAllSentList}">
+					<c:forEach var="result" items="${resultAllSentList}" varStatus="status">
+						<tr>
+							<td>
+								<label for="msgApproveChk${status.count}" class="label">선택</label>
+								<c:choose>
+									<c:when test="${result.apiAprvYn eq 'Y'}">
+										<input type="checkbox" disabled>
+									</c:when>
+									<c:otherwise>
+										<input type="checkbox" id="msgApproveChk${status.count}" name="msgApproveChk" value="${result.msgGroupId}">
+									</c:otherwise>
+								</c:choose>
+							</td>
+							<td>
+								<c:choose>
+									<c:when test="${result.delayYn eq 'Y' && result.delayCompleteYn eq 'N'}">
+										<c:choose>
+											<c:when test="${result.curState eq '0'}">
+												<p>${result.delayOrgTime}</p>
+											</c:when>
+											<c:otherwise>
+												<p>${result.reqDate}</p>
+											</c:otherwise>
+										</c:choose>
+
+									</c:when>
+									<c:otherwise>
+										<p>${result.reqDate}</p>
+									</c:otherwise>
+								</c:choose>
+							</td>
+							<td>
+								<c:choose>
+									<c:when test="${result.msgType eq '4' || result.msgType eq '6'}">
+										문자
+									</c:when>
+									<c:when test="${result.msgType eq '8'}">
+										알림톡
+									</c:when>
+									<c:otherwise>
+										문자
+									</c:otherwise>
+								</c:choose>
+							</td>
+							<td>
+								<p class="tLeft">
+									<a href="#none" onclick="fn_sentDetailView('${result.msgGroupId}','${result.msgType}')">
+										<c:choose>
+											<c:when test="${result.subjectChkYn eq 'Y' }">
+												<c:out value="${result.subject }" />
+											</c:when>
+											<c:otherwise>
+												<c:out value="${result.smsTxt}" />
+											</c:otherwise>
+										</c:choose>
+									</a>
+								</p>
+							</td>
+							<td>
+								<fmt:formatNumber value="${result.msgGroupCnt}" type="number" groupingUsed="true" />
+							</td>
+							<c:choose>
+								<c:when test="${result.apiAprvYn eq 'N' || result.apiAprvYn eq null}">
+									<td class="c_e40000 fwMd">미승인</td>
+								</c:when>
+								<c:when test="${result.apiAprvYn eq 'Y'}">
+									<td class="c_002c9a fwMd">승인완료</td>
+								</c:when>
+								<c:otherwise>
+									<td class="c_e40000 fwMd">미승인</td>
+								</c:otherwise>
+							</c:choose>
+							<td>
+								<c:choose>
+									<c:when test="${result.apiAprvYn eq 'N' || result.apiAprvYn eq null}">
+										<button type="button" class="btnType btnType5" onclick="fnApprove('${result.msgGroupId}')">승인</button>
+									</c:when>
+								<c:when test="${result.apiAprvYn eq 'Y' && result.apiAprvDt ne null}">
+									<p>${result.apiAprvDt}</p>
+								</c:when>
+								<c:otherwise>
+									<button type="button" class="btnType btnType5" onclick="fnApprove('${result.msgGroupId}')">승인</button>
+								</c:otherwise>
+								</c:choose>
+
+							</td>
+						</tr>
+					</c:forEach>
+				</c:when>
+				<c:otherwise>
+					<tr>
+						<td colspan="7">발송 내역이 없습니다.</td>
+					</tr>
+				</c:otherwise>
+			</c:choose>
+			</tbody>
+		</table>
+	</div>
+
+	<div class="table_btn clearfix">
+		<div class="table_btn_left">
+			<button id="btnApprove" type="button" class="btnType btnType2" data-tooltip="approve_popup">선택승인</button>
+		</div>
+
+		<c:if test="${!empty resultAllSentList}">
+			<ul class="pagination">
+				<ui:pagination paginationInfo = "${paginationInfo}"  type="imageWeb" jsFunction="linkPage" />
+			</ul>
+		</c:if>
+
+	</div>(No newline at end of file)
 
src/main/webapp/WEB-INF/jsp/web/api/approvalList.jsp (added)
+++ src/main/webapp/WEB-INF/jsp/web/api/approvalList.jsp
@@ -0,0 +1,248 @@
+<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="ui" uri="http://egovframework.gov/ctl/ui"%>
+<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+<%@ page import="itn.com.cmm.LoginVO" %>
+
+<script type="text/javascript">
+	$(document).ready(function(){
+		linkPage($('#searchForm #pageIndex').val());
+
+		fn_setActiveTab($('#searchForm #msgType').val())
+		fn_activateTab($('#searchForm #apiAprvYn').val())
+
+		$(document).on('click', 'button[name="btnApprvYn"]', function (){
+			fn_activateTab($(this).data('info'));
+			linkPage(1);
+		});
+
+		$(document).on('change','#pageUnitS', function(){
+			setPageUnit($(this).val());
+			linkPage(1);
+		});
+
+		$(document).on('change', '#allCheck', function () {
+			var checked = $(this).prop('checked');
+			$('#approveListCont input[name="msgApproveChk"]:not(:disabled)').prop('checked', checked);
+		});
+
+		$(document).on('click', '#btnApprove', function (e){
+			var $checked = $('#approveListCont input[name="msgApproveChk"]:checked:not(:disabled)');
+			$('#approvePopup .title_text b').text($checked.length);
+		});
+
+	});
+
+	function setPageUnit(val){
+		$('#pageUnit').val(val);
+	}
+
+	function fn_activateTab(tabInfo) {
+		// 1. data-info 값을 가진 버튼 요소 찾기
+		var $button = $('button[name="btnApprvYn"][data-info="' + tabInfo + '"]');
+
+		// 2. 해당 버튼이 속한 탭 활성화 처리
+		$button.closest('ul').find('.tab').removeClass('active');
+		$button.closest('.tab').addClass('active');
+
+		// 3. hidden input 요소에 값 설정
+		$('#apiAprvYn').val(tabInfo);
+	}
+
+	function linkPage(pageNo){
+		var form = document.searchForm;
+		form.pageIndex.value = pageNo;
+
+		var sendData =  $(document.searchForm).serializeArray();
+		$("#approveListCont").html('<div class="list_info"><table class="tType4"><tbody><tr><td colspan="12">LOADING...</td></tr></tbody></table></div>');
+		$("#approveListCont").load("/web/api/selectApiMsgSentListViewAjax.do", sendData ,function(response, status, xhr){
+		});
+
+	}
+
+	function fnTabLoad(tabType){
+		fn_activateTab('');
+		setPageUnit('10');
+
+		fn_setActiveTab(tabType);
+		linkPage(1);
+	}
+
+	function fn_setActiveTab(tabType) {
+		var $tabs = $(".list_tab_wrap2 li");
+		$tabs.removeClass("active").find("button").removeAttr("title");
+
+		$tabs.each(function() {
+			var buttonText = $(this).find("button").text();
+			if ((tabType === '' && buttonText === "전체") ||
+					(tabType === 'M' && buttonText === "문자") ||
+					(tabType === 'K' && buttonText === "알림톡")) {
+				$(this).addClass("active").find("button").attr("title", "선택됨");
+			}
+		});
+
+		$('#msgType').val(tabType);
+	}
+
+	function fnApproveAll() {
+
+		var $checked = $('#approveListCont input[name="msgApproveChk"]:checked:not(:disabled)');
+
+		if ($checked.length === 0) {
+			alert("한 개 이상의 승인건을 선택하세요.");
+			$('#approvePopup .tooltip-close[data-focus="approve_popup-close"]').first().trigger('click');
+			return false;
+		}
+
+		var msgId = [];
+
+		$checked.each(function () {
+			msgId.push($(this).val());
+		});
+
+		console.log("일괄 승인 대상:", msgId);
+
+		var loginVO = '${LoginVO}';
+
+		if (loginVO == "" || loginVO == null) {
+			alert("로그인 후 이용이 가능합니다.");
+			return false;
+		}
+
+		// 기존 form 사용
+		var form = document.detailForm;
+
+		// msgGroupId들을 comma로 전달
+		form.msgGroupIdList.value = msgId.join(",");
+
+		var data = new FormData(form);
+
+		$.ajax({
+			type: "POST",
+			url: "/web/api/updateApproveDataAjax.do",
+			data: data,
+			dataType: "json",
+			async: true,
+			processData: false,
+			contentType: false,
+			cache: false,
+
+			success: function (returnData, status) {
+
+				if (status === "success") {
+					if (returnData.result === "fail") {
+						alert(returnData.message);
+						return false;
+					}
+					// 목록 새로고침
+					linkPage(1);
+				} else {
+					alert("승인이 실패하였습니다.");
+				}
+				$('#approvePopup .tooltip-close[data-focus="approve_popup-close"]').first().trigger('click');
+			},
+
+			error: function (e) {
+				console.log("ERROR : ", e);
+				alert("승인이 실패하였습니다.");
+				$('#approvePopup .tooltip-close[data-focus="approve_popup-close"]').first().trigger('click');
+			}
+		});
+
+		return false;
+	}
+
+</script>
+<style>
+
+	.padding_add > p.padding_add > p
+	{
+		padding-left: 30px;
+		padding-bottom: 8px;
+	}
+
+	.padding_add > div > p
+	{
+		padding-left: 60px;
+		padding-bottom: 8px;
+	}
+</style>
+<!-- 발송내역 승인 -->
+<!-- //발송내역 승인 -->
+<!-- content 영역 -->
+<div class="inner">
+	<!-- send top -->
+	<div class="send_top">
+
+		<c:import url="./top_tepMenu.jsp" />
+
+		<div class="api_guide_cont api_send_cont current">
+			<div class="heading">
+				<h2>발송내역관리</h2>
+			</div>
+
+			<ul class="info">
+				<li>- API를 통해 발송된 내역을 확인하고 사후 승인할 수 있습니다.</li>
+				<li>- 미승인 건은 여러 건을 선택하여 일괄 승인할 수 있습니다.</li>
+				<li>- 미승인 건은 로그인 시 반복 안내됩니다.</li>
+			</ul>
+
+			<form id="searchForm" name="searchForm" method="post">
+				<input type="hidden" id="pageIndex" name="pageIndex" value="<c:out value="${searchVO.pageIndex}" />" />
+				<input type="hidden" id="pageUnit" name="pageUnit" value="<c:out value="${searchVO.pageUnit}" />" />
+				<input type="hidden" id="msgGroupId" name="msgGroupId" value=""/>
+				<input type="hidden" id="msgGroupIdList" name="msgGroupIdList" value=""/>
+				<input type="hidden" id="msgType" name="msgType" value="${searchVO.searchCondition01}"/>
+				<input type="hidden" id="apiAprvYn" name="apiAprvYn" value="${searchVO.searchCondition02}"/>
+
+				<div class="list_tab_wrap2 type2">
+					<!-- tab button -->
+					<ul class="list_tab">
+						<li class="tab active"><button type="button" onclick="fnTabLoad(''); return false;">전체</button></li>
+						<li class="tab"><button type="button" onclick="fnTabLoad('M'); return false;">문자</button></li>
+						<li class="tab"><button type="button" onclick="fnTabLoad('K'); return false;">알림톡</button></li>
+					</ul>
+					<!--// tab button -->
+				</div>
+
+				<div class="table_tab_wrap">
+					<!-- tab button -->
+					<ul>
+						<li class="tab ${empty searchVO.searchCondition_01 ? 'active' : ''}"><button type="button" name="btnApprvYn" type="button" data-info="">전체</button></li>
+						<li class="tab ${searchVO.searchCondition_01 == '0' ? 'active' : ''}"><button name="btnApprvYn" type="button" data-info="N">미승인</button></li>
+						<li class="tab ${searchVO.searchCondition_01 == '1' ? 'active' : ''}"><button name="btnApprvYn" type="button" data-info="Y">승인완료</button></li>
+					</ul>
+					<!--// tab button -->
+				</div>
+
+				<div class="table_cont current" id="approveListCont"></div>
+
+			</form>
+
+		</div><!--// send top -->
+	</div>
+
+	<form name="detailForm" id="detailForm" method="post">
+		<input type="hidden" name="msgGroupIdList" value=""/>
+		<input type="hidden" name="apiAprvYn" value="Y"/>
+	</form>
+
+	<div class="tooltip-wrap">
+		<div class="popup-com approve_popup" id="approvePopup" data-tooltip-con="approve_popup" data-focus="approve_popup" data-focus-prev="approve_popup-close" style="width:420px;">
+			<div class="popup_heading">
+				<p>발송내역 승인</p>
+				<button type="button" class="tooltip-close" data-focus="approve_popup-close"><img src="/publish/images/content/layerPopup_close.png" alt="팝업 닫기"></button>
+			</div>
+			<div class="layer_in" style="padding:40px 30px;">
+				<p class="title_text">체크한 <b class="fwBold c_002c9a">3</b>건을 일괄 승인하시겠습니까?</p>
+				<div class="popup_btn" style="margin:30px 0 0 0;gap:8px;">
+					<button type="button" class="btnType btnType2" onClick="javascript:fnApproveAll(); return false;">승인</button>
+					<button type="button" class="btnType fill_gray tooltip-close">취소</button>
+				</div>
+			</div>
+		</div>
+	</div>
+
+</div>
+
src/main/webapp/WEB-INF/jsp/web/api/top_tepMenu.jsp
--- src/main/webapp/WEB-INF/jsp/web/api/top_tepMenu.jsp
+++ src/main/webapp/WEB-INF/jsp/web/api/top_tepMenu.jsp
@@ -106,4 +106,5 @@
 			<li class="tab"><button type="button" class="goPage" id="apiSpec" data-info="<c:url value="/web/api/apiSpec.do" />">개발 가이드</button></li>
 			<li class="tab"><button type="button" class="goPage" id="examDown" data-info="<c:url value="/web/api/examDown.do" />">예제 다운로드</button></li>
 			<li class="tab"><button type="button" class="goPage" id="appMgmt" data-info="<c:url value="/web/api/appMgmt.do" />" >신청/관리</button></li>
+            <li class="tab"><button type="button" class="goPage" id="approvalList" data-info="<c:url value="/web/api/approvalList.do" />" >발송내역관리</button></li>
 	    </ul>
src/main/webapp/WEB-INF/jsp/web/com/webCommonHeader.jsp
--- src/main/webapp/WEB-INF/jsp/web/com/webCommonHeader.jsp
+++ src/main/webapp/WEB-INF/jsp/web/com/webCommonHeader.jsp
@@ -170,19 +170,7 @@
 
 	//등급제 시행 ON/OFF 체크(비로그인)
 	//getMberSettingDetailByNotLogin_Header();			
-	
-	var secuLoginFlag = '${mberManageVO.secuLoginFlag}';
-    
-	const $securityBox = $('.security_box');
 
-	// secuLoginFlag 값에 따라 클래스 및 내용 변경
-	$securityBox
-		.removeClass('green_box red_box')
-		.addClass(secuLoginFlag === 'Y' ? 'green_box' : 'red_box')
-		.find('.state')
-		.text(secuLoginFlag === 'Y' ? 'ON' : 'OFF')
-		.removeClass('on off')
-		.addClass(secuLoginFlag === 'Y' ? 'on' : 'off');
 });	
 
 //등급제 시행 ON/OFF 체크(비로그인)
@@ -401,18 +389,8 @@
 			    dataType:'json',
 			    success : function(data) {
 			      if (data.status=="success"){
-	
-			    	    document.loginForm.passFlag.value = data.passFlag;
-				    	if(data.passFlag == "Y")
-			    		{
-				    		actionLogin_end();
-			    		}
-				    	else
-				    	{
-							goLoginPage(id.val(), pw.val(), "secure");
-				    	}
-			   		}else if (data.status=="fail"){
-			   			
+					  goLoginPage(id.val(), pw.val(), "secure");
+				  }else if (data.status=="fail"){
 			   			if (data.returnType == "A"){
 							//휴면 회원
 			   				document.loginForm.action="<c:url value='/web/user/humanPage.do'/>";
@@ -427,12 +405,9 @@
 			   				document.cmpChangeForm.action="<c:url value='/web/user/login/loginRestrictionUse.do'/>";
 					 	    document.cmpChangeForm.submit();
 			   			}else{
-							//로그인 실패 page이동
 			   				goLoginPage(id.val(), pw.val(), data.msg);
 			   			}
 			   		}
-			      
-		      	//alert(JSON.stringify(data));
 				      	console.log("sucess data1 : " + JSON.stringify(data));        	
 					if (data.isSuccess == true) {
 				      	console.log("fn_click_banner_add_stat sucess data2 : " + JSON.stringify(data));
@@ -1657,6 +1632,7 @@
 	            						<li><a href="<c:out value='/web/api/apiSpec.do'/>" >개발 가이드</a></li>
 	            						<li><a href="<c:out value='/web/api/examDown.do'/>" >예제 다운로드</a></li>
 	            						<li><a href="<c:out value='/web/api/appMgmt.do'/>" >신청/관리</a></li>
+										<li><a href="<c:out value='/web/api/approvalList.do'/>" >발송내역관리</a></li>
 	            					</ul>
 	            				</dd>
 	            			</dl> --%>
@@ -1792,9 +1768,9 @@
 		                        		</c:otherwise>
 		                        	</c:choose>
 		                        </div>
-		                        <div class="session" style="width:110px">
+		                        <div class="session">
 		                        	<div>
-										<span id="ViewTimer" />
+										<span id="ViewTimer"></span>
 									</div>
 									<button type="button" onclick="sessionExpend(); return false;">연장</button>
 									<!-- <a href="#" onclick="sessionExpend(); return false;">
@@ -1804,10 +1780,10 @@
 		                        <!-- <button type="button" class="btnType btnType3" onclick="location.href='/web/user/mberInfoIndex.do'">마이페이지</button> -->
 		                        <button type="button" class="btnType btnType1" onclick="location.href='/web/uat/uia/actionLogout.do'">로그아웃</button>
 		                        
-								
-								<div class="security_box">
-									<a href="<c:out value="/web/user/mberSecureLogin.do"/>" class="title">보안로그인</a><!-- 보안로그인 클릭시 보안로그인 홈페이지로 -->
-									<a href="#" id="secuLoginFlag" class="state"></a><!-- ON/OFF 클릭시 이벤트 발생 -->
+
+								<div class="security_box yellow_box">
+									<i class="icon security"></i>
+									<a href="<c:out value="/web/user/mberSecureLogin.do"/>" class="title">보안로그인</a>
 								</div>
 								
 								
src/main/webapp/WEB-INF/jsp/web/cop/bbs/include/agree_private/agreeSelectVersion.jsp
--- src/main/webapp/WEB-INF/jsp/web/cop/bbs/include/agree_private/agreeSelectVersion.jsp
+++ src/main/webapp/WEB-INF/jsp/web/cop/bbs/include/agree_private/agreeSelectVersion.jsp
@@ -9,8 +9,7 @@
     <p>이용약관 개정이력 보기</p>
     <div class="privacy_content_select_wrap"><!-- option 선택 시 해당페이지로 이동 -->
         <select name="termsMastNo" id="termsMastNo" class="selType2 version_change_select" onchange="getTermsOtherVersion();">
-        	<option value="2026" <c:if test="${termsMstNo eq '2026'}"> selected</c:if> >2026.10.15.</option>
-            <option value="2025" <c:if test="${termsMstNo eq '2025'}"> selected</c:if> >2025.07.30 ~ 2026.10.14. 적용</option>
+            <option value="2025" <c:if test="${termsMstNo eq '2025'}"> selected</c:if> >2025.07.30</option>
             <option value="2024" <c:if test="${termsMstNo eq '2024'}"> selected</c:if> >2024.11.29. ~ 2025.07.29. 적용</option>
             <option value="2023" <c:if test="${termsMstNo eq '2023'}"> selected</c:if> >2023.04.26. ~ 2024.11.28. 적용</option>
             <option value="2022" <c:if test="${termsMstNo eq '2022'}"> selected</c:if> >2022.08.01. ~ 2023.04.25. 적용</option>
src/main/webapp/WEB-INF/jsp/web/cop/bbs/include/bbsTabType.jsp
--- src/main/webapp/WEB-INF/jsp/web/cop/bbs/include/bbsTabType.jsp
+++ src/main/webapp/WEB-INF/jsp/web/cop/bbs/include/bbsTabType.jsp
@@ -8,7 +8,7 @@
 
        			<ul class="tabType4">
 					<li class="tab"><button type="button" onclick="TabType5(this,'1');location.href='/web/cop/bbs/NoticeList.do'">공지사항</button></li>
-					<li class="tab"><button type="button" onclick="TabType5(this,'2');location.href='/web/cop/bbs/TermsList.do?menuNm=02&termsMastNo=2025'">이용약관</button></li>
+					<li class="tab"><button type="button" onclick="TabType5(this,'2');location.href='/web/cop/bbs/TermsList.do'">이용약관</button></li>
 					<li class="tab"><button type="button" onclick="TabType5(this,'3');location.href='/web/cop/bbs/FaqList.do'">자주하는 질문</button></li>
 					<li class="tab"><button type="button" onclick="TabType5(this,'4');location.href='/web/cop/bbs/QnaList.do'">1:1 문의</button></li>
 					<li class="tab"><button type="button" onclick="TabType5(this,'5');location.href='/web/cop/bbs/SuggestList.do'">불편신고</button></li>
src/main/webapp/WEB-INF/jsp/web/login/EgovLoginGnrlUsr_back_250325.jsp
--- src/main/webapp/WEB-INF/jsp/web/login/EgovLoginGnrlUsr_back_250325.jsp
+++ src/main/webapp/WEB-INF/jsp/web/login/EgovLoginGnrlUsr_back_250325.jsp
@@ -182,15 +182,7 @@
 			      if (data.status=="success"){
 
 			    	    document.loginForm2.passFlag.value = data.passFlag;
-				    	if(data.passFlag == "Y")
-			    		{
-				    		actionLogin_end();
-			    		}
-				    	else
-				    	{
-					    	  
-					    	//번호인증팝업 띄우기
-					    	$("#input_list_item_1").hide();
+				    		$("#input_list_item_1").hide();
 					    	$("#input_list_item_2").hide();
 					    	$("#input_list_item_3").hide();
 					    	$("#input_list_item_0").hide();
@@ -207,9 +199,6 @@
 					    			text: item.mbtlnum+'('+item.certAlias+')'  // option의 화면에 보이는 텍스트
 					    		}));
 					    	});
-		
-				    	}
-				    	
 			   		}else if (data.status=="fail"){
 			   			
 			   			if (data.returnType == "A"){
@@ -545,7 +534,6 @@
     <input type="hidden" name="goEventPay" value="${goEventPay}"/>
     <input type="hidden" id="hp" name="hp">
     <input type="hidden" id="num" name="num">
-    <input type="hidden" id="passFlag" name="passFlag">
     
     <div class="send_top">
 		<div class="mypage_content current">
 
src/main/webapp/WEB-INF/jsp/web/login/include/credentials.jspf (added)
+++ src/main/webapp/WEB-INF/jsp/web/login/include/credentials.jspf
@@ -0,0 +1,207 @@
+<%@ page pageEncoding="UTF-8" %>
+<script type="text/javascript">
+    function press(event) {
+        if (event.keyCode === 13) {actionCredentials();}
+    }
+
+    function actionCredentials() {
+        const $id = $("#id_text2");
+        const $password = $("#password_text2");
+
+        const id = $.trim($id.val());
+        const password = $password.val();
+
+        $id.val(id);
+
+        if (!id) {
+            alert("아이디를 입력하세요.");
+            $id.focus();
+            return;
+        }
+
+        if (!password) {
+            alert("비밀번호를 입력하세요.");
+            $password.focus();
+            return;
+        }
+
+        // RSA
+        const rsa = new RSAKey();
+        rsa.setPublic($("#RSAModulus2").val(), $("#RSAExponent2").val());
+
+        document.loginForm2.id.value = rsa.encrypt(id.toLowerCase());
+        document.loginForm2.password.value = rsa.encrypt(password);
+
+        $.ajax({
+            type: "POST",
+            url: "<c:url value='/web/user/login/actionSecurityLoginBeforeHpAjax.do' />",
+            data: $("form[name=loginForm2]").serialize(),
+            dataType: "json",
+
+            success: function (data) {
+                if (data.status === "success") {
+					if (data.data && data.data.id) {
+						$("#userId").val(data.data.id);
+					}
+                    initVerification(data.mberCertPhoneList);
+                    showVerification();
+                    return;
+                }
+
+                if (data.returnType === "A") {
+                    document.loginForm2.action = "<c:url value='/web/user/humanPage.do'/>";
+                    document.loginForm2.submit();
+                    return;
+                }
+
+                if (data.returnType === "B") {
+                    document.cmpChangeForm.mberId.value = data.mberId;
+                    document.cmpChangeForm.action = "<c:url value='/web/user/login/membershipAttachDocBefore.do'/>";
+                    document.cmpChangeForm.submit();
+
+                    return;
+                }
+
+                if (data.returnType === "C") {
+                    document.cmpChangeForm.mberId.value = data.mberId;
+                    document.cmpChangeForm.action = "<c:url value='/web/user/login/loginRestrictionUse.do'/>";
+                    document.cmpChangeForm.submit();
+
+                    return;
+                }
+
+				if (data.locked) {
+					$("#credentials_frame .mem_btn5")
+						.prop("disabled", true)
+						.addClass("disabled");
+				}
+
+                if (data.count !== undefined && data.count <= 4) {
+                    showCredentialsError(data.count);
+                    return;
+                }
+
+                showCredentialsError(data.msg || "아이디 또는 비밀번호가 올바르지 않습니다.");
+            },
+
+            error: function (xhr, status, error) {
+                console.log(
+                    "credentials error",
+                    xhr,
+                    status,
+                    error
+                );
+
+                showCredentialsError(
+                    "로그인 처리 중 오류가 발생했습니다."
+                );
+            }
+        });
+    }
+
+    function showCredentialsError(message) {
+        $("#credentials_error").html("<i></i><p>" + message + "</p>").show();
+    }
+
+    function fn_passView(obj) {
+        const $password = $("#password_text2");
+
+        if ($password.prop("type") === "text") {
+            $password.prop("type", "password");
+            $(obj).find("img").attr("src", "/publish/images/content/password_protect.png");
+        } else {
+            $password.prop("type", "text");
+
+            $(obj).find("img").attr("src", "/publish/images/content/password_protect2.png");
+        }
+    }
+
+</script>
+
+<div id="credentials_frame">
+    <div class="input_list_item">
+        <div class="input_left">아이디</div>
+
+        <div class="input_right">
+            <label for="id_text2" class="label">아이디</label>
+            <input type="text"
+                   id="id_text2"
+                   class="list_inputType1"
+                   placeholder="아이디를 입력해주세요"
+                   onfocus="this.placeholder=''"
+                   onblur="this.placeholder='아이디를 입력해주세요'"
+                   onkeypress="press(event);"
+                   maxlength="20"/>
+        </div>
+
+        <div class="login_del">
+            <button type="button" onclick="textClear('id_text2')" tabindex="-1">
+                <img src="/publish/images/content/login_del.png" alt="입력 삭제">
+            </button>
+        </div>
+
+    </div>
+
+
+    <div class="input_list_item">
+        <div class="input_left">비밀번호</div>
+
+        <div class="input_right">
+            <label for="password_text2" class="label">비밀번호 입력</label>
+
+            <input type="password"
+                   id="password_text2"
+                   class="list_inputType1"
+                   placeholder="비밀번호를 입력해주세요"
+                   onfocus="this.placeholder=''"
+                   onblur="this.placeholder='비밀번호를 입력해주세요'"
+            onkeypress="press(event);"
+            >
+        </div>
+
+        <div class="login_del">
+
+            <button type="button"
+                    onclick="fn_passView(this); return false;"
+                    tabindex="-1">
+
+                <img id="passViewImg"
+                     src="/publish/images/content/password_protect.png"
+                     alt="비밀번호 보기">
+
+            </button>
+
+            <button type="button"
+                    onclick="textClear('password_text2')"
+                    tabindex="-1">
+
+                <img src="/publish/images/content/login_del.png"
+                     alt="입력 삭제">
+
+            </button>
+
+        </div>
+
+    </div>
+
+    <div class="pass_no2" data-case="case01" id="credentials_error" style="display: none;"></div>
+
+    <div class="mem_btnWrap2">
+        <button type="button" class="btnType mem_btn5" data-case="case01" onclick="actionCredentials();">로그인</button>
+    </div>
+
+    <div class="lgoin_bottom clearfix">
+        <div>
+            <div class="checkbox_wrap">
+                <input type="checkbox" id="checkId" name="checkId" title="아이디 저장" class="joinCheckbox type3">
+                <label for="checkId">아이디 저장</label>
+            </div>
+        </div>
+        <ul>
+            <li><a href="/web/user/login/selectUserDept.do">회원가입</a></li>
+            <li class="SortLine"><a href="/web/user/findUserId.do">아이디찾기</a></li>
+            <li class="SortLine"><a href="/web/user/rePassword.do">비밀번호재설정</a></li>
+        </ul>
+    </div>
+
+</div>
 
src/main/webapp/WEB-INF/jsp/web/login/include/verification.jspf (added)
+++ src/main/webapp/WEB-INF/jsp/web/login/include/verification.jspf
@@ -0,0 +1,527 @@
+<%@ page pageEncoding="UTF-8" %>
+
+<script type="text/javascript">
+    const verificationState = {
+        sending: false,
+		verifying: false,
+        sent: false,
+        sentPhone: null,
+        resendRemaining: 3,
+        expireTimer: null,
+		cooldownTimer: null,
+		lockTimer: null
+    };
+
+    $(document).ready(function () {
+        $(".btn_auth_send").on("click", function () {
+            handleSendVerification($(this));
+        });
+
+        $("#hp_text2").on("change", function () {
+            const phone = $(this).val();
+
+            if (verificationState.sent && verificationState.sentPhone !== phone) {
+                stopVerificationTimer();
+                verificationState.sent = false;
+                verificationState.sentPhone = null;
+
+                $("#num_text2").val("");
+                $(".timer").hide();
+                $(".cf_text").hide();
+                hideVerificationError();
+            }
+        });
+
+		$(".btn_kmc_phone_reset").on("click", function () {
+			startKmcPhoneReset($(this));
+		});
+    });
+
+	var kmcPhoneResetWindow = null;
+
+	function startKmcPhoneReset($button) {
+		if ($button.prop("disabled")) {
+			return;
+		}
+
+		kmcPhoneResetWindow = window.open(
+			"",
+			"KMCISWindow",
+			"width=425,height=550,resizable=0,scrollbars=no,status=0,titlebar=0,toolbar=0,left=435,top=250"
+		);
+
+		if (!kmcPhoneResetWindow) {
+			alert("팝업 차단을 해제한 후 다시 시도해주세요.");
+			return;
+		}
+
+		$button.prop("disabled", true);
+
+		$.ajax({
+			url: "<c:url value='/web/cert/resetPhoneRequest.do' />",
+			type: "POST",
+			dataType: "json",
+			success: function (data) {
+				if (!data || !data.tr_cert || !data.tr_url) {
+					if (kmcPhoneResetWindow && !kmcPhoneResetWindow.closed) {
+						kmcPhoneResetWindow.close();
+					}
+					alert(data && (data.message || data.object)
+						? (data.message || data.object)
+						: "KMC 인증 요청 생성에 실패했습니다.");
+					return;
+				}
+
+				var form = document.kmcPhoneResetForm;
+				form.tr_cert.value = data.tr_cert;
+				form.tr_url.value = data.tr_url;
+				form.tr_add.value = data.tr_add || "N";
+				form.target = "KMCISWindow";
+				form.action = "https://www.kmcert.com/kmcis/web/kmcisReq.jsp";
+				form.submit();
+			},
+			error: function (xhr) {
+				if (kmcPhoneResetWindow && !kmcPhoneResetWindow.closed) {
+					kmcPhoneResetWindow.close();
+				}
+
+				var message = "KMC 인증 요청 중 오류가 발생했습니다.";
+				if (xhr.responseJSON && (xhr.responseJSON.message || xhr.responseJSON.object)) {
+					message = xhr.responseJSON.message || xhr.responseJSON.object;
+				}
+				alert(message);
+			},
+			complete: function () {
+				$button.prop("disabled", false);
+			}
+		});
+	}
+
+	function completeKmcPhoneReset(success, message, phoneNo) {
+		if (!success) {
+			alert(message || "본인인증 처리에 실패했습니다.");
+			return;
+		}
+
+        $(".change_number_popup .popup_heading .tooltip-close").trigger("click");
+
+		stopVerificationTimer();
+		stopResendCooldown();
+		verificationState.sent = false;
+		verificationState.sentPhone = null;
+		verificationState.resendRemaining = 3;
+		$("#num_text2").val("");
+		$(".timer").hide();
+		$(".btn_auth_send")
+			.prop("disabled", false)
+			.removeClass("disabled")
+			.text("인증번호 받기");
+
+		callMemberCertPhoneList($("#userId").val(), phoneNo);
+		hideVerificationError();
+		$(".cf_text")
+			.show()
+			.html("휴대폰번호가 변경되었습니다. 변경된 번호로 인증번호를 받아 재인증해주세요.")
+			.removeClass("c_e40000")
+			.addClass("c_002c9a");
+	}
+
+    function callMemberCertPhoneList(userId, selectedPhone) {
+        $.ajax({
+            url: "/web/cert/phoneList.do",
+            type: "POST",
+            data: { userId: userId },
+            dataType: "json",
+            success: function (response) {
+                initVerification(response);
+				if (selectedPhone) {
+					$("#hp_text2").val(selectedPhone);
+				}
+            },
+            error: function (request, status, error) {
+                console.error("callMemberCertPhoneList error", request, status, error);
+            }
+        });
+    }
+
+    function initVerification(phoneList) {
+        const $select = $("#hp_text2");
+        $select.empty();
+
+        if (!phoneList) {
+            return;
+        }
+
+		phoneList.forEach(function (item, index) {
+            $select.append(
+                $("<option>", {
+                    value: item.mbtlnum,
+					text: item.mbtlnum + (index === 0 ? " (기본)" : " (추가)")
+                })
+            );
+        });
+    }
+
+    async function handleSendVerification($button) {
+        if (verificationState.sending) {
+            return;
+        }
+
+        const phone = $("#hp_text2").val();
+
+        if (!phone) {
+            showVerificationError("휴대폰 번호를 선택해주세요.");
+            return;
+        }
+
+        if (verificationState.sent && verificationState.resendRemaining <= 0) {
+            showVerificationError("인증번호 재발송 가능 횟수를 초과했습니다.");
+            return;
+        }
+
+        verificationState.sending = true;
+        hideVerificationError();
+        $button.prop("disabled", true).addClass("disabled");
+
+        try {
+            const checkResult = await checkVerificationSendAvailable(phone);
+
+			if (!checkResult || !checkResult.compare) {
+				if (checkResult && checkResult.locked) {
+					disableSecondAuthInputs(checkResult.lockSeconds || 300);
+				}
+				throw new Error(
+					checkResult && checkResult.message
+						? checkResult.message
+						: "인증번호 재발송은 30초마다 가능합니다."
+				);
+            }
+
+            const sendResult = await requestVerificationCode(phone);
+
+			if (!sendResult || sendResult.result !== "success") {
+				if (sendResult && sendResult.locked) {
+					disableSecondAuthInputs(sendResult.lockSeconds || 300);
+				}
+                throw new Error(
+                    sendResult && sendResult.message
+                        ? sendResult.message
+                        : "인증번호 발송에 실패했습니다."
+                );
+            }
+
+            verificationState.sent = true;
+            verificationState.sentPhone = phone;
+			verificationState.resendRemaining =
+				typeof sendResult.resendRemaining === "number"
+					? sendResult.resendRemaining
+					: verificationState.resendRemaining;
+
+            $("#msgId").val(sendResult.resultMsgId || "");
+            $("#num_text2").val("");
+
+            showSendSuccess();
+            startVerificationTimer(180);
+            startResendCooldown($button, 30);
+        } catch (error) {
+            console.error("handleSendVerification error", error);
+
+            showVerificationError(
+                error.message || "인증번호 발송 중 오류가 발생했습니다."
+            );
+
+            $button.prop("disabled", false).removeClass("disabled");
+        } finally {
+            verificationState.sending = false;
+        }
+    }
+
+    function checkVerificationSendAvailable(phone) {
+        document.checkForm.receive.value = phone;
+
+        return $.ajax({
+            url: "<c:url value='/web/user/sendSysMsgSelectAjax.do' />",
+            type: "POST",
+            data: $("form[name=checkForm]").serialize(),
+            dataType: "json"
+        });
+    }
+
+    function requestVerificationCode(phone) {
+        document.checkForm.callTo.value = phone;
+
+        return $.ajax({
+            url: "<c:url value='/web/user/sendSysMsgDataAjax.do' />",
+            type: "POST",
+            data: $("form[name=checkForm]").serialize(),
+            dataType: "json"
+        });
+    }
+
+    function showSendSuccess() {
+        hideVerificationError();
+
+        $(".cf_text")
+            .show()
+            .html(
+                "인증번호를 발송했습니다. 3분 이내에 입력해주세요. " +
+                "(남은 재발송 <b>" + verificationState.resendRemaining + "</b>회)"
+            )
+            .removeClass("c_e40000")
+            .addClass("c_002c9a");
+    }
+
+    function startVerificationTimer(seconds) {
+        stopVerificationTimer();
+
+        let remaining = seconds;
+
+        $(".timer").show();
+        renderVerificationTimer(remaining);
+
+        verificationState.expireTimer = setInterval(function () {
+            remaining--;
+
+            if (remaining <= 0) {
+                renderVerificationTimer(0);
+                stopVerificationTimer();
+
+                $(".timer").hide();
+                $(".cf_text")
+                    .show()
+					.html(
+						"입력 시간이 지났습니다. 새로운 인증번호를 받아주세요. " +
+						"(남은 재발송 <b>" + verificationState.resendRemaining + "</b>회)"
+					)
+                    .removeClass("c_002c9a")
+                    .addClass("c_e40000");
+
+                return;
+            }
+
+            renderVerificationTimer(remaining);
+        }, 1000);
+    }
+
+    function renderVerificationTimer(time) {
+        const min = String(Math.floor(time / 60)).padStart(2, "0");
+        const sec = String(time % 60).padStart(2, "0");
+
+        $("#timer").text(min + ":" + sec);
+    }
+
+    function stopVerificationTimer() {
+        if (verificationState.expireTimer !== null) {
+            clearInterval(verificationState.expireTimer);
+            verificationState.expireTimer = null;
+        }
+    }
+
+    function startResendCooldown($button, seconds) {
+        stopResendCooldown();
+
+        let remaining = seconds;
+
+        $button.prop("disabled", true).addClass("disabled");
+
+        verificationState.cooldownTimer = setInterval(function () {
+            remaining--;
+
+            if (remaining <= 0) {
+                stopResendCooldown();
+
+                if (verificationState.resendRemaining > 0) {
+                    $button.prop("disabled", false).removeClass("disabled").text("재전송");
+                } else {
+                    $button.prop("disabled", true).addClass("disabled").text("재전송 불가");
+                }
+
+                return;
+            }
+
+            $button.text("재전송 (" + remaining + ")");
+        }, 1000);
+    }
+
+    function stopResendCooldown() {
+        if (verificationState.cooldownTimer !== null) {
+            clearInterval(verificationState.cooldownTimer);
+            verificationState.cooldownTimer = null;
+        }
+    }
+
+	function actionVerification() {
+		if (verificationState.verifying) {
+			return;
+		}
+
+        const phone = $("#hp_text2").val();
+        const number = $.trim($("#num_text2").val());
+
+        if (!number) {
+            showVerificationError("인증번호를 입력해주세요.");
+            $("#num_text2").focus();
+            return;
+        }
+
+        if (!/^\d{6}$/.test(number)) {
+            showVerificationError("인증번호 숫자 6자리를 입력해주세요.");
+            $("#num_text2").focus();
+            return;
+        }
+
+        document.loginForm2.hp.value = phone;
+        document.loginForm2.num.value = number;
+
+		verificationState.verifying = true;
+		hideVerificationError();
+
+		$.ajax({
+			url: "<c:url value='/web/user/login/verifySecondAuthAjax.do' />",
+			type: "POST",
+			dataType: "json",
+			data: {
+				hp: phone,
+				num: number,
+				msgId: $("#msgId").val()
+			},
+			success: function (data) {
+				if (data.status === "success") {
+					saveid(document.loginForm2);
+					document.loginForm2.action = "<c:url value='/web/user/login/actionSecurityLoginAfterHp.do'/>";
+					document.loginForm2.submit();
+					return;
+				}
+
+				showVerificationError(data.message || "인증번호가 일치하지 않습니다.");
+				if (data.locked) {
+					disableSecondAuthInputs(data.lockSeconds || 300);
+				}
+			},
+			error: function () {
+				showVerificationError("인증 처리 중 오류가 발생했습니다.");
+			},
+			complete: function () {
+				verificationState.verifying = false;
+			}
+		});
+    }
+
+	function disableSecondAuthInputs(seconds) {
+		stopVerificationTimer();
+		stopResendCooldown();
+		if (verificationState.lockTimer !== null) {
+			clearTimeout(verificationState.lockTimer);
+		}
+		$("#hp_text2, #num_text2, .btn_auth_send, .mem_btnWrap2 .mem_btn5")
+			.prop("disabled", true)
+			.addClass("disabled");
+
+		verificationState.lockTimer = setTimeout(function () {
+			verificationState.lockTimer = null;
+			verificationState.sent = false;
+			verificationState.sentPhone = null;
+			verificationState.resendRemaining = 3;
+			$("#hp_text2, #num_text2, .btn_auth_send, .mem_btnWrap2 .mem_btn5")
+				.prop("disabled", false)
+				.removeClass("disabled");
+			$(".btn_auth_send").text("인증번호 받기");
+			showVerificationError("로그인 제한이 해제되었습니다. 새로운 인증번호를 받아주세요.");
+		}, (seconds || 300) * 1000);
+	}
+
+    function showVerificationError(message) {
+        $(".cf_text").hide();
+        $("#verification_error").html("<i></i><p>" + message + "</p>").show();
+    }
+
+    function hideVerificationError() {
+        $("#verification_error").hide();
+    }
+
+    function saveid(form) {
+        const expdate = new Date();
+
+        if (form.checkId.checked) {
+            expdate.setTime(expdate.getTime() + 1000 * 3600 * 24 * 30);
+        } else {
+            expdate.setTime(expdate.getTime() - 1);
+        }
+
+        setCookie("ncms_saveid", form.id_text2.value, expdate);
+    }
+</script>
+
+<div class="tooltip-wrap">
+    <div class="popup-com change_number_popup" data-tooltip-con="change_number_popup" data-focus="change_number_popup" data-focus-prev="change_number_popup-close" style="width:540px;">
+        <div class="popup_heading">
+            <p>가입번호 변경 안내</p>
+            <button type="button" class="tooltip-close" data-focus="change_number_popup-close"><img src="/publish/images/content/layerPopup_close.png" alt="팝업 닫기"></button>
+        </div>
+
+        <div class="layer_in" style="padding:30px;">
+            <p class="title_text">가입 휴대폰번호가 변경되셨나요?</p>
+
+            <span class="sub_text">
+                등록된 번호로 인증이 어려운 경우, 가입자 본인 명의 휴대폰으로<br>
+                본인인증(PASS)을 하시면 변경된 번호로 갱신 및 해당번호로<br>
+                인증을 다시 받을 수 있습니다.
+            </span>
+
+            <p class="gray_box fill" style="margin:16px 0 0 0;">
+                ※ 가입자 본인 명의의 휴대폰으로만 인증할 수 있습니다.<br>
+                ※ 인증 완료 시 가입 휴대폰번호와 보안로그인 기본 번호가 함께<br>
+                &ensp;&ensp;변경됩니다.
+            </p>
+
+            <div class="popup_btn" style="margin:30px 0 0 0;">
+				<button type="button" class="btnType btnType2 tooltip-close btn_kmc_phone_reset">본인인증하기</button>
+                <button type="button" class="btnType fill_gray tooltip-close">취소</button>
+            </div>
+        </div>
+    </div>
+</div>
+
+<div id="verification_frame" style="display:none;">
+    <div class="input_list_item">
+        <div class="input_left">휴대폰번호</div>
+
+        <div class="input_right">
+            <label for="hp_text2" class="label">휴대폰번호</label>
+            <select id="hp_text2" class="list_selType1" style="width:70%;"></select>
+        </div>
+
+        <div class="login_del">
+            <button type="button" class="btnType btnType3 btn_auth_send">인증번호 받기</button>
+        </div>
+    </div>
+
+    <div class="input_list_item">
+        <div class="input_left">인증번호 입력</div>
+
+        <div class="input_right">
+            <label for="num_text2" class="label">인증번호 입력</label>
+            <input type="text" id="num_text2" class="list_inputType1" placeholder="인증번호를 입력해주세요" maxlength="6" inputmode="numeric" autocomplete="one-time-code" onfocus="this.placeholder=''" onblur="this.placeholder='인증번호를 입력해주세요'" onkeypress="if(event.keyCode === 13) { actionVerification(); return false; }">
+            <div id="timer" class="timer" style="display:none;"></div>
+        </div>
+
+        <div class="login_del">
+            <button type="button" onclick="textClear('num_text2')" tabindex="-1"><img src="/publish/images/content/login_del.png" alt="입력 삭제"></button>
+        </div>
+    </div>
+
+    <div id="verification_error" class="pass_no2" style="display:none;">
+        <i></i>
+        <p></p>
+    </div>
+
+    <div class="mem_bottom">
+        <p class="cf_text" style="display:none;">휴대폰번호로 전송된 인증번호를 입력해주세요.</p>
+    </div>
+
+    <div class="mem_btnWrap2">
+        <button type="button" class="mem_btn5" onclick="actionVerification(); return false;">확인</button>
+    </div>
+
+    <button type="button" class="btn_text btn_change_phone" data-tooltip="change_number_popup">가입 휴대폰번호가 변경된 경우?</button>
+</div>
 
src/main/webapp/WEB-INF/jsp/web/login/login.jsp (added)
+++ src/main/webapp/WEB-INF/jsp/web/login/login.jsp
@@ -0,0 +1,155 @@
+<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
+
+<link rel="stylesheet" href="/publish/css/mem.css">
+
+<script src="/publish/js/content.js"></script>
+<script src="/js/RSA/rsa.js"></script>
+<script src="/js/RSA/jsbn.js"></script>
+<script src="/js/RSA/prng4.js"></script>
+<script src="/js/RSA/rng.js"></script>
+
+<script type="text/javascript">
+
+  $(document).ready(function () {
+    getId(document.loginForm2);
+
+    <c:if test="${headerLoginResult ne 'N'}">
+      headerLoginResult("<c:out value='${headerLoginResult}'/>");
+    </c:if>
+  });
+
+  function getId(form) {
+    if (getCookie("ncms_saveid") !== "") {
+      form.checkId.checked = (form.id_text2.value = getCookie("ncms_saveid"));
+    }
+  }
+
+  function showCredentials() {
+    $("#credentials_frame").show();
+    $("#verification_frame").hide();
+  }
+
+  function showVerification() {
+    $("#subText").show();
+    $("#badge").show();
+    $("#credentials_frame").hide();
+    $("#verification_frame").show();
+  }
+
+  function setEncrypted() {
+    const id = $("#id_text2").val();
+    const password = $("#password_text2").val();
+
+    const rsa = new RSAKey();
+    rsa.setPublic($("#RSAModulus2").val(), $("#RSAExponent2").val());
+
+    document.loginForm2.id.value = rsa.encrypt(id.toLowerCase());
+    document.loginForm2.password.value = rsa.encrypt(password);
+  }
+
+  function headerLoginResult(headerLoginResult) {
+    if (headerLoginResult === 'secure') {
+      const userId = $("#userId").val();
+
+      document.loginForm2.id_text2.value = "<c:out value='${id_secure}'/>";
+      document.loginForm2.password_text2.value = "<c:out value='${pw_secure}'/>";
+      setEncrypted();
+      showVerification();
+      callMemberCertPhoneList(userId);
+    } else {
+      showCredentials();
+      showCredentialsError(headerLoginResult);
+	  if (headerLoginResult.indexOf("5회 이상 로그인에 실패") === 0) {
+		$("#credentials_frame .mem_btn5")
+			.prop("disabled", true)
+			.addClass("disabled");
+	  }
+    }
+  }
+
+</script>
+
+<div class="inner">
+  <form:form id="loginForm2" name="loginForm2" method="post">
+
+    <input type="hidden" name="beforeUrl" value="<c:out value='/web/user/login/login.do'/>"/>
+    <input type="hidden" name="userSe" value="USR"/>
+
+    <input type="hidden" id="RSAModulus2" value="${RSAModulus}"/>
+    <input type="hidden" id="RSAExponent2" value="${RSAExponent}"/>
+
+    <input type="hidden" id="id" name="id"/>
+    <input type="hidden" id="password" name="password"/>
+
+    <input type="hidden" name="goEventPay" value="${goEventPay}"/>
+
+    <input type="hidden" id="hp" name="hp"/>
+    <input type="hidden" id="num" name="num"/>
+    <input type="hidden" id="msgId" name="msgId"/>
+
+
+    <div class="send_top">
+      <div class="mypage_content current">
+        <div class="heading">
+          <h2>로그인</h2>
+        </div>
+
+        <div class="mem_cont_in widthS login_wrap security_login">
+          <div class="input_list">
+            <p class="welcome">
+              <span>문자온</span>에 오신것을 환영합니다.<br>
+              <span class="c_222 fwMd">로그인</span>해주세요.
+              <span id="subText" class="sub_text" style="display:none;">
+                안전한 서비스 이용을 위해 등록된 휴대폰번호로 인증을 진행해주세요.
+              </span>
+            </p>
+            <div id="badge" class="badge yellow" style="display:none;">
+              <i>!</i>
+              보안 강화를 위해&nbsp;모든 계정에 2차 인증(보안로그인)이 상시 적용됩니다.
+            </div>
+            <%@ include file="include/credentials.jspf" %>
+            <%@ include file="include/verification.jspf" %>
+          </div>
+        </div>
+
+      </div>
+    </div>
+
+  </form:form>
+
+	<form name="kmcPhoneResetForm" method="post" action="#">
+		<input type="hidden" name="tr_cert" value=""/>
+		<input type="hidden" name="tr_url" value=""/>
+		<input type="hidden" name="tr_add" value="N"/>
+		<input type="hidden" name="tr_ver" value="V2"/>
+	</form>
+
+
+  <%-- SMS 인증번호 발송용 --%>
+  <form name="checkForm"
+        id="checkForm"
+        method="post"
+        action="#">
+
+    <input type="hidden" name="searchNm" value=""/>
+    <input type="hidden" id="userId" name="userId" value="${id_secure}"/>
+    <input type="hidden" name="callTo" value=""/>
+    <input type="hidden" name="receive" value=""/>
+	<input type="hidden" name="securityLogin" value="Y"/>
+
+  </form>
+
+
+  <%-- 기업회원 처리용 --%>
+  <form name="cmpChangeForm"
+        id="cmpChangeForm"
+        method="post"
+        action="#">
+
+    <input type="hidden" name="mberId" value=""/>
+
+  </form>
+
+</div>
src/main/webapp/WEB-INF/jsp/web/login/new_usrInsertView.jsp
--- src/main/webapp/WEB-INF/jsp/web/login/new_usrInsertView.jsp
+++ src/main/webapp/WEB-INF/jsp/web/login/new_usrInsertView.jsp
@@ -717,13 +717,10 @@
 	function fnInsertJoin(){
 		var joinfrm = document.joinForm;
         var id = $("#mberId").val();
-        var pw = $("#passwordCheck").val();
-        $("#joinId").val(id);
-        $("#joinPassword").val(pw);
-        
-        //개인회원은 바로 로그인 처리, 기업회원은 회원가입 심사로 인한 로그인 미처리
+
+		// 자동 로그인 없이 가입 유형별 완료 화면으로 이동
         if(document.mberManageVO.dept.value  != 'c'){
-        	joinfrm.action="<c:url value='/web/user/login/actionSecurityJoinLogin.do'/>";
+			joinfrm.action="<c:url value='/web/user/login/usrJoinComplete.do'/>";
         }else{
         	joinfrm.action="<c:url value='/web/user/login/usrJoinMemShipComplete.do'/>";
         }
@@ -1329,8 +1326,6 @@
 		<input type="hidden" name="mberNm" />
 		<input type="hidden" name="mberId" />
 		<input type="hidden" name="dept" />
-		<input type="hidden" id="joinId" name="id">
-		<input type="hidden" id="joinPassword" name="password">
 
 	</form>
 <!-- 	<form name="joinForm" action="/web/user/login/usrJoinComplete.do" method="post">
 
src/main/webapp/WEB-INF/jsp/web/login/resetPhoneKmcResult.jsp (added)
+++ src/main/webapp/WEB-INF/jsp/web/login/resetPhoneKmcResult.jsp
@@ -0,0 +1,30 @@
+<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<!doctype html>
+<html lang="ko">
+<head>
+	<meta charset="utf-8"/>
+	<meta name="robots" content="noindex,nofollow"/>
+	<title>휴대폰번호 변경 본인인증</title>
+</head>
+<body
+		data-success="<c:out value='${success}'/>"
+		data-message="<c:out value='${message}'/>"
+		data-phone-no="<c:out value='${phoneNo}'/>">
+<script>
+	(function () {
+		var success = document.body.getAttribute("data-success") === "Y";
+		var message = document.body.getAttribute("data-message");
+		var phoneNo = document.body.getAttribute("data-phone-no");
+
+		if (window.opener && !window.opener.closed
+				&& typeof window.opener.completeKmcPhoneReset === "function") {
+			window.opener.completeKmcPhoneReset(success, message, phoneNo);
+		} else {
+			alert(message);
+		}
+		window.close();
+	}());
+</script>
+</body>
+</html>
src/main/webapp/WEB-INF/jsp/web/login/usrInserCompl.jsp
--- src/main/webapp/WEB-INF/jsp/web/login/usrInserCompl.jsp
+++ src/main/webapp/WEB-INF/jsp/web/login/usrInserCompl.jsp
@@ -70,7 +70,7 @@
                        <div class="text_bottom">
                             <div>
                                 <p>아이디</p>
-                                <p><c:out value="${mberManageVO.id}" /></p>
+                                <p><c:out value="${mberManageVO.mberId}" /></p>
                            </div>
                            <div>
                                <p>이름</p>
src/main/webapp/WEB-INF/jsp/web/login/usrInsertView.jsp
--- src/main/webapp/WEB-INF/jsp/web/login/usrInsertView.jsp
+++ src/main/webapp/WEB-INF/jsp/web/login/usrInsertView.jsp
@@ -603,13 +603,10 @@
 	function fnInsertJoin(){
 		var joinfrm = document.joinForm;
         var id = $("#mberId").val();
-        var pw = $("#passwordCheck").val();
-        $("#joinId").val(id);
-        $("#joinPassword").val(pw);
-        
-        //개인회원은 바로 로그인 처리, 기업회원은 회원가입 심사로 인한 로그인 미처리
+
+		// 자동 로그인 없이 가입 유형별 완료 화면으로 이동
         if(document.mberManageVO.dept.value  != 'c'){
-        	joinfrm.action="<c:url value='/web/user/login/actionSecurityJoinLogin.do'/>";
+			joinfrm.action="<c:url value='/web/user/login/usrJoinComplete.do'/>";
         }else{
         	joinfrm.action="<c:url value='/web/user/login/usrJoinMemShipComplete.do'/>";
         }
@@ -1209,8 +1206,6 @@
 		<input type="hidden" name="mberNm" />
 		<input type="hidden" name="mberId" />
 		<input type="hidden" name="dept" />
-		<input type="hidden" id="joinId" name="id">
-		<input type="hidden" id="joinPassword" name="password">
 
 	</form>
 <!-- 	<form name="joinForm" action="/web/user/login/usrJoinComplete.do" method="post">
src/main/webapp/WEB-INF/jsp/web/main/mainPage.jsp
--- src/main/webapp/WEB-INF/jsp/web/main/mainPage.jsp
+++ src/main/webapp/WEB-INF/jsp/web/main/mainPage.jsp
@@ -18,6 +18,8 @@
 
 <script type="text/javascript">
 let cookieCache = null; // 쿠키 데이터를 캐시할 변수
+const currentSessionId = '<%= session.getId() %>';
+const apiPopupStorageKey = 'apiMsgApprovalPopup';
 
 $(document).ready(function() {
 	// http => https 로 이동
@@ -230,6 +232,8 @@
 	setTimeout(function () {
 		hidePopupByCookie();
 	}, 300);
+
+	showApiApprovalPopup();
 });
 
 /* ********************************************************
@@ -1025,7 +1029,22 @@
     });	
 }
 
-  
+function closeApiApprovalPopup(button) {
+	sessionStorage.setItem(apiPopupStorageKey, currentSessionId);
+	popupClose(button);
+}
+
+function showApiApprovalPopup() {
+	if (sessionStorage.getItem(apiPopupStorageKey) === currentSessionId) {
+		$("#btnApiApprovalPopup").closest(".popup-com").hide().css("opacity","0");
+		$(".mask").removeClass("on").removeAttr("style");
+		return;
+	}
+
+	if (Number("${apiApprovalN}") > 0) {
+		$('.api_unchecked_send_popup').show();
+	}
+}
 
 
 </script>
@@ -1050,6 +1069,29 @@
 	<input type="hidden" id="eventNttId" name="eventNttId" value="<c:out value='${resultEvent.nttId}'/>"/>
 	<input type="hidden" id="eventBbsId" name="eventBbsId" value="<c:out value='${resultEvent.bbsId}'/>"/>
 </form>
+<c:if test="${apiApprovalN gt 0}">
+<!-- API 미확인 발송 내역 안내 -->
+<div class="tooltip-wrap" style="z-index:9999;">
+	<div class="popup-com api_unchecked_send_popup" data-tooltip-con="api_unchecked_send_popup" data-focus="api_unchecked_send_popup" data-focus-prev="api_unchecked_send_popup-close" style="width:460px;left:50%;top:50%;transform:translate(-50%,-50%);">
+		<div class="popup_heading">
+			<p>API 미확인 발송 내역 안내</p>
+			<button type="button" class="tooltip-close" id="btnApiApprovalPopup" onclick="closeApiApprovalPopup(this);" data-focus="api_unchecked_send_popup-close"><img src="/publish/images/content/layerPopup_close.png" alt="팝업 닫기"></button>
+		</div>
+		<div class="layer_in" style="padding:40px 30px;">
+			<p class="title_text">승인 대기 중인 발송 내역이 <b class="fwBold c_002c9a">${apiApprovalN}</b>건 있습니다.</p>
+			<span class="sub_text">발송내역관리에서 확인 후 승인해 주세요.</span>
+			<p class="gray_box fill" style="margin:16px 0 0 0;">
+				※ 승인 완료 전까지 로그인 시 반복 안내됩니다.
+			</p>
+			<div class="popup_btn" style="margin:20px 0 0 0;">
+				<button type="button" class="btnType btnType2 tooltip-close" onclick="location.href='/web/api/approvalList.do'; return false;">발송내역관리 이동</button>
+			</div>
+		</div>
+
+	</div>
+</div>
+<!-- //API 미확인 발송 내역 안내 -->
+</c:if>
 
 <!-- 이벤트 팝업 -->
 <div class="layer_popup_wrap eventLayerPop" style="display:none; ">
src/main/webapp/WEB-INF/jsp/web/msgdata/MsgDataSMLView.jsp
--- src/main/webapp/WEB-INF/jsp/web/msgdata/MsgDataSMLView.jsp
+++ src/main/webapp/WEB-INF/jsp/web/msgdata/MsgDataSMLView.jsp
@@ -3,30 +3,13 @@
 <%@ taglib prefix="ui" uri="http://egovframework.gov/ctl/ui"%>
 <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
 <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
-
-
 <script type="text/javascript" src="<c:url value='/publish/js/content.js'/>"></script>
 <script type="text/javascript" src="<c:url value='/js/txtSpecialReplace.js?date=202304250003'/>"></script>
 <!-- 주소록 유효성 체크 공통유틸로 인해 추가 -->
 <script type="text/javascript" src="<c:url value='/js/web/addr/cmn.js?date=202409021440'/>"></script>
-
 <% pageContext.setAttribute("newLineChar", "\r\n"); %>
 <script type="text/javascript">
-
-
-
-
 $(document).ready(function(){
-
-
-// 	console.log(' + $(#tabDision).val() : ',$('#tabDision').val())
-// 	if($('#tabDision').val() == 'tab02'){
-// 		$('#send_adYnY').prop('checked', true); // 상태 변경 및 이벤트 발생
-// 		advMsgInsert();
-// 	}
-	
-	//받는사람 연락처 내용 처리
-	//Tabulator AJAX Data Loading
 	tableL = new Tabulator(".callList_box", {
 		height:"255px",
 	    layout:"fitColumns",
@@ -43,8 +26,7 @@
 	 	columns:[ //Define Table Columns
 	 		{formatter:"rowSelection", headerHozAlign:"center", titleFormatter:"rowSelection",clipboard:false, hozAlign:"center", width:5, headerSort:false, cellClick:function(e, cell){
 	 	        cell.getRow().toggleSelect();
-		 	}}, 
-// 			{formatter:"rownum", align:"center", title:"No", hozAlign:"center", headerHozAlign:"center", field:"No", width:30},
+			}},
 			{formatter:"rownum", align:"center" ,title:"No", hozAlign:"center", headerHozAlign:"center", width:40},
 		 	{title:"이름", field:"name", editor:false, validator:["maxLength:12"], cellEdited:function(cell){
 		 		fnReplCell_advc();
@@ -83,10 +65,6 @@
 		 			cell.setValue(strMaxLengthSubstring(cellVal, 39));
 		 			cell.clearValidation();
 	 			}
-	 			
-	 			//해당 셀 데이터 삭제
-	 			//cell.setValue("");
-	 			
 	 		}
 	 		return value % parameters.phone;
 	    },
@@ -105,7 +83,6 @@
 	    progressiveRenderSize: 100, // 한 번에 렌더링할 행 수를 줄임
 	    placeholder: "주소록 그룹을 선택해 주세요.", 
 	    columns: [
-// 	        {formatter: "rowSelection", clipboard: false, hozAlign: "center", headerSort: false}, 
 	        {formatter: "rowSelection", titleFormatter: "rowSelection", hozAlign: "center", headerSort: false, 
 	            cellClick: function(e, cell) {
 	        		fn_loadAddActive(); // 로딩 활성화
@@ -145,35 +122,21 @@
 	        return value % parameters.addrPhone;
 	    },
 	});
-	
-	
-	
-	/*  
-	* 20240823
-	* 미리보기 스크립트
-	*/
-	// 현재 위치
 	var currentIndex = 0;
-
 	// 제일 이전
 	$('#btnFirst').on('click', function(){
 		currentIndex = 0;
-		
 		updateButtons(currentIndex );
 		fn_previewText(currentIndex);
 	});
-
 	// 이전
 	$('#btnPrev').on('click', function(){
-    	
 		if (currentIndex > 0) {
 			currentIndex--;
 			updateButtons(currentIndex );
 			fn_previewText(currentIndex);
 		}
     });
-    
-
 	// 다음
 	$('#btnNext').on('click', function(){
 		var dataLength = tableL.getData().length;
@@ -183,7 +146,6 @@
 			fn_previewText(currentIndex);
 		}
     });
-
 	// 제일 다음
 	$('#btnLast').on('click', function(){
 		var dataLength = tableL.getData().length;
@@ -191,37 +153,26 @@
 		updateButtons(currentIndex );
 		fn_previewText(currentIndex);
 	});
-
 	updateButtons(currentIndex);
-
 	// 최근 전송내역
 	resultLatestMsgList();
 	//자주보내는 번호
 	resultBookMarkMsgList();
-	
 });
-
 
 //최근 전송내역
 function resultLatestMsgList(){
-
-	
 	$.ajax({
 		type:"POST",
 		url:"/web/mjon/msgdata/resultLatestMsgListAjax.do",
 		data:{},
 		dataType:'json',
-		// timeout:(1000*30),
 		success:function(data){
-			
-
 			// 가져온 데이터 배열
 			let resultList = data.object; 
 			let $latestMsgUl = $('#latestMsgUl'); // 기존 리스트 UL
-
 			// 기존 내용을 비우기
 			$latestMsgUl.empty();
-
 			// 데이터가 있는 경우
 			if (resultList && resultList.length > 0) {
 				resultList.forEach(function(item, index) {
@@ -234,7 +185,6 @@
 								'<img src="/publish/images/popup/close3.png" alt="전화번호 삭제">' +
 							'</button>' +
 						'</li>';
-					
 					$latestMsgUl.append(listItem);
 				});
 			} else {
@@ -242,33 +192,23 @@
 				$latestMsgUl.append('<li><p>최근 발송 내역이 없습니다.</p></li>');
 			}
 		},
-		error:function(request , status, error){
-			console.log(' error ?');
-			console.log('request : ', request);
-			console.log('status : ', status);
-		}
+		error:function(request , status, error){}
 	});
 }
 
 // 자주보내는 번호
 function resultBookMarkMsgList(){
-
 	$.ajax({
 		type:"POST",
 		url:"/web/mjon/msgdata/resultBookMarkMsgList.do",
 		data:{},
 		dataType:'json',
-		// timeout:(1000*30),
 		success:function(data){
-			
-
 			// 가져온 데이터 배열
 			let resultList = data.object; 
 			let $bookMsgUl = $('#bookMsgUl'); // 기존 리스트 UL
-
 			// 기존 내용을 비우기
 			$bookMsgUl.empty();
-
 			// 데이터가 있는 경우
 			if (resultList && resultList.length > 0) {
 				resultList.forEach(function(item, index) {
@@ -282,7 +222,6 @@
 								'<img src="/publish/images/popup/close3.png" alt="전화번호 삭제">' +
 							'</button>' +
 						'</li>';
-					
 					$bookMsgUl.append(listItem);
 				});
 			} else {
@@ -298,18 +237,8 @@
 	});
 }
 
-
-/*
- * 버튼 disabled 스크립트
- */
 function updateButtons(currentIndex ) {
-	
-	
 	var dataLength = tableL.getData().length;
-// 	console.log('dataLength ', dataLength);
-// 	console.log('currentIndex ', currentIndex);
-	
-	
 	if (currentIndex <= 0) {
 		$('#btnPrev').attr('disabled', true);
 		$('#btnFirst').attr('disabled', true);
@@ -331,32 +260,24 @@
  * 미리보기 데이터 치환
  */
 function fn_previewText(rowIndex){
-	
 	var smsText = $('#smsTxtArea').val()
 	// 데이터 가져오기
 	var data = tableL.getData();
-	
 	// 특정 인덱스(예: 1)로 행을 선택하여 치환
 	var row = data[rowIndex]; // 인덱스 1의 행을 선택
-	
-	
 	// 이름 치환
 	smsText = smsText.replace(/\[\*이름\*\]/g, row.name !== undefined && row.name !== null ? row.name : '[*이름*]');
-	
 	// rep1, rep2, rep3, rep4 치환
 	smsText = smsText.replace(/\[\*1\*\]/g, row.rep1 !== undefined && row.rep1 !== null ? row.rep1 : '[*1*]');
 	smsText = smsText.replace(/\[\*2\*\]/g, row.rep2 !== undefined && row.rep2 !== null ? row.rep2 : '[*2*]');
 	smsText = smsText.replace(/\[\*3\*\]/g, row.rep3 !== undefined && row.rep3 !== null ? row.rep3 : '[*3*]');
 	smsText = smsText.replace(/\[\*4\*\]/g, row.rep4 !== undefined && row.rep4 !== null ? row.rep4 : '[*4*]');
-	
 	// 결과 출력 또는 다른 곳에 사용
-	console.log(smsText);
 	$('.realtime').text(smsText);
 }
 
 var tableErrorData = [];
 var totRows = 0; // 좌측 받는사람 총 갯수
-
 /* 파일등록 */
 var _fileIdx = 0;
 var _fileForm2 = new Array();
@@ -365,57 +286,35 @@
 
 //전체 데이터 갯수 구하는 함수
 function updateTotCnt(data){
-	
-	console.log(' :: updateTotCnt :: ');
 	var rowTotCnt = data;
 	$("#rowTotCnt").text(numberWithCommas(rowTotCnt));
-
 }
 
-
 function addPhoneInfo(data){
-	
 	if(data == null){
-		
 		alert("연락처 정보가 없습니다.");
 		return;
-		
 	}else{
-		
 		var idx = 0;
 		var newData = data;//중복 연락처는 하나만 남기고 모두 제거
-		
 		var recTableData = tableL.getRows();		 // 받는사람 리스트의 전체 목록을 저장
 		var tableData = [];
-		
 		//기존 받는사람 리스트를 배열에 미리 담아둔다.
 		if(recTableData.length > 0){
-			
 			for(var j=0; j < recTableData.length; j++){
-				
 				tableData.push({phone: removeDash(recTableData[j].getData().phone) , name: recTableData[j].getData().name, rep1: recTableData[j].getData().rep1, rep2: recTableData[j].getData().rep2, rep3: recTableData[j].getData().rep3, rep4: recTableData[j].getData().rep4});
-			
 			}
-		
 		}
-		
 		//받는사람 리스트를 담아둔 배열에 신규 추가 데이터를 추가해 준다.
 		for(var i=0; i < newData.length; i++){
-			
 			tableData.push({phone: removeDash(newData[i].phone) , name: newData[i].name, rep1: newData[i].rep1, rep2: newData[i].rep2, rep3: newData[i].rep3, rep4: newData[i].rep4});
-			
 		}
-		
-// 		var temp = tableData.length;
-		
 		//기존 수신 리스트를 지워준 후 신규 전체 리스트를 추가해준다.
 		tableL.clearData(); //기존 받는사람 목록을 삭제.
-		//tableL.addData(dupliPhoneData(tableData)); // 받는사람 목록에 주소 정보 입력하기
 		tableL.addData(getSpupDupliPhoneDataChk(tableData)); // 받는사람 목록에 주소 정보 입력하기
-		
+
 		_fileForm2 = []; //form file data 초기화
 		_fileIdx = 0;	//form file idx 초기화
-		
 		totRows = tableL.getRows().length; 
 		updateTotCnt(totRows); //전체 데이터 갯수 구하기
 		
@@ -426,18 +325,12 @@
 				|| smsTxtArea.indexOf("[*2*]") > -1
 				|| smsTxtArea.indexOf("[*3*]") > -1
 				|| smsTxtArea.indexOf("[*4*]") > -1){
-			
 			fnReplCell_advc();
-			
 		}else{
-			
 			//결제 금액 구하기
 		    totalPriceSum(totRows);
-			
 		}
-		
 	}
-	
 }
 
 /* 파일사이즈 가져오기 */
@@ -455,48 +348,31 @@
 
 // 엑셀 파일 불러오기
 function excelExport(event){
-	
 	var data = new FormData(document.excelForm);
 	data.append("file0", $('#excelFile').prop('files')[0]);
 	
 	var fileValue = $("#excelFile").val().split("\\");
 	var fileName = fileValue[fileValue.length-1];
-	
 	var fileExt = fileName.split('.').pop().toLowerCase();
 
 	if(fileExt.length > 0){
 		if($.inArray(fileExt, ['txt','xls','xlsx']) == -1) {
-
 			alert('txt, xls, xlsx 파일만 업로드 할수 있습니다.');
 			return false;
-
 	   	}
-	   
-		
 		var url = "";
-		
 		if(fileExt == "xls" || fileExt == "xlsx"){
-			
 			url = "/web/mjon/msgdata/sendExelFilePhoneNumAjax.do";
-			
-		}else if(fileExt = "txt"){
-			
+		} else if(fileExt = "txt"){
 			url = "/web/mjon/msgdata/sendTxtFilePhoneNumAjax.do";
-			
 		}else{
-			
 			alert('txt, xls, xlsx 파일만 업로드 할수 있습니다.');
 			return;
-			
 		}
-		
 		//엑셀 데이터 변수에 자료가 있으면 지워준다.
 	   	if(excelAddr.length > 0){
-	    	
 	    	excelAddr = [];
-	    	
 	    }
-		
 		$.ajax({
 	        type: "POST",
 	        enctype: 'multipart/form-data',
@@ -507,51 +383,28 @@
 	        processData: false,
 	        contentType: false,
 	        cache: false,
-	        //timeout: 600000,
 	        success: function (returnData, status) {
 				if(status == 'success'){ // status 확인 필요한가. 석세스 안뜨면 에러 가지 않나
-					
 					if(returnData.success){
-						
 						var data = returnData.data;
 						var message = returnData.message;
-						
 						if(message != '' ){
 							alert(returnData.message);
 						}
-						
 						if(data != null){
-							
 							$.each(data, function(i, item){
-								
 								if(checkHpNum(item.phone)){
-								
 									excelAddr.push({phone: item.phone, name: item.name, rep1: item.replace1, rep2: item.replace2, rep3: item.replace3, rep4: item.replace4});
-									
-								}/* else{
-									
-									alert("엑셀 파일 데이터의 형식이 맞지 않는 부분이 있습니다.");
-					        		excelAddr = []; //엑셀 데이터 저장 변수 초기화
-					        		$("#excelFile").val(""); //첨부파일 input 초기화
-					        	    $("#excelNm").val("");  // 첨부파일 명 초기화
-					        	    fileName = "";
-					        		return;
-									
-								} */
-							 
+								}
 							});
-	
 						}
-						
 					}else{
 						alert(returnData.message);
-// 						alert("첨부파일 불러오는 중 오류가 발생하였습니다.");
 						excelAddr = []; //엑셀 데이터 저장 변수 초기화
 		        		$("#excelFile").val(""); //첨부파일 input 초기화
 		        	    $("#excelNm").val("");  // 첨부파일 명 초기화
 		        	    fileName = "";
 						return;
-						
 					}
 				} else if(status== 'fail'){
 					alert("첨부파일 불러오는 중 오류가 발생하였습니다.");
@@ -573,90 +426,63 @@
 	    });
 		
 		$("#excelFile").val("");
-	    $("#excelNm").val(fileName); 
-		
+	    $("#excelNm").val(fileName);
 	}
 	
 }
 
 //엑셀 불러오기 팝업의 추가 버튼 처리
 $("#excelAdd").click(function(){
-	
 	if(excelAddr.length > 0){
-		
 		//첨부파일로 불러온 데이터를 받는사람 목록에 추가해 준다.
 		addPhoneInfo(excelAddr);
-		
 		 //첨부파일 초기화 시켜주기
 	    $("#excelFile").val("");
 	    $("#excelNm").val("");
-		
 	}else{
-		
 		alert("추가 엑셀 데이터가 없습니다.");
 		return false;
-		
 	}
-	
-	
 });
-
-
 //첨부 이미지 정보 확인 처리 해주기(이미지 리사이징 전단계)
 function imgResizeInfo(event){
-	
 	var fileValue = $("#imgFile").val().split("\\");
 	var fileName = fileValue[fileValue.length-1];
-	
 	var fileExt = fileName.split('.').pop().toLowerCase();
-
 	if(fileExt.length > 0){
 		if($.inArray(fileExt, ['jpg','jpeg']) == -1) {
 			alert('jpg, jpeg 파일만 업로드 할수 있습니다.');
 			return false;
 	   	}
-	   
 		$("#imgNm").val(fileName); 
 	}
 }
 
 //첨부 이미지 파일 리사이징 처리 해주기
 function imgExport(event){
-	
 	var loginVO = '${LoginVO}';
-	
 	if(loginVO == "" || loginVO == null){
 		alert("문자전송 그림 이미지 등록은 로그인 후 이용 가능합니다.");
 		return false;
 	}
-	
 	var data = new FormData(document.imgForm);
 	data.append("file0", $('#imgFile').prop('files')[0]);
 	
 	var fileValue = $("#imgFile").val().split("\\");
 	var fileName = fileValue[fileValue.length-1];
-	
 	var fileExt = fileName.split('.').pop().toLowerCase();
 
 	if(fileExt.length > 0){
 		if($.inArray(fileExt, ['jpg','jpeg']) == -1) {
-
 			alert('jpg, jpeg 파일만 업로드 할수 있습니다.');
 			return false;
-
 	   	}
-		
 		var url = "";
-		
 		if(fileExt == "jpg" || fileExt == "jpeg"){
-			
 			url = "/web/mjon/msgdata/insertAtchImageResizeAjax.do";
-			
 		}else{
-			
 			alert('jpg, jpeg 파일만 업로드 할수 있습니다.');
 			return;
-			
 		}
 		
 		$.ajax({
@@ -669,22 +495,17 @@
 	        processData: false,
 	        contentType: false,
 	        cache: false,
-	        //timeout: 600000,
 	        success: function (returnData, status) {
 				if(status == 'success'){ // status 확인 필요한가. 석세스 안뜨면 에러 가지 않나
-					
 					var data = returnData.data;
 					var message = returnData.message;
 					var result = returnData.result;
-					
 					if(result == "success"){
-						
 						var fullPath = returnData.filefullPath;
 						var fileName = returnData.fileName;
 						var atchFileId = returnData.atchFileId;
 						var imgSrc = returnData.imgSrc;
 						var imgCnt = $("#imgCnt").val();
-
 						//현재 문자내용에 첨부된 이미지 갯수 체크
 						if(imgCnt >= 3){
 							alert("이미지는 3장만 선택할 수 있습니다.");
@@ -692,40 +513,27 @@
 						}
 						//내문서함 이미지 불러오는 방식과 동일하여 함수 호출 처리함.
 						myMsgImageControl(imgSrc, "edit_n", fileName, atchFileId);
-						
 						var contents = $('#smsTxtArea').val();
-						//(contents);
-						
 						totRows = tableL.getRows().length; 
 						updateTotCnt(totRows); //전체 데이터 갯수 구하기
-						
 						//일괄변환 문구 결제금액 처리
 						if(contents.indexOf("[*이름*]")  > -1
 								|| contents.indexOf("[*1*]") > -1
 								|| contents.indexOf("[*2*]") > -1
 								|| contents.indexOf("[*3*]") > -1
 								|| contents.indexOf("[*4*]") > -1){
-							
 							fnReplCell_advc();
-							
 						}else{
-							
 							//결제금액 표시해주기
 							var totalPrice  = totalPriceSum(totRows);
 							$('#totalPrice').text(totalPrice);
 							$('#txtReplYn').val("N");
-							
 						}
-
 						alert(message);
-						
 					}else if(result == "fail"){
-						
 						alert(message);
 						return false;
-						
 					}
-					
 				} else if(status== 'fail'){
 					alert("첨부파일 불러오는 중 오류가 발생하였습니다.");
 					console.log("status : fail ~");
@@ -744,13 +552,10 @@
 	        	$('.loading_layer').removeClass('active');
 			}
 	    });
-		
 		//첨부파일 이름 및 데이터 지워주기 - 완료 후 팝업이 자동으로 닫힘.
 		$("#imgFile").val("");
-	    $("#imgNm").val(""); 
-		
+	    $("#imgNm").val("");
 	}
-	
 }
 
 function txtExport(event){
@@ -762,14 +567,11 @@
     var fileExt = fileNm.split('.').pop().toLowerCase();
     
     if(fileExt != "txt"){
-    	
     	alert("TXT 파일만 등록 할 수 있습니다.");
     	return false;
-    	
     }
     
     if (!file) {
-    	
     	alert("첨부파일이 없습니다.");
         return;
     }
@@ -779,14 +581,10 @@
     reader.onload = function(e) {
         fileData = e.target.result;
         arrSplitData = fileData.split("\n");
-        
         for(var i=0; i < arrSplitData.length; i++){
-        	
         	var arrTemp = arrSplitData[i].split("\t");
         	txtAddr.push({phone: arrTemp[1].trim(), name: arrTemp[0].trim()});
-        	
         }
-        
       	//Tabulator에 데이터 입력해주기
         addPhoneInfo(txtAddr);
     };
@@ -800,8 +598,7 @@
 function SetPriceWrapOpen() {
 	var loginVO = '${LoginVO}';
 	//로그인 체크
-	if(loginVO == "" || loginVO == null){
-	}
+	if(loginVO == "" || loginVO == null){}
 	else {
 		// 발송금액 가격안내
 		$("#myPriceWrap").show();		
@@ -820,90 +617,54 @@
 			if (data.isSuccess) {
 				// 발송금액 가격안내
 				$("#myPriceWrap").show();
-			} 
-			else {
-				//alert("Msg : " + data.msg);
 			}
 		},
-        error: function (e) { 
-        	//alert("ERROR : " + JSON.stringify(e)); 
-        }
+        error: function (e) {}
     });				
 }	
 
 $(document).ready(function (){
 	// 발송금액 단가표시
-	SetPriceWrapOpen();	
-			
+	SetPriceWrapOpen();
 	// Bline 확연 및 라디오 버튼 클릭 이벤트 추가
 	var blineCodeYn = $("#blineCode").val();
 	if(blineCodeYn != 'N' && blineCodeYn != ''){
 		$('input[name="send_adYn"]').val(['Y']);
 		$(".send_adYnN").css('display', 'none');
-// 		advMsgInsert();
 	}
-	
 	//미리보기 이미지 영역 숨김 처리
 	$('.preiew_img').hide();
 	$('.prePhoto').hide();
-	
 	//최초 화면 문자열 길이 체크해주기
 	var strCon = $('#smsTxtArea').val();
 	fnByteString(strCon);
-	
 	//일반문자, 광고문자 텍스트 내용 추가해주기
-	$('input[name=send_adYn]').change(function(){
-	
-// 		advMsgInsert();
-		
-	});
-	
-
-	
+	$('input[name=send_adYn]').change(function(){});
 	let debounceTimer;
 	//문자 내용 입력시 바이트수 계산하기
 	$('#smsTxtArea').keyup(function(e){
-// 		console.log("11$('.preview_auto').test() :: ",$('.realtime').html())
-// 		console.log("11$('.preview_auto').test() :: ",$('.realtime').text())
-		
 		var contents = $(this).val();
 		clearTimeout(debounceTimer); // 기존 타이머를 초기화
 		debounceTimer = setTimeout(() => {
 			fnByteString(contents); // 일정 시간 후에만 문자 내용 처리 실행
 		}, 300); // 300ms 지연
 	});//문자 바이트수 계산하기 끝
-	
-	
-	//스프레드 시트 Tabulator 초기 셋팅
-	/* var tabledata = [
-	 ]; */
-	
-	//var selectRow;
-	
 	//핸드폰 번호 Tabulator에서 수정시 중복 체크
 	function fnDuplPhone(){
-		
 		var recTableData = tableL.getRows();		 // 받는사람 리스트의 전체 목록을 저장
 		var tableData = [];
-		
 		//기존 받는사람 리스트를 배열에 미리 담아둔다.
 		if(recTableData.length > 0){
-			
 			for(var j=0; j < recTableData.length; j++){
-				
 				tableData.push({phone: removeDash(recTableData[j].getData().phone.trim()) , name: recTableData[j].getData().name, rep1: recTableData[j].getData().rep1, rep2: recTableData[j].getData().rep2, rep3: recTableData[j].getData().rep3, rep4: recTableData[j].getData().rep4});
-			
 			}
-		
 		}
 		
 		//수신목록에서 중복 휴대폰문자가 있는지 체크해주기
 		var dupCnt = dupliPhoneDataChk(tableData);
 		
 		if(dupCnt > 0){
-			
 			if(confirm("동일한 휴대폰 번호가 받는사람 목록에 등록되어 있습니다. 중복 번호를 삭제하시겠습니까?")){
-				
 				//수신목록에서 중복 휴대폰 번호 삭제해주기
 				tableL.clearData(); //기존 받는사람 목록을 삭제.
 				tableL.addData(dupliPhoneData(tableData)); // 받는사람 목록에 주소 정보 입력하기
@@ -912,42 +673,26 @@
 				
 				totRows = tableL.getRows().length; 
 				updateTotCnt(totRows); //전체 데이터 갯수 구하기
-				
 			}
-			
 		}
-		
 	}
 	
 	//받는사람 목록에 복사/붙여넣기 기능 처리
 	$('.callList_box').on('paste', function (e) {
-		console.log(' :: callList _box :: ');
-		
 		var element = e.originalEvent.clipboardData.getData('text'); // 클립보드에 복사한 데이터 가져오기
-		
 		var elmSplit= [];
-		  
 		elmSplit = element.split("\n");
-		
 		var elmLen = elmSplit.length;
-		
-		  
 		if(elmLen < 0){
-			  
 			alert("붙여넣을 연락처를 복사해주세요.");
 			return false;
-			  
 		}else{
 			tableErrorData.length = 0;	// 오류 번호 배열 초기화
 			
 			var splitData = [];
 			var realPhone = [];
 			for(var i=0; i < elmLen; i++){
-				  
-				var splitStr = elmSplit[i]; 
-				var tabData = [];
-				var comData = [];
-				
+				var splitStr = elmSplit[i];
 				if(splitStr.indexOf('\t') != -1){
 					splitData = splitStr.split('\t'); //탭 구분으로 데이터 분할
 				}else if(splitStr.indexOf(',') != -1){
@@ -955,15 +700,10 @@
 				}else{
 					splitData = splitStr.split(' ');
 				}
-				
-				
 				if(splitData.length == 0){// 데이터가 없는경우
-					
 					alert("탭으로 구분하여 데이터를 복사해 주세요.");
 				  	return false;
-				  
 				}else if(splitData.length == 1){// 데이터가 탭으로 구분이 없는 경우
-					
 					for(var j=0; j < splitData.length; j++){
 						if(checkHpNum(splitData[j].trim())){//핸드폰 양식이 맞는지 확인
 							//배열 끝에 데이터 추가해 주기
@@ -977,7 +717,6 @@
 					}
 				
 				}else{//데이터가 탭으로 구분되어 이름, 연락처로 구분된 경우
-					
 					var phone = "";	//핸드폰번호
 					var name = "";	//이름
 					var rep1 = "";	//치환문자1
@@ -986,15 +725,10 @@
 					var rep4 = "";	//치환문자4
 					var isPhoneValid = false;
 					for(var j=0; j < splitData.length; j++){
-
 						if(j == 0){
-							
 							name = splitData[j].trim();
-							
 						}
-						
 						if(j == 1){
-							
 							if(checkHpNum(splitData[j].trim())){//핸드폰 양식이 맞는지 확인
 								phone = removeDash(splitData[j].trim());
 								isPhoneValid = true;
@@ -1003,89 +737,53 @@
 									tableErrorData.push(splitData[j].trim());
 								}
 							}
-							
 						}
-						
 						if(j == 2){	//치환문자1
-							
 							rep1 = splitData[j].trim();
-							
 						}
-						
 						if(j == 3){	//치환문자2
-							
 							rep2 = splitData[j].trim();
-							
 						}
-						
 						if(j == 4){	//치환문자3
-							
 							rep3 = splitData[j].trim();
-							
 						}
-						
 						if(j == 5){	//치환문자4
-							
 							rep4 = splitData[j].trim();
-							
 						}
-						
 					}
-					
 					if (isPhoneValid == true) {
 						//배열 끝에 데이터 추가해 주기
 						realPhone.push({phone: phone, name: name, rep1 : rep1, rep2 : rep2, rep3 : rep3, rep4 : rep4 });
 					}
-					
 				}//else end
-				  
 			}
-			
 			var recTableData = tableL.getRows();		 // 받는사람 리스트의 전체 목록을 저장
 			var tableData = [];
 			
 			//기존 받는사람 리스트를 배열에 미리 담아둔다.
 			if(recTableData.length > 0){
-				
 				for(var j=0; j < recTableData.length; j++){
-					
 					tableData.push({phone: removeDash(recTableData[j].getData().phone.trim()) , name: recTableData[j].getData().name, rep1: recTableData[j].getData().rep1, rep2: recTableData[j].getData().rep2, rep3: recTableData[j].getData().rep3, rep4: recTableData[j].getData().rep4,  });
-				
 				}
-			
 			}
-			
 			if(realPhone.length > 0){
-				
 				for(var j=0; j < realPhone.length; j++){
-					
 					tableData.push({phone: removeDash(realPhone[j].phone.trim()) , name: realPhone[j].name, rep1: realPhone[j].rep1, rep2: realPhone[j].rep2, rep3: realPhone[j].rep3, rep4: realPhone[j].rep4});
-				
 				}
-			
 			}
-			
-			//tableData.push(realPhone);
-				
 			//중복 연락처 1개만 남기고 삭제하기
 			var removeDuplPhone = dupliPhoneData(tableData);
-			  
 			//수신자 리스트에 전화번호 추가해주기
-			//tableL.addData(removeDuplPhone);
 			tableL.setData(removeDuplPhone);
-			
 			totRows = tableL.getRows().length; 
 			updateTotCnt(totRows); //전체 데이터 갯수 구하기
-			
 			var smsTxtArea = $('#smsTxtArea').val();
-			
 			//일괄변환 문구 결제금액 처리
 			if(smsTxtArea.indexOf("[*이름*]") > -1
 					|| smsTxtArea.indexOf("[*1*]") > -1
 					|| smsTxtArea.indexOf("[*2*]") > -1
 					|| smsTxtArea.indexOf("[*3*]") > -1
 					|| smsTxtArea.indexOf("[*4*]") > -1){
-				
 				fnReplCell_advc();
 			}else{
 				//결제 금액 구하기
@@ -1094,166 +792,88 @@
 
 			if (tableErrorData.length > 0) {
 				alert("올바르지 않은 휴대폰 번호가 "+ tableErrorData.length +" 건 있습니다.");
-			}			
-			
-
+			}
 			// 미리보기 버튼 활성화
 			updateButtons(0);
-			
 		}
-
 	});
 	
 	//받는사람 번호 버튼 클릭시 Tabulator에 데이터 넣어주기
-	$('.addCallToF').click(function(){ 
-		
+	$('.addCallToF').click(function(){
 		var textarea = $('#callTo');
 		if (textarea.val() == null || textarea.val() == "") {
 		    alert("받는사람 번호를 입력해 주세요.");
 		    return false;
 		}
-
 		const numbers = textarea.val().split('\n')
 									.map(num => removeDash(num.trim()))
 									.filter(num => num !== "")
 									.filter(num => isValidPhoneNumber(num)); // 유효한 번호만 필터링;
-
-		console.log('numbers : ', numbers);
 		const addrData = processPhoneNumbers(numbers);
-        
-        
 		// 기존 tableL의 데이터를 가져옵니다.
 		var existingData = tableL.getData();
-        
 		// 데이터 병합 및 중복 제거
 		const result = mergeAndValidateData(existingData, addrData);
-        
-        
 		// 테이블 데이터 업데이트
 		if (!updateTableData(tableL, result)) return false;
-		
 		// textarea 초기화
 		textarea.val(''); // jQuery 객체에서 값을 초기화할 때는 .val('') 사용
-		
 	});
-	
-
-
-
-	
 	//받는사람 전체삭제 버튼 처리
 	$('.all_del').click(function(){
-		
-		var data = tableL.getRows();	
-	
+		var data = tableL.getRows();
 		if(data == null || data == ""){
-			
 			alert("받는사람을 추가해 주세요.");
 			return false;
-		
 		}else{
-			
 			if(confirm("받는사람 목록을 모두 삭제하시겠습니까?")){
 				tableL.clearData();
 				$("#rowTotCnt").text(0); //총건수 수정
 				$("#rowDupCnt").text(0); //중복건수 수정
 				totalPriceSum(0);		 //결제 금액 수정
 			}
-	
 		}
-		
 	});
-	
 	
 	// 받는사람 선택삭제 버튼 처리해주기
 	$('.select_del').click(function(){
-		
 		if(tableL == null || tableL == ""){
-			
 			alert("받는사람을 추가해 주세요.");
 			return false;
-		
 		}
-		
 		var selectedData = tableL.getSelectedRows();
-		
+
 		if(selectedData == "" || selectedData == null){
-			
 			alert("삭제할 연락처를 선택해주세요.");
 			return false;
-		
 		}else{ // 선택한 Row 데이터 삭제하기
-			
 			if(confirm("선택하신 받는 사람을 삭제하시겠습니까?")){
-				
 				// 선택 데이터 삭제
 			    selectedData.forEach(row => row.delete());
-			    
-			    
 				totRows = tableL.getRows().length;
 				updateTotCnt(totRows);
-			    
 				var smsTxtArea = $('#smsTxtArea').val();
-				
 				//일괄변환 문구 결제금액 처리
 				if(smsTxtArea.indexOf("[*이름*]") > -1
 						|| smsTxtArea.indexOf("[*1*]") > -1
 						|| smsTxtArea.indexOf("[*2*]") > -1
 						|| smsTxtArea.indexOf("[*3*]") > -1
 						|| smsTxtArea.indexOf("[*4*]") > -1){
-					
 					fnReplCell_advc();
 				}else{
 					//결제 금액 구하기
 				    totalPriceSum(totRows);
 				}
-				
 			}
-
 		}
-		
 	});
-	
-/* 	//받는사람 중복 삭제
-	$('.duple_del').click(function(){
-		
-		//기존 연락처 모두 불러오기
-	    var data = tableL.getRows();
-	    var tableData = [];
-	    var dpCnt = 0;
-	    var totLen = tableL.getRows().length;
-	    
-	    for(var i=0; i < totLen; i++){
-	    	
-	    	tableData.push({phone: data[i].getData().phone.trim(), name: data[i].getData().name});
-	    	
-	    }
-	    
-	    var removeDuplData = dupliPhoneData(tableData);
-		
-	    tableL.setData(removeDuplData);
-	    
-	    //총 받는사람 수 계산
-	    totRows = tableL.getRows().length;
-	    updateTotCnt(totRows);
-	    
-	  	//결제 금액 구하기
-	    totalPriceSum(totRows);
-	
-	}); */
-	
 	//주소록 불러오기 버튼 클릭시
 	$('.popupAddr').click(function(){
-		
 		var login = '${LoginVO.id}';
-		
 		if(login == null || login == ''){
-			
 			alert("로그인 후 이용이 가능합니다.");
 			return false;
-			
 		}
-		
 		$("#addrGroupLoad").load("/web/mjon/msgdata/selectAddrGroupListAjax.do", "" ,function(response, status, xhr){
 			//리스트 스크롤 처리해주기
 			$(".adr_pop_list").mCustomScrollbar({
@@ -1263,21 +883,15 @@
 				autoHideScrollbar: false
 			});
 		});
-		
 	});
-	
 	//엑섹불러오기 버튼 클릭시 파일 첨부 실행
 	$('.c3').click(function(){ // 엑셀파일 불러오기 선택 시
-		
 		$("#excelFile").click();
-		
 	});
 	
 	//엑섹불러오기 버튼 클릭시 파일 첨부 실행
 	$('.upImg').click(function(){ // 엑셀파일 불러오기 선택 시
-		
 		$("#imgFile").click();
-		
 	});
 	
 	//최근전송내역 팝업 선택추가 버튼 처리(체크내역 받는사람 리스트로 추가해주기)
@@ -1292,20 +906,13 @@
 				numbers.push(removeDash(chkPhone.trim()));
 			}
 		});
-
-
-		if(numbers.length < 1)
-		{
+		if(numbers.length < 1) {
 			alert("연락처를 선택해주세요.");
 			return false;
-		}	
-		
-
+		}
 		fn_phoneAddProcess(tableL, numbers);
-		
-		
+
 		$("#btnLatestAddPhoneClose").trigger("click");
-		
 	});
 	
 	//최근전송내역 팝업 전체추가 버튼 처리
@@ -1325,28 +932,20 @@
 		{
 			alert("연락처를 선택해주세요.");
 			return false;
-		}	
-		
-
+		}
 		fn_phoneAddProcess(tableL, numbers);
-		
-		
+
 		$("#btnLatestAddPhoneClose").trigger("click");
-			
 	});
 	
 	//최근전송내역 팝업 선택 취소 버튼 처리
 	$(document).on('click', '#latestCancelPhone', function (){
-		
 		$("input[name=latAddrChk]").prop("checked", false);
-		
 	});
 	
 	//최근전송내역 팝업 삭제 버튼 처리
 	$(document).on('click', '#latestAddrDel', function (){
-		
 		$(this).parent("#latestLi").remove();
-		
 	});
 	
 	//자주보내는 번호 팝업 선택추가 버튼 처리(체크내역 받는사람 리스트로 추가해주기)
@@ -1361,17 +960,12 @@
 				numbers.push(removeDash(chkPhone.trim()));
 			}
 		});
-
-		if(numbers.length < 1)
-		{
+		if(numbers.length < 1) {
 			alert("연락처를 선택해주세요.");
 			return false;
-		}	
-		
-
+		}
 		fn_phoneAddProcess(tableL, numbers);
-		
-		
+
 		$("#btnLatestAddPhoneClose").trigger("click");
 	});
 	
@@ -1387,27 +981,18 @@
 				numbers.push(removeDash(chkPhone.trim()));
 			}
 		});
-		
-
-		if(numbers.length < 1)
-		{
+		if(numbers.length < 1) {
 			alert("연락처를 선택해주세요.");
 			return false;
-		}	
-		
-
+		}
 		fn_phoneAddProcess(tableL, numbers);
-		
-		
+
 		$("#btnLatestAddPhoneClose").trigger("click");
-		
 	});
 	
 	//자주보내는 번호 팝업 선택 취소 버튼 처리
 	$(document).on('click', '#bookMarkCancelPhone', function (){
-		
 		$("input[name=bookAddrChk]").prop("checked", false);
-		
 	});
 	
 	//자주보내는 번호 팝업 삭제 버튼 처리
@@ -1432,59 +1017,36 @@
 	
 	//오류 검사 버튼 처리
 	$('.check_validity').click(function(){
-		
 		var invalid = tableL.getInvalidCells();		//오류 데이터 체크
 		var dataLen = tableL.getRows().length;		//연락처 데이터 갯수
 		var totLen = invalid.length;				//오류 데이터 갯수
 		var errMsg = "";							//최종 alert에 표시할 메시지 저장 변수
-		
 		if(dataLen > 0){ //연락처 정보가 있으면 수행
-			
 			if(totLen > 0){ //내용에 오류가 있으면 수행
-				
 				//오류 데이터 값 저장
 				for(var i=0; i < totLen; i++){
-					
 					var cellValue = invalid[i].getValue();
-					
 					if(i == 0){
-						
 						errMsg = cellValue;
-						
 					}else{
-						
 						errMsg = errMsg +", "+ cellValue;
-						
 					}
-					
 				}
-				
 				if(errMsg == "" || errMsg == null){
-					
 					alert("내용에 오류가 있습니다.");
 					return false;
-					
 				}else{
-					
 					alert(errMsg + "의 내용에 오류가 있습니다.");
 					return false;
-					
 				}
-				
 			}else{
-				
 				alert("오류 데이터가 없습니다.");
 				return false;
-				
 			}
-			
 		}else{
-			
 			alert("연락처를 입력해 주세요.");
 			return false;
-			
 		}
-		
 	});
 	
 	//문자입력창의 첨부된 이미지 순서를 바꿀 때
@@ -1494,7 +1056,6 @@
 			var previewHtml = "";
 			var imgCnt = $("#imgCnt").val();
 			//이미지 갯수만큼 for문 돌림
-
 			for(var i=0; i<imgCnt; i++) {
 				var imgTag = $(".thumb_wrap li:eq("+i+") div").html(); // 썸네일에 있는 이미지 태그 가져오기
 				previewHtml += '<div class="img_box preBoxF on_scroll_img" id="preBoxF'+(i+1)+'">'
@@ -1504,53 +1065,37 @@
 			$('.preViewShort div').remove(); //미리보기 영역에 있던 이미지 다 지우고
 			$('.preViewShort').append(previewHtml);  //새로 sorting된 이미지 그려주기
 		}, 100);
-		
 	});
-	
 	//문자 샘플 탭, 그림문자 샘플 탭 선택시 옵션 초기화
 	$('.tab').click(function(){
-
 		var index = $(this).index();
 		var form = document.letterForm;
-		
 		if($(this).hasClass("active") === true){
-			
 			if(index == 0){
-				
 				form.pageIndex.value = 1;
 				form.letterType.value = "S";
 				form.categoryCode.value = "";
 				form.hashTag.value = "";
-			
 			}else if(index == 1){
-				
 				form.pageIndex.value = 1;
 				form.letterType.value = "P";
 				form.categoryCode.value = "";
 				form.hashTag.value = "";
-				
 			}else if(index == 2){
-				
 				setTimeout(function(){ 
 			    	map.relayout();
 			    	map.setCenter(testcoords);
 			    }, 0);
-				
 			}
-		
 		}
-		
 	});
 	
 	
 	//이미지 선택시 그림문자 처리
-	$(document).on('click', '.photoOnImg', function(){ 
-		
+	$(document).on('click', '.photoOnImg', function(){
 		// 클릭 시 조회수 증가 로직
 		var letterId = $(this).attr('id');
-		
 		var url = "/web/mjon/msgdata/updateSmsTemplateCntAjax.do";
-		
 		$.ajax({
 			type: "POST",
 			url: url,
@@ -1564,89 +1109,58 @@
 			error: function (e) {console.log("ERROR : ", e); }
 		});
 		// 클릭 시 조회수 증가 로직 끝
-		
 		var imgId = $(this).parent().find("input").val();
-		
 		imageControl($(this), "edit_n", "Template", imgId);
-		
 		var contents = $('#smsTxtArea').val();
-		//(contents);
-		
 		totRows = tableL.getRows().length; 
 		updateTotCnt(totRows); //전체 데이터 갯수 구하기
-		
 		//일괄변환 문구 결제금액 처리
 		if(contents.indexOf("[*이름*]")  > -1
 				|| contents.indexOf("[*1*]") > -1
 				|| contents.indexOf("[*2*]") > -1
 				|| contents.indexOf("[*3*]") > -1
 				|| contents.indexOf("[*4*]") > -1){
-			
 			fnReplCell_advc();
-			
 		}else{
-			
 			//결제금액 표시해주기
 			var totalPrice  = totalPriceSum(totRows);
 			$('#totalPrice').text(totalPrice);
 			$('#txtReplYn').val("N");
-			
 		}
 
 		var sendOfT=$('.send_top').offset().top;
 		$('html,body').stop().animate({'scrollTop':sendOfT-60},250);
-		/* 
-		//결제 금액 구하기
-	    totalPriceSum(totRows); */
-		
 	});
-	
-	
 	//그림문자 이미지 삭제 처리 - 동적으로 추가한 객체는 on 함수로 불러올 수 있다.
 	$(document).on('click', '.file_close', function(){ 
 		var delImgPath = ""; //삭제하려는 이미지 경로 받아오기 
 		var index = $(this).parent().index(); // 현재 선택 된 객체 인덱스 번호 받아오기
 		var imgCnt = $("#imgCnt").val(); // 현재 첨부된 이미지의 갯수
 		var contents = $('#smsTxtArea').val();
-		
 		//선택한 태그의 부모 이미지 li 객체 삭제
 		$(this).parent(".delLi").remove();
-		
 		//미리보기 화면에서의 이미지 객체 삭제
 		//삭제하려는 이미지 경로 받아오기
 		delImgPath = $('.preBoxF').eq(index).find("img").attr("src");
-		
 		$('.preBoxF').eq(index).remove();
-		
 		// 삭제한 이미지 경로 배열에서 삭제해 주기
 		for(var i=0; i < imgFilePath.length; i++){
-			
 			if(imgFilePath[i] == delImgPath){
-				
 				imgFilePath.splice(i, 1); //이미지 경로 삭제
 				imgFileId.splice(i, 1);   //이미지 아이디 삭제 - 내 문자 보관함 저장용으로 사용
 				i--;
 			}
-			
 		}
-		
-		//imgCnt--;
 		$("#imgCnt").val(Number(imgCnt) - 1);
-		
 		//미리보기 포토 1,2,3 번 번호 표시 변경
 		$('.photo_num_02').hide();
 		$('.photo_num_03').hide();
 		if(imgCnt > 2){
-			
 			$('.photo_num_01').show();
 			$('.photo_num_02').show();
-			
-			
 		}else if(imgCnt > 1){
 			$('.photo_num_01').show();
-			
 		}else{
-			
 			//그림을 모두 삭제 했을때 단문으로 변경
 			$('.preShort').show();
 			$('.prePhoto').hide();
@@ -1655,62 +1169,46 @@
 			$('.msg_com').addClass("msg_short");
 			$('.put_left').removeClass("photo");
 			$('#msgType').val("4"); // 메세지 타입 설정
-			
+
 			fnByteString(contents);
-			
 		}
-		
 		//수신목록 전체 데이터 갯수 구하기
 		updateTotCnt(totRows);
-		
 		//일괄변환 문구 결제금액 처리
 		if(contents.indexOf("[*이름*]")  > -1
 				|| contents.indexOf("[*1*]") > -1
 				|| contents.indexOf("[*2*]") > -1
 				|| contents.indexOf("[*3*]") > -1
 				|| contents.indexOf("[*4*]") > -1){
-			
 			fnReplCell_advc();
-			
 		}else{
-			
 			//결제금액 표시해주기
 			var totalPrice  = totalPriceSum(totRows);
 			$('#totalPrice').text(totalPrice);
 			$('#txtReplYn').val("N");
-			
 		}
-		
 		var scrollHei = $(".mCustomScrollBox").height();
 		if(scrollHei > 488) {
 			$(".img_box").removeClass("on_scroll_img");
 		}
-		
 	}); 
 	
 	// 초기화 버튼 처리
 	$('.btnReset').click(function(){
-		
 		//일반문자 라이오 선택해 주기
 		$("#send_adYnN").prop("checked", true);
-		
 		// Bline 확연 및 라디오 버튼 클릭 이벤트 추가
 		var blineCodeYn = $("#blineCode").val();
 		if(blineCodeYn != 'N' && blineCodeYn != ''){
 			$('input[name="send_adYn"]').val(['Y']);
 			$(".send_adYnN").css('display', 'none');
 		}
-		
 		$('.delLi').each(function(){
-			
 			$(this).remove();
-			
 		});
 		
 		$('.preBoxF').each(function(){
-			
 			$(this).remove();
-			
 		});
 		
 		//그림 카운트 초기화
@@ -1727,17 +1225,13 @@
 		//메세지 내용 초기화
 		$('#smsTxtArea').val("");
 		fnByteString("");
-		
 	});
 	
 	// 동적으로 추가해주기 때문에 on 사용
-	$(document).on('click', '.letterMsg', function(){ //문자 템플릿 선택시 상단 문자 내용에 입력해주기 
-	
+	$(document).on('click', '.letterMsg', function(){ //문자 템플릿 선택시 상단 문자 내용에 입력해주기
 		// 클릭 시 조회수 증가 로직
 		var letterId = $(this).attr('id');
-		
 		var url = "/web/mjon/msgdata/updateSmsTemplateCntAjax.do";
-		
 		$.ajax({
 			type: "POST",
 			url: url,
@@ -1764,30 +1258,20 @@
 	});
 	
 	// 동적으로 추가해주기 때문에 on 사용
-	$(document).on('click', '.btnSearchF', function(){ // 검색 기능 실행 
-		
+	$(document).on('click', '.btnSearchF', function(){ // 검색 기능 실행
 		var formL = document.letterForm;
 		var formM = document.myMsgForm;
 		formL.categoryCode.value = ""; //해시태그 검색 시 카테고리 제거
 		formL.upperCateNo.value = ""; //해시태그 검색 시 카테고리 제거
-		/* if(form.searchKeyword.value == ""){
-			
-			alert("검색어를 입력해 주세요.");
-			return false;
-			
-		} */
-		
 		var tabIndex = $(".selectedTab").children(".active").index();
 
 		//문자 리스트 불러오기 
 		if(tabIndex == 0) {
-			
 			if($('.tDep1_letType a').eq(0).hasClass('on')) {
 				formL.letterType.value="S";
 			} else if ($('.tDep1_letType a').eq(1).hasClass('on')) {
 				formL.letterType.value="L";
 			}
-			
 			fnLetterListAjax(1);
 		}
 		//그림 문자 리스트 불러오기
@@ -1810,16 +1294,13 @@
 		var formM = document.myMsgForm;
 		formL.categoryCode.value = ""; //탭 선택시 카테고리 제거
 		formL.upperCateNo.value = ""; //탭 선택시 카테고리 제거
-		
 		var tabIndex = $(".selectedTab").children(".active").index();
-		
 		//2줄보기, 4줄보기 초기화
 		$("#LPageUnit option:eq(0)").prop("selected", true);
 		$("#PhPageUnit option:eq(0)").prop("selected", true);
 		$("#msgPageUnit option:eq(0)").prop("selected", true);
 
 		$('.bottom_content .area_total_count').hide();
-		 
 		//문자 리스트 불러오기 
 		if(tabIndex == 0) {
 			formL.letterType.value="S";
@@ -1840,55 +1321,34 @@
 			formM.searchKeyword.value = "";
 			fnMyMsgListAjax(1);
 		}
-		
 	});
-	
 	/*
-	
-		단문 / 장문 탭 옵션 선택시 실행 
-	
+		단문 / 장문 탭 옵션 선택시 실행
 	**/
-	
 	$('.tDep1_mType').click(function(){ // 전체,기업, 개인 선택 옵션 처리
-		
 		var mType = $(this).find('.on').text();
 		var form = document.letterForm;
 		
 		if(mType == "전체"){
-			
 			form.memberType.value = "";
-			
 		}else if(mType == "기업"){
-			
 			form.memberType.value = "B";
-			
 		}else{
-			
 			form.memberType.value = "P";
-			
 		}
-		
-		//문자 리스트 불러오기
-		//form.letterType.value = "S";
 		form.categoryCode.value = "";
 		form.hashTag.value = "";
 		form.pageIndex.value = 1;
-		
 		//문자 리스트 불러오기
 		fnLetterListAjax(1);
-		
 	});
 	
 	$('.tDep1_letType').click(function(){ // 단문, 장문 선택 시 옵션처리
-		
 		var letterType = $(this).find('.on').attr("value");
 		var form = document.letterForm;
-		
 		form.letterType.value = letterType;
-		
 		//문자 리스트 불러오기
 		fnLetterListAjax(1);
-		
 	});
 	
 	$('.tDep1_cateCode').click(function(){ // 카테고리 선택 시 옵션 처리
@@ -1901,9 +1361,7 @@
 		}
 		
 		if(cateCode == "all"){ //전체 선택시
-			
-			cateCode = "";	
-		
+			cateCode = "";
 		} else {
 			//카테고리 코드 클릭 시 해쉬태그 초기화 후 전체 선택
 			form.hashTag.value="";
@@ -1915,10 +1373,8 @@
 		form.pageIndex.value = 1;
 		//카테고리 클릭 시 검색자 초기화
 		form.searchKeyword.value = "";
-		
 		//문자 리스트 불러오기
 		fnLetterListAjax(1);
-		
 	});
 	
 	$('.tDep1_threeCateCode').click(function(){ // 하위 카테고리 선택 시 문자샘플 조회
@@ -1930,57 +1386,35 @@
 		//해쉬태그 검색자 초기화
 		form.searchKeyword.value = "";
 		form.pageIndex.value = 1;
-		
 		//문자 리스트 불러오기
 		fnLetterListAjax(1);
-		
 	});
 	
 	$('.tDep1_hashTag').click(function(){ // 해시 태그 선택 시 옵션 처리
 		var hashTag = $(this).find('.on').attr("value");
 		var form = document.letterForm;
-		
 		//해쉬태그 클릭 시 카테고리코드 초기화 후 전체 선택
 		form.categoryCode.value="";
 		$('.tDep1_cateCode').find('.on').removeClass('on');
 		$('.tDep1_cateCode').find('.cateAll').addClass('on');
 		//해쉬태그 클릭 시 검색자 초기화
 		form.searchKeyword.value = "";
-		
 		form.hashTag.value = hashTag;
 		form.pageIndex.value = 1;
-		
 		//문자 리스트 불러오기
 		fnLetterListAjax(1);
-		
 	});
-	
-	
-	/*
-		
-		그림문자 탭 옵션 선택시 사용
-	
-	*/
-	
+	/* 그림문자 탭 옵션 선택시 사용 */
 	$('.tDep2_mType').click(function(){ // 전체,기업, 개인 선택 옵션 처리
-		
 		var mType = $(this).find('.on').text();
 		var form = document.letterForm;
-		
 		if(mType == "전체"){
-			
 			form.memberType.value = "";
-			
 		}else if(mType == "기업"){
-			
 			form.memberType.value = "B";
-			
 		}else{
-			
 			form.memberType.value = "P";
-			
 		}
-		
 		//문자 리스트 불러오기
 		form.letterType.value = "P";
 		form.categoryCode.value = "";
@@ -1988,29 +1422,22 @@
 		form.pageIndex.value = 1;
 		
 		fnPhotoListAjax(1);
-		
 	});
 	
-	$('.tDep2_letType').click(function(){ // 그림문자 선택 
-		
+	$('.tDep2_letType').click(function(){ // 그림문자 선택
 		var letterType = $(this).find('.on').attr("value");
 		var form = document.letterForm;
 		form.letterType.value = 'P';
 		
 		//문자 리스트 불러오기
 		fnPhotoListAjax(1);
-		
 	});
 	
 	$('.tDep2_cateCode').click(function(){ // 그림 문자 카테고리 선택 시 옵션 처리
-		
 		var cateCode = $(this).find('.on').attr("value");
 		var form = document.letterForm;
-		
 		if(cateCode == "all"){ //전체 선택시
-			
-			cateCode = "";	
-		
+			cateCode = "";
 		} else {
 			//카테고리 코드 클릭 시 해쉬태그 초기화 후 전체 선택
 			form.hashTag.value="";
@@ -2025,7 +1452,6 @@
 		form.searchKeyword.value = "";
 		//문자 리스트 불러오기
 		fnPhotoListAjax(1);
-		
 	});
 	
 	$('.tDep2_threeCateCode').click(function(){ // 하위 카테고리 선택 시 문자샘플 조회
@@ -2037,31 +1463,25 @@
 		//해쉬태그 검색자 초기화
 		form.searchKeyword.value = "";
 		form.pageIndex.value = 1;
-		
 		//그림문자 리스트 불러오기
 		fnPhotoListAjax(1);		
 	});
-	
-	
+
 	$('.tDep2_hashTag').click(function(){ // 그림문자 해시 태그 선택 시 옵션 처리
 		var hashTag = $(this).find('.on').attr("value");
 		var form = document.letterForm;
-		
 		//해쉬태그 클릭 시 카테고리코드 초기화 후 전체 선택
 		form.categoryCode.value="";
 		$('.tDep2_cateCode').find('.on').removeClass('on');
 		$('.tDep2_cateCode').find('.cateAll').addClass('on');
 		//해쉬태그 클릭 시 검색자 초기화
 		form.searchKeyword.value = "";
-		
 		form.hashTag.value = hashTag;
 		form.pageIndex.value = 1;
-		
 		//문자 리스트 불러오기
 		fnPhotoListAjax(1);
 	});
-	
-	
+
 	$(".fontPlus").click(function () {
 		var currentSize = $(".preview_auto").css("fontSize");
 		var size = parseInt(currentSize, 10) * 1.2;
@@ -2084,7 +1504,6 @@
 	
 	//즉시, 예약 선택시 날짜 영역 초기 표시
 	var reYn = $("input[name=reserYn]:checked").val();
-	
 	if(reYn == "N"){
 		$('.rev_selected').hide();
 	}else{
@@ -2105,48 +1524,6 @@
 		$('.send_btn .btnType:first-child').html('예약하기');
 	});
 });
-
-/* 
- * 
- function advMsgInsert(){
-	
-	var advStr = '<div class="ad_txt"><p>(광고)</p></div>';
-	var advDenyStr = '<div class="deny_txt"><p>무료거부 0808800858</p> </div>';
-	
-	var preAdvStr = '<p class="ad_tit">(광고)</p>';
-	var preAdvDenyStr = '<p class="deny_receipt">무료거부 0808800858</p>';
-	
-	//광고 및 무료수신거부 텍스트 추가
-	if ($('input[name=send_adYn]:checked').val() == 'Y'){
-		
-		//문자 내용에 광고 문자 추가해주기
-		$(advStr).insertBefore('textarea[name=smsTxtArea]'); 
-		$(advDenyStr).insertBefore('div[name=afterDeny]');
-		
-		//미리보기에 광고 문자 추가해주기
-		$(preAdvStr).insertBefore('.none_txt');
-		$(preAdvDenyStr).insertAfter('.realtime');
-		
-// 		$('#smsTxtArea').val(advStr + "\r\n" + contents + "\r\n" + advDenyStr);
-		
-		
-	}else{ //광고 및 무료수신거부 텍스트 삭제
-		
-		//문자내용 광고문자 삭제하기
-		$('.ad_txt').remove();
-		$('.deny_txt').remove();
-		
-		//미리보기 광고 문자 삭제하기
-		$('.ad_tit').remove();
-		$('.deny_receipt').remove();
-		
-	}
-	
-	//문자 바이트수 계산하기
-	fnByteString($('#smsTxtArea').val());
-	
-} */
-
 //문자 바이트수 계산하기 함수
 function fnByteString(contents){
 	console.log(' : fnByteString : ');
@@ -2158,23 +1535,18 @@
 	var adverTxt = $('.ad_txt').html();
 	var denyTxt = $('.deny_txt').html();
 	totRows = tableL.getRows().length;
-
 	//광고문자 상단 문구 줄바꿈 처리하기(문자길이 바이트수 계산을 위해서)
 	if(typeof adverTxt !='undefined' && adverTxt != null){
-		
 		adverTxt = adverTxt.replaceAll("<p>","");
 		adverTxt = adverTxt.replaceAll("</p>","");
 		adTxtLeng = conByteLeng(adverTxt);
-
 	}
 	
 	//광고문자 하단 문구 줄바꿈 처리하기(문자길이 바이트수 계산을 위해서)
 	if(typeof denyTxt !='undefined' && denyTxt != null){
-		
 		denyTxt = denyTxt.replaceAll("<p>","\n");
 		denyTxt = denyTxt.replaceAll("</p>","");
 		denyTxtLeng = conByteLeng(denyTxt);
-		
 	}
 	
 	$('#msgLeng').html("");
@@ -2189,12 +1561,8 @@
 	$('#msgLeng').text(conLeng);
 	//문자 길이 변수에 저장해주기
 	$('#smsLen').val(conLeng);
-	
-	
-	
 	var photoSts = $('.delLi').length;
 	if(photoSts > 0){
-		
 		$('#msgLeng').html(conLeng + " / ");
 		$('#limitLeng').html("2000");
 		$('.msg_com').html("그림");
@@ -2208,12 +1576,8 @@
 		$('.put_left').addClass("photo");  // 내용 입력 박스에 클래스 삽입
 		$('.put_left').css("display","block");  // 내용 입력 박스에 클래스 삽입
 		$('.msg_title').addClass('active');
-		//document.getElementById("mmsSubject").disabled = false;
-		
 	}else{
-		
 		if(conLeng > 90){
-			
 			$('#msgLeng').html(conLeng + " / ");
 			$('#limitLeng').html("2000");
 			$('.msg_com').html("장문");
@@ -2224,10 +1588,7 @@
 			$('.msg_com').addClass("msg_long"); // 장문 클래스 삽입
 			$('.put_left').addClass("long");  // 내용 입력 박스에 클래스 삽입
 			$('.msg_title').addClass('active');
-			//document.getElementById("mmsSubject").disabled = false;
-			
 		}else{
-			
 			$('#msgLeng').html(conLeng + " / ");
 			$('#limitLeng').html("90");
 			$('.msg_com').html("단문");
@@ -2240,86 +1601,54 @@
 			$('#mmsSubject').val("");
 			$('#title_y').prop('checked',false);
 			$('#title_n').prop('checked',true);
-			//제목 비활성화로 내용 지워주기
-			/* $('#mmsSubject').val("");
-			document.getElementById("mmsSubject").disabled = true; */
-			
 		}
-		
 	}
-	
-	
-	// 미리보기 데이터 입력해 주기 , 줄바꿈 문자 변환해주기
-	//var repContent = "";
-	//repContent = content.replace(/(?:\r\n|\r|\n)/g, '<br/>');
-	
-	
 	if(conLeng > 0){
-		
 		$('.none_txt').text("");
-		
 	}else{
-		
 		$('.none_txt').text("내용을 입력해주세요.");
-		
 	}
-
 	//미리보기에 내용 입력해 주기
 	$('.realtime').text(contents);
-
 	// 2000byte 초과시 메세지 알림
 	if(conLeng > 2000){
-		
 		var fullContents = adverYn != "N" ? adverTxt + denyTxt + contents : contents;
 		var subUnderStr = strUnderLineSubstring(fullContents, 2000).replace(adverTxt, "").replace(denyTxt, "");
 		subUnderStr = subUnderStr.replace(adverTxt,"").replace(denyTxt, "");
-// 		console.log('subUnderStr : ', subUnderStr)
-		
-		
+
 		$('#smsTxtArea').val(subUnderStr);
 
 		var contentLeng = conByteLeng(subUnderStr);
 		var totalLeng = adverYn != "N" ? contentLeng + adTxtLeng + denyTxtLeng : contentLeng;
 
-// 		console.log('contentLeng : ', totalLeng);
 		$('#msgLeng').text(totalLeng + " / ");
 		$('#smsLen').val(contentLeng);
 		
  		alert("문자 내용은 2000byte를 넘을 수 없습니다.");
 		return false;
 	}
-	
-	//수신목록 전체 데이터 갯수 구하기
-// 	updateTotCnt(totRows);
-	
+
 	//일괄변환 문구 결제금액 처리
 	if(contents.indexOf("[*이름*]")  > -1
 			|| contents.indexOf("[*1*]") > -1
 			|| contents.indexOf("[*2*]") > -1
 			|| contents.indexOf("[*3*]") > -1
 			|| contents.indexOf("[*4*]") > -1){
-		
-// 		fnReplCell();
 		fnReplCell_advc();
-		
 	}else{
 		console.log('totRows : ', totRows);
 		var totalPrice  = totalPriceSum(totRows);
 		$('#totalPrice').text(totalPrice);
 		$('#txtReplYn').val("N");
-		
 	}
-	
 }
 	
 function openPhotoEditor() {
 	var imgCnt = $("#imgCnt").val();
-	
 	if(imgCnt >= 3) {
 		alert("메시지에 첨부할 수 있는 이미지는 최대 3장입니다.");
 		return;		
 	}
-	
     //만들려는 팝업의 크기
     var popup_wid = '1100';
     var popup_ht = '851';
@@ -2329,12 +1658,10 @@
     var popup_top =(window.screen.height / 2) - (popup_ht / 2);
 
     window.open('<c:url value='/web/mjon/msgdata/openImageEditorPopup.do'/>', 'a', 'width=' + popup_wid +', height='+ popup_ht +', left=' + popup_left + ', top='+ popup_top + ', scrollbar=no' );
-
 }
 
 //그림샘플 이미지 선택시 문자 내용에 입력 처리
 function imageControl(obj, editYn, imageName, imgId) {
-	
 	var imgCnt = $("#imgCnt").val(); // 현재 페이지에 첨부된 이미지 갯수
 	var imgSrc = obj.attr("src");
 	
@@ -2347,33 +1674,20 @@
 	$('.prePhoto').show();
 	
 	if($('.msg_com').hasClass("msg_short") === true){
-		
 		$('.msg_com').removeClass("msg_short"); //단문 클래스 삭제하고
 		$('.msg_com').addClass("msg_photo"); // 그림문자용 클래스 삽입
 		$('.put_left').removeClass("short");
 		$('.put_left').addClass("photo");
 		$('.msg_com').html("그림");
 		$('#msgType').val("6"); // 메세지 타입 설정
-		
-		//제목 입력 항목 활성화
-		//document.getElementById("mmsSubject").disabled = false;
-		
 	}else{
-		
 		$('.msg_com').removeClass("msg_long"); //장문 클래스 삭제하고
 		$('.msg_com').addClass("msg_photo"); // 그림문자용 클래스 삽입
 		$('.put_left').removeClass("long");
 		$('.put_left').addClass("photo");
 		$('.msg_com').html("그림");
 		$('#msgType').val("6"); // 메세지 타입 설정
-		
-		//제목 입력 항목 활성화
-		//document.getElementById("mmsSubject").disabled = false;
-		
 	}
-	
-	//이미지 카운트 증가시키기
-	//imgCnt ++;
 	$("#imgCnt").val(Number(imgCnt)+1);
 	$(".thumb_wrap").removeClass("none");
 	
@@ -2384,41 +1698,28 @@
 				+ '<button type="button" class="file_close"><img src="/publish/images/content/thumb_del.png" alt="첨부파일 삭제"></button>'
 				+ '<button type="button" class="file_close_on"><img src="/publish/images/content/thumb_del_on.png" alt="첨부파일 삭제"></button>'
 				+ '</li>';
-				
-				
-		/* "<li class=\"delLi\"><p>" + imgPath + "</p><button type=\"button\" class=\"file_close\"><img class=\"delImg\" src=\"/publish/images/content/file_close.png\" alt=\"첨부파일 삭제\"></button></li>" */
 	$('.liOnImg').append(liPath);	
 		
 	//미리보기 보여주기
 	$('.preiew_img').show();
-	
 	var imgHtml = "";
 	var imgPath = "<div class=\"img_box preBoxF\" id=preBoxF"+ imgCnt +"><img src=\"" + imgSrc + "\" /></div>";
-
 	if($("#imgCnt").val() == 1){
 		$('.preViewShort').append(imgPath);
 		$('.photo_num_01').show();
 		$('.photo_num_02').hide();
 		$('.photo_num_03').hide();
-		//$(".thumb_wrap li:eq(0)").addClass("on");
-		
 	}else if($("#imgCnt").val() == 2){
 		$('.preViewShort').append(imgPath);
 		$('.photo_num_02').show();
 		$(".thumb_wrap li").removeClass("on");
-		//$(".thumb_wrap li:eq(1)").addClass("on");
-		
 	}else if($("#imgCnt").val() == 3){
 		$('.preViewShort').append(imgPath);
 		$('.photo_num_03').show();
 		$(".thumb_wrap li").removeClass("on");
-		//$(".thumb_wrap li:eq(2)").addClass("on");
-		
 	}
-	
 	// 배열에 선택한 이미지 경로 저장해주기
 	imgFilePath.push(imgSrc);
-	
 	// 배열에 선택한 이미지 아이디 저장해주기 - 내 문자 보관함 저장용으로 사용
 	if(typeof(imgId) != 'undefined' && imgId != null && editYn != 'edit_y'){
 		imgFileId.push(imgId);
@@ -2426,58 +1727,41 @@
 	//문자 길이 및 타입 변경해주기
 	var contents = $('#smsTxtArea').val();
 	fnByteString(contents);
-	
+
 	var scrollHei = $(".mCustomScrollBox").height();
 	if(scrollHei > 488) {
 		$(".img_box").addClass("on_scroll_img");
 	}
-	
 }
 
 //그림샘플 이미지 선택시 문자 내용에 입력 처리
 function myMsgImageControl(imgSrc, editYn, imageName, imgId) {
-	
 	var imgCnt = $("#imgCnt").val(); // 현재 페이지에 첨부된 이미지 갯수
 	var imgSrc = imgSrc;
-
 	if(imgCnt >= 3){
 		alert("이미지는 3장만 선택할 수 있습니다.");
 		return false;
 	}
-
 	$('.preShort').hide();
 	$('.prePhoto').show();
 	
 	if($('.msg_com').hasClass("msg_short") === true){
-		
 		$('.msg_com').removeClass("msg_short"); //단문 클래스 삭제하고
 		$('.msg_com').addClass("msg_photo"); // 그림문자용 클래스 삽입
 		$('.put_left').removeClass("short");
 		$('.put_left').addClass("photo");
 		$('.msg_com').html("그림");
 		$('#msgType').val("6"); // 메세지 타입 설정
-		
-		//제목 입력 항목 활성화
-		//document.getElementById("mmsSubject").disabled = false;
-		
 	}else{
-		
 		$('.msg_com').removeClass("msg_long"); //장문 클래스 삭제하고
 		$('.msg_com').addClass("msg_photo"); // 그림문자용 클래스 삽입
 		$('.put_left').removeClass("long");
 		$('.put_left').addClass("photo");
 		$('.msg_com').html("그림");
 		$('#msgType').val("6"); // 메세지 타입 설정
-		
-		//제목 입력 항목 활성화
-		//document.getElementById("mmsSubject").disabled = false;
-		
 	}
-	
-	//이미지 카운트 증가시키기
-	//imgCnt ++;
+
 	$("#imgCnt").val(Number(imgCnt)+1);
-	
 	//문자 내용 상단에 이미지 이름 영역 객체 추가해주기
 	var imgPath = '<img src="' + imgSrc + '" class="thumb_img '+ editYn +'" alt="thumb1"/>';
 	var liPath = '<li class="delLi '+imageName+'" id="delLi'+ imgCnt +'">'
@@ -2485,12 +1769,8 @@
 				+ '<button type="button" class="file_close"><img src="/publish/images/content/thumb_del.png" alt="첨부파일 삭제"></button>'
 				+ '<button type="button" class="file_close_on"><img src="/publish/images/content/thumb_del_on.png" alt="첨부파일 삭제"></button>'
 				+ '</li>';
-				
-				
-		/* "<li class=\"delLi\"><p>" + imgPath + "</p><button type=\"button\" class=\"file_close\"><img class=\"delImg\" src=\"/publish/images/content/file_close.png\" alt=\"첨부파일 삭제\"></button></li>" */
-	$('.liOnImg').append(liPath);	
-		
-	//미리보기 보여주기
+	$('.liOnImg').append(liPath);
+
 	$('.preiew_img').show();
 	
 	var imgHtml = "";
@@ -2501,22 +1781,15 @@
 		$('.photo_num_01').show();
 		$('.photo_num_02').hide();
 		$('.photo_num_03').hide();
-		//$(".thumb_wrap li:eq(0)").addClass("on");
-		
 	}else if($("#imgCnt").val() == 2){
 		$('.preViewShort').append(imgPath);
 		$('.photo_num_02').show();
 		$(".thumb_wrap li").removeClass("on");
-		//$(".thumb_wrap li:eq(1)").addClass("on");
-		
 	}else if($("#imgCnt").val() == 3){
 		$('.preViewShort').append(imgPath);
 		$('.photo_num_03').show();
 		$(".thumb_wrap li").removeClass("on");
-		//$(".thumb_wrap li:eq(2)").addClass("on");
-		
 	}
-	
 	// 배열에 선택한 이미지 경로 저장해주기
 	imgFilePath.push(imgSrc);
 	// 배열에 선택한 이미지 아이디 저장해주기 - 내 문자 보관함 저장용으로 사용
@@ -2531,14 +1804,10 @@
 	if(scrollHei > 488) {
 		$(".img_box").addClass("on_scroll_img");
 	}
-	
 }
 
 
 $(document).on('click', '.symbolButton, .changeWord', function (){
-	//var smsTxtArea = $('#smsTxtArea').val();
-	//$('#smsTxtArea').val(smsTxtArea + $(this).attr("value"));
-	
 	// 커서 위치에 삽입
 	setCursorInsertText("smsTxtArea", $(this).attr("value"));
 	
@@ -2546,16 +1815,11 @@
 });
 
 $(document).on('click', '#recommBtn', function(){
-	
 	var recommId = $('#recommId').val();
-	
 	if(recommId == ''){
-		
 		alert("추천인 ID를 입력해 주세요.");
 		return false;
-		
 	}
-	
 	var data = new FormData(document.msgForm);
 	var url = "/web/mjon/msgdata/selectRecommIdCheckAjax.do";
 	
@@ -2569,34 +1833,25 @@
         processData: false,
         contentType: false,
         cache: false,
-        //timeout: 600000,
         success: function (returnData, status) {
 			if(status == 'success'){ // status 확인 필요한가. 석세스 안뜨면 에러 가지 않나
-				
 				if(returnData.status == "success"){
-					
 					alert("추천인 아이디가 확인 되었습니다.");
 					return false;
-					
 				}else{
-					
 					alert("존재하지 않는 추천인 아이디 입니다.");
 					return false;
-					
 				}
-				
 			} else if(status== 'fail'){
 				alert("추천인 확인에 실패하였습니다. !!");
 			}
 		},
         error: function (e) { alert("추천인 확인에 실패하였습니다."); console.log("ERROR : ", e); }
     });
-	
 });
 
 //문자발송 받는사람 목록 주소록에 등록하기 기능 처리
 $('.registAddr').click(function(){
-	
 	var tableData = tableL.getRows();
 	var dataLen = tableL.getRows().length;
 	
@@ -2610,35 +1865,26 @@
 	if(dataLen > 0){
 		
 		for(var i=0; i < dataLen; i++){
-			
 			var name = tableData[i].getData().name;
 			var phone = removeDash(tableData[i].getData().phone);
 			var rep1 = tableData[i].getData().rep1;
 			var rep2 = tableData[i].getData().rep2;
 			var rep3 = tableData[i].getData().rep3;
 			var rep4 = tableData[i].getData().rep4;
-			
 			if(phone == "" || phone == null){
-				
 				alert("수신 목록에 핸드폰 번호가 없는 항목이 있습니다.");
 				return false;
-				
 			}else if(!checkHpNum(phone)){
-				
 				alert("수신 목록에 잘 못된 핸드폰 번호가 있습니다. 핸드폰 번호 : " + phone + " 입니다.");
 				return false;
-				
 			}
-			
-			
 			phoneNum[i] = phone;
-			
 			if(name == "" || name == null){
 				nameList[i] = "";
 			}else{
 				nameList[i] = name.replaceAll(",","§");
 			}
-			
+
 			if(rep1 == "" || rep1 == null){
 				rep1List[i] = "-";
 			}else{
@@ -2662,47 +1908,8 @@
 			}else{
 				rep4List[i] = rep4.replaceAll(",","§");
 			}
-			
-			/* if(name == "" || name == null){
-				
-				alert("수신 목록에 이름이 없는 항목이 있습니다. 이름을 입력해 주세요");
-				return false;
-				
-			}else{
-				
-				nameList[i] = name;
-				phoneNum[i] = phone;
-				
-				if(rep1 == "" || rep1 == null){
-					rep1List[i] = "-";
-				}else{
-					rep1List[i] = rep1;
-				}
-				
-				if(rep2 == "" || rep2 == null){
-					rep2List[i] = "-";
-				}else{
-					rep2List[i] = rep2;
-				}
-				
-				if(rep3 == "" || rep3 == null){
-					rep3List[i] = "-";
-				}else{
-					rep3List[i] = rep3;
-				}
-				
-				if(rep4 == "" || rep4 == null){
-					rep4List[i] = "-";
-				}else{
-					rep4List[i] = rep4;
-				}
-				
-			} */
-			
 		}
-		
 		//주소록 정보를 Form에 넣어준다.
-		
 		var form = document.msgForm;
 		form.callToList.value = phoneNum;
 		form.nameList.value = nameList;
@@ -2724,10 +1931,9 @@
 			}
 		}
 		if(confirm("연락처 정보를 주소록에 등록 하시겠습니까?")){
-			
 			var data = new FormData(form);
 			var url = "/web/mjon/msgdata/insertMsgAddrListAjax.do";
-			
+
 			$.ajax({
 		        type: "POST",
 		        enctype: 'multipart/form-data',
@@ -2787,22 +1993,15 @@
 		        	$('.loading_layer').removeClass('active');
 				}
 		    });
-			
 		}
-		
 	}else{
-		
 		alert("연락처 정보를 등록해 주세요.");
 		return false;
-		
 	}
-	
-	
 });
 
 //주소록 불러오기 그룹명 검색 기능처리
 function fnAddrGrpSearch(){
-	
 	var form = document.searchAddrGrpForm;
 	form.searchKeyword.value = form.searchGrpKeyword.value;
 	form.searchCondition.value = "";
@@ -2818,66 +2017,39 @@
 			autoHideScrollbar: false
 		});
 	});
-	
 }
-
-
 //주소록 불러오기 팝업의 그룹 선택시 우측에 주소록 불러오기
-function fnSelectAddrList(type,addrGrpId,item){
+function fnSelectAddrList(type,addrGrpId,item) {
 	// 주소록 그룹 열림, 닫힘 폴더이미지 초기화
 	$(".adr_pop_list div p").each(function (index, item) {
 		$(item).find("img").attr("src", "/publish/images/content/close_folder2.png");
 		$(item).find("img").attr("alt", "폴더 닫힘");
 	});
-	
 	var form = document.searchAddrGrpForm;
 	form.type.value = type;
 	form.searchAddrGrpId.value = addrGrpId;
 	form.searchKeyword.value = "";
 	form.searchCondition.value = form.searchAddrCondition.value;
-	
 	//왼쪽 그룹리스트의 그룸명을 선택시 검색어를 초기화해준다.
-	form.searchAddrKeyword.value="";
-	
-	/*
-	$(item).toggleClass("open");
-	if ($(item).hasClass("open") === true) {
-		$(item).find("img").attr("src", "/publish/images/content/open_folder2.png");
-		$(item).find("img").attr("alt", "폴더 열림");
-	}
-	else {
-		$(item).find("img").attr("src", "/publish/images/content/close_folder2.png");
-		$(item).find("img").attr("alt", "폴더 닫힘");
-	}
-	*/
-	   
+	form.searchAddrKeyword.value = "";
 	$(item).find("img").attr("src", "/publish/images/content/open_folder2.png");
 	$(item).find("img").attr("alt", "폴더 열림");
-	
 	loadAddrList();
-	
 }
 
 
 function fnAddrSearch(){
-	
 	var form = document.searchAddrGrpForm;
 	form.searchKeyword.value = form.searchAddrKeyword.value;
 	form.searchCondition.value = form.searchAddrCondition.value;
-	
 	loadAddrList();
-	
 }
 
 function loadAddrList(){
-
-	/*
-		serialize 를 사용할때는 processDa    ta, contentType 옵션 제가할것
-	*/
-	var data = $("#searchAddrGrpForm").serialize();	 
+	/* serialize 를 사용할때는 processData, contentType 옵션 제거할 것*/
+	var data = $("#searchAddrGrpForm").serialize();
 
 	var url = "/web/mjon/msgdata/selectMsgAddrListAjax_advc.do";
-	
 	$.ajax({
         type: "POST",
         url: url,
@@ -2886,23 +2058,14 @@
         async: true,
         cache: false,
         success: function (data) {
-        	console.log('data : ', data);
-			if(data.status == "OK"){ // status 확인 필요한가. 석세스 안뜨면 에러 가지 않나
-				
+			if(data.status == "OK"){
 					var addrList = data.object;
-					
 					if(addrList.length == 0){
-						
 						alert("주소록 정보가 없습니다.");
-// 	                    tableAddr.setData([]);
 						return false;
 					}
-					
-// 					console.log('data : ', data);
 					tableAddr.setData(addrList);
-			} 
-			else
-			{
+			} else {
 				alert("주소록 불러오기에 실패하였습니다. !!");
 			}
 		},
@@ -2926,24 +2089,18 @@
         	$('.loading_layer').removeClass('active');
 		}
     });
-	
 }
 
 //주소록 불러오기에서 수신자 리스트 추가해 주기
 function addrToList(){
-	
 	var selectedData = tableAddr.getSelectedRows();
 	var tableData = [];
 	
 	if(selectedData == "" || selectedData == null){
-		
 		alert("주소록을 선택해 주세요.");
 		return false;
-	
 	}else{ // 선택한 Row 데이터 저장해주기
-		
 		for(var i=0; i < selectedData.length; i++){
-			
 			//좌측 받는사람 리스트를 담아둔 배열에 데이터를 추가해 준다.
 			tableData.push({phone: removeDash(selectedData[i].getData().addrPhone), 
 				name: selectedData[i].getData().addrName, 
@@ -2952,38 +2109,28 @@
 				rep3: selectedData[i].getData().addrRep3, 
 				rep4: selectedData[i].getData().addrRep4}
 			);
-		
 		}
-	
 		//선택한 데이터 받는사람 리스트에 추가해 주기
 		addPhoneInfo(tableData);
 		$(".closeAddr").trigger("click");
-		
 		//주소록 레이어 팝업의 Tabulator 데이터 지워주기
 		tableAddr.clearData();
-
 	}
-	
 }
 
 //주소록 불러오기에서 수신자 리스트 추가해 주기
 function addrToList_advc(type){
-	console.log('tableAddr.getDataCount() : ', tableAddr.getDataCount());
 	// 선택된 데이터 또는 전체 데이터 변수 초기화
 	let selectedData = type === 'select' ? tableAddr.getSelectedRows() : tableAddr.getData();
-
 	// 데이터가 비어있으면 경고 후 종료
 	if (!selectedData || selectedData.length < 1) {
-		
 		if(tableAddr.getDataCount() < 1){
 			alert("주소록을 선택해 주세요.");
 		}else{
 			alert("전화번호를 선택해 주세요.");
 		}
-		
 		return false;
 	}
-
 	// 데이터 변환 로직
 	const addrData = selectedData.map(row => {
 		const rowData = type === 'select' ? row.getData() : row; // 'select'는 행 객체에서 데이터 추출
@@ -2996,59 +2143,42 @@
 			rep4: rowData.rep4
 		};
 	});
-	
-
 	// 기존 tableL의 데이터를 가져옵니다.
 	var existingData = tableL.getData();
 	// 기존 데이터와 새로운 데이터를 합칩니다.
 	var combinedData = existingData.concat(addrData);
-
 	// @ phone을 기준으로 중복 제거 및 갯수 계산
 	const result = removeDuplicatesAndCount(combinedData, 'phone');
-	
-
-	
 	// 총 30만건이 넘으면 false
 	if (!validateRowLimit(result.uniqueCount)) {
 		return false;
 	}
-	
-	
 	// 합쳐진 데이터를 tableL에 설정합니다.
 	tableL.setData(result.uniqueArray);
 	// 중복데이터 건수 입력
 	setRowDupCnt(result.duplicateArray.length);
-	 
-	
 	// 미리보기 버튼 활성화
 	updateButtons(0);
-	
+
 	var totRows = tableL.getRows().length; 
 	updateTotCnt(totRows); //전체 데이터 갯수 구하기
 	totalPriceSum(totRows);
-	
 
 	$(".closeAddr").trigger("click");
 	//주소록 레이어 팝업의 Tabulator 데이터 지워주기
 	tableAddr.clearData();
-
-	
 }
 
 
 //주소록 팝업 닫기 기능
 function addrClose(){
-	
 	$(".closeAddr").trigger("click");
-	
 	//주소록 레이어 팝업의 Tabulator 데이터 지워주기
 	tableAddr.clearData();
-	
 }
 
 //내문자함 선택시 저장된 내용 문자 입력영역에 처리해주기
 function fnMyMsgAdd(msgId){
-	
 	var form = document.addMyMsgForm;
 	form.msgId.value = msgId;
 	form.pageType.value = "myMsg";
@@ -3067,76 +2197,53 @@
         cache: false,
         success: function (returnData, status) {
 			if(status == 'success'){ // status 확인 필요한가. 석세스 안뜨면 에러 가지 않나
-				
 				if(returnData.result == "success"){
-					
 					var msgData = returnData.resultMsgInfo;
 					var fileData = returnData.resultMsgAddPhotoInfo;
-					
 					var smsTxt = msgData.smsTxt;
 					var subject = msgData.subject;
 					var fileId = "";
 					var filePath = "";
 					var len = fileData.length;
-					
 					//기존 내용 지워주기
 					//$(".btnReset").trigger("click");
 					$('.delLi').each(function(){
 						$(this).remove();
 					});
-					
 					$('.preBoxF').each(function(){
 						$(this).remove();
 					});
-
 					$('#imgCnt').val(0);
-					
 					//기존 imgFileId, imgFilePath 배열에 있는 내용 초기화 해주기
 					imgFilePath = [];
 					imgFileId = [];
-					
 					//내문자함 텍스트 내용 입력해주기
 					$('#smsTxtArea').val(smsTxt);
-					
 					//첨부 이미지가 있는경우 이미지 처리해주기
 					if(len > 0){
-						
 						for(var i=0; i < len; i++){
-							
 							var num = i + 1;
 							var imgId = fileData[i].atchFileId;
 							var imgSn = fileData[i].fileSn;
 							var fileStreCours = fileData[i].fileStreCours;
 							var streFileNm = fileData[i].streFileNm;
 							var fileExtsn = fileData[i].fileExtsn;
-							
-							/* var imgSrc = fileStreCours + "/" + streFileNm + "." + fileExtsn; */
 							var imgSrc = '/cmm/fms/getImage2.do?atchFileId=' + imgId + '&fileSn=' + imgSn;
 							myMsgImageControl(imgSrc, "edit_n", "Template", imgId);
-							
 						}
-						
 						fnByteString(smsTxt);
-						
 					}else{
-						
 						fnByteString(smsTxt);
-						
 					}
-					
 					//내 문자함 가져오기에서 글자길이 계산을 다시한다.
-					$("#smsTxtArea").trigger("keyup");										
-					
+					$("#smsTxtArea").trigger("keyup");
 					// 문자전송 상단으로 이동
 					var sendOfT=$('.send_top').offset().top;
 					$('html,body').stop().animate({'scrollTop':sendOfT-60},250);
 				}else{
-					
 					alert("내문자함 내용을 불러오지 못하였습니다.");
 					return false;
-					
 				}
-				
 			} else if(status== 'fail'){
 				alert("내문자함 내용 입력에 실패하였습니다. !!");
 			}
@@ -3146,19 +2253,14 @@
 	
 }
 //최초 문자샘플 탭 리스트 데이터 불러오기
-//fnLetterListAjax();
-
 /* 윈도우팝업 열기 */
 /* 윈도우팝업 열기 */
 var infoPopT; // 전역 변수로 선언하여 팝업 추적
 
 function infoPop(pageUrl) {
-	// 기존 팝업이 존재하면 닫기
 	if (infoPopT && !infoPopT.closed) {
 		infoPopT.close();
 	}
-
-	// 기본값 설정
 	let width = 790, height = 350;
 
 	if (pageUrl === "adrvertisement1") {
@@ -3168,35 +2270,18 @@
 		width = 500;
 		height = 550;
 	}
-
-	// 🔥 현재 브라우저 창 크기 가져오기
 	let screenWidth = window.innerWidth || document.documentElement.clientWidth || screen.width;
 	let screenHeight = window.innerHeight || document.documentElement.clientHeight || screen.height;
-
-	// 💡 팝업을 브라우저 창 중앙에 위치시키기 위해 `top`, `left` 계산
 	let left = (screenWidth - width) / 2 + window.screenX;
 	let top = (screenHeight - height) / 2 + window.screenY;
-
-	// 📝 옵션 문자열 (정확한 중앙 정렬 적용)
-	let options = "width=" + width + ",height=" + height + ",top=" + top + ",left=" + left +
-		",fullscreen=no,menubar=no,status=no,toolbar=no,titlebar=yes,location=no,scrollbars=1";
-
-	console.log('팝업 옵션 : ', options);
-
-	// 🔥 window.open() 호출
+	let options = "width=" + width + ",height=" + height + ",top=" + top + ",left=" + left + ",fullscreen=no,menubar=no,status=no,toolbar=no,titlebar=yes,location=no,scrollbars=1";
 	infoPopT = window.open("", 'infoPop', options);
-
-	// 🔄 폼 데이터 설정 및 제출
 	document.popForm.pageType.value = pageUrl;
 	document.popForm.action = "/web/pop/infoPop.do";
 	document.popForm.method = "post";
 	document.popForm.target = "infoPop";
 	document.popForm.submit();
 }
-
-
-
-
 
 $(document).on('click', '.addressregi_btn', function() {
 	var tableData = tableL.getRows();
@@ -3224,7 +2309,6 @@
 		$("#addrGrpNm").val(""); //	새그룹명 Clear;
 	}
 });
-
 //주소록 그룹정보 불러오기
 function getAddrGroupList() {
  $.ajax({
@@ -3255,7 +2339,6 @@
 	for (var j = 0; j < jsonList.length; j++) {
 		sHtml += "	<option value='" + $.trim(jsonList[j].addrGrpId) + "' />" + $.trim(jsonList[j].addrGrpNm) + "</option>";
 	}
-	    	
 	$("#addrGrpIdInfo").html(sHtml);
 }
 
@@ -3272,30 +2355,22 @@
 		success : function(data, status){
 			if(data.isSuccess == true) {
 				if(data.isDupl == true) {
-					//alert("중복된 그룹명입니다.");
 					isReturn = false;
 				}
 			} 
-			else {
-				//alert("Message : " + msg);
-			}			
+			else {}
 		},
-		error: function (e) {
-			//alert("주소록 중복체크에 실패했습니다.");
-		}
+		error: function (e) {}
 	});
-	
 	return isReturn;	
 }
 
 /* 테스트발송 열기 */
-function fnTestSend(){
-	
-	if(!checkConf()){	//문자고 conf-check
+function fnTestSend(skipSenderAuthCheck){
+	if(!checkConf()){	//문자온 conf-check
 		alert("현재 문자 발송하기 기능 점검 중입니다.\n\n1분 후 다시 시도해주세요.");
 		return false;
 	}
-	
 	var msgForm = document.msgForm;
 	var form = document.testSendForm;
 	var loginVO = '${LoginVO}';
@@ -3316,13 +2391,6 @@
 			return false;
 		}
 	}
-	
-	//MJ_MSG_GROUP_DATA 누락데이터 체크
-	//if (getMsgGroupDataErrorCheck() == false) {
-	//	alert("고객님께서 발송한 최근문자가 발송결과에 표시되지않아, 문자발송이 임시로 차단되었습니다.\n문자고 고객센터에 문의해주세요.");
-	//	return false;	 	
-	//}
-	
 	//문자내용에 이모지가 있는지 체크
 	var strCont = msgForm.smsTxtArea.value;
 	if(!emojiCheck(strCont)) return false;
@@ -3341,128 +2409,56 @@
 			return false;
 		}
 	}
-	/* else{//문자내용에 스팸 단어 포함여부 체크
-		console.log(' : spam데이터 : ');
-		var spmData = new FormData(msgForm);
-		url = "/web/mjon/msgdata/selectSpamTxtChkAjax.do";
-		
-		$.ajax({
-	        type: "POST",
-	        url: url,
-	        data: spmData,
-	        dataType:'json',
-	        async: false,
-	        processData: false,
-	        contentType: false,
-	        cache: false,
-	        success: function (returnData, status) {
-				if(status == 'success'){ // status 확인 필요한가. 석세스 안뜨면 에러 가지 않나
-					
-					if("fail" == returnData.result){
-						
-						alert(returnData.message);
-						return false;
-						
-					}else if("loginFail" == returnData.result){
-						
-						alert(returnData.message);
-						return false;
-							
-					}else if("spams" == returnData.result){
-						
-// 						var spmFilterTxt = returnData.spmFilterTxt;
-						spamStatus = true;
-						//alert("문자 내용에 \"" + spmFilterTxt + "\"의 스팸문자가 포함되어 있습니다.");
-						alert("전송 문자내용 중 스팸문구로 의심되는 단어가 포함되어 있어 문자발송이 불가합니다.\n자세한 사항은 문자고 고객센터를 통해 문의주시기 바랍니다.");
-						return false;
-						
-					}
-					
-				} else if(status== 'fail'){
-					alert(returnData.message);
-					return false;
-				}
-			},
-	        error: function (e) { alert("시스템 오류가 발생하였습니다."); console.log("ERROR : ", e); return false;}
-	    });
-		
-	} */
 
+	// 인증 완료 후에는 현재 화면 값을 다시 검증하되 인증 상태 조회만 생략한다.
+	if (skipSenderAuthCheck !== true) {
+		ensureSenderAuthenticated(function () {
+			fnTestSend(true);
+		});
+		return false;
+	}
 
 	//단문 장문 그림(몇장인지) 판별하기 - 발송금액 계산
-
 	if($('#nowMsgType').text() == "그림"){
-		
 		msgType = '6';
-		
 		if(imgFilePath.length == 1) {
-		
 			form.eachPrice.value = '<c:out value="${picturePrice}" />';
-		
 		} else if(imgFilePath.length == 2) {
-		
 			form.eachPrice.value = '<c:out value="${picture2Price}" />';
-		
 		} else if(imgFilePath.length == 3) {
-		
 			form.eachPrice.value = '<c:out value="${picture3Price}" />';
-		
 		}
-		
 		//제목 사용한 경우
 		if($("input[name=subjectChkYn]:checked").val() == 'Y') {
 			form.mmsSubject.value = msgForm.mmsSubject.value;
 		} else {
 			form.mmsSubject.value = ""; //초기화
 		}
-		
-
 	}else if($('#nowMsgType').text() == "장문"){
-		
 		msgType = '6';
-		
 		form.eachPrice.value = '<c:out value="${longPrice}" />';
-		
 		//제목 사용한 경우
 		if($("input[name=subjectChkYn]:checked").val() == 'Y') {
 			form.mmsSubject.value = msgForm.mmsSubject.value;
 		} else {
 			form.mmsSubject.value = ""; //초기화
 		}
-		
 	}else{
-		
 		msgType = '4';
-		
 		form.eachPrice.value = '<c:out value="${shortPrice}" />';
-		
 		form.mmsSubject.value = ""; // 장문/그림 제목 초기화
 	}
-	
 	form.msgType.value = msgType;
-	
-	/* 
-	var phoneIn = $(".send_right .phone").html();
-	//팝업으로 보내기 전에 realtime class 삭제 - 스크립트 제외
-	phoneIn = phoneIn.replace(' class="realtime"',"");
-	form.msgpreview.value = phoneIn;
-	 */
-
-	
-	
 	//발신번호 세팅
 	form.callFrom.value = callFrom;
-	
 	//이미지 path 보내기
 	form.imgFilePath.value = imgFilePath;
-	
 	//광고
 	if(adverYn == 'Y'){
 		form.msgKind.value = "A";
 	}else{
 		form.msgKind.value = "N";
 	}
-	
 	form.action = "/web/pop/testSmsSendPop.do";
 	form.method = "post"; 
 	window.open("about:blank", 'testSendPop', 'width=770, height=850, top=100, left=100, fullscreen=no, menubar=no, status=no, toolbar=no, titlebar=yes, location=no, scrollbars=1');
@@ -3510,14 +2506,12 @@
 }
 
 </script>
-	
 	<div class="loading_layer">
 		<div class="loading_container">
 			<div class="bar"></div>
 			<div class="text">Loading</div>
 		</div>
 	</div>
-	
 	<form id="sentListForm" name="sentListForm" method="post">
 		<input type="hidden" name="pageType"  value=""/>
 		<input type="hidden" name="pageIndex" value=""/>
@@ -3525,8 +2519,6 @@
 		<input type="hidden" name="searchSortCnd" value="" />
 		<input type="hidden" name="searchSortOrd" value="" />		
 	</form>
-	
-	<!-- 2023 10 16 재전송 : 상세내용 팝업 -->
 	<div class="tooltip-wrap" style="z-index:110;">
 		<div class="popup-com adr_layer rev_popup01" tabindex="0" data-tooltip-con="rev_popup01"
 			data-focus="rev_popup01" data-focus-prev="rev_popup01-close" style="width: 440px;">
@@ -3542,9 +2534,6 @@
 			</div>
 		</div>
 	</div>
-	<!--// 2023 10 16  재전송 : 상세내용 팝업 -->	
-	
-	<!-- 2023 10 16  재전송 팝업 -->  
 	<div class="tooltip-wrap">
 		<div class="popup-com import_layer popup05" tabindex="0" data-tooltip-con="popup05" data-focus="popup05" data-focus-prev="popup05-close" style="width: 900px;">
 			<div class="popup_heading">
@@ -3558,43 +2547,21 @@
 			</div>			
 		</div>
 	</div>
-	<!--// 2023 10 16  재전송 팝업 -->
-
 	<div class="tooltip-wrap">
 		<div class="popup-com history_layer popup03" tabindex="0" data-tooltip-con="popup03" data-focus="popup03" data-focus-prev="popup03-close">
 			<div class="popup_heading">
-				<p><span>전송내역</p>
+				<p><span>전송내역</span></p>
 				<button type="button" class="tooltip-close" data-focus="popup03-close" id="btnLatestAddPhoneClose"><img src="/publish/images/content/layerPopup_close.png" alt="팝업 닫기"></button>
 			</div>
 			<div class="layer_in">
-				<!-- tab button -->
-				<ul class="tabType6"> 
+				<ul class="tabType6">
 					<li class="tab active"><button type="button" onclick="TabType(this,'1');">최근 전송내역</button></li>
 					<li class="tab"><button type="button" onclick="TabType(this,'2');">자주보내는 번호</button></li>
-				</ul><!--// tab button -->
-				<!-- 최근 전송내역 -->
+				</ul>
 				<div class="history_cont hascont current">
 					<div class="histroy_trans latestMsgArea" id="latestMsgArea">
 						<ul id="latestMsgUl">
-							<%-- <c:choose>
-								<c:when test="${not empty resultLatestMsgList}">
-									<c:forEach var="latestMsgList" items="${resultLatestMsgList}" varStatus="status">
-										<li id="latestLi">
-											<input type="checkbox" id="addrChk_${status.count}" name="latAddrChk" value="<c:out value='${latestMsgList.callTo}'/>">
-											<label for="addrChk_${status.count}" class="label">최근 전송내역</label>
-											<p><c:out value="${latestMsgList.callTo}"/></p>
-											<button type="button" id="latestAddrDel">
-												<img src="/publish/images/popup/close3.png"  alt="전화번호 삭제">
-											</button>
-										</li>
-									</c:forEach>
-								</c:when>
-								<c:otherwise>
-									<li>
-										<p>최근 발송 내역이 없습니다.</p>
-									</li>
-								</c:otherwise>
-							</c:choose> --%>
+
 						</ul>
 					</div>
 					<div class="popup_btn_wrap2 hisroy_btn" style="width: 230px;">
@@ -3602,31 +2569,13 @@
 						<button type="button" id="latestAddPhone">선택추가</button>
 						<button type="button" id="latestCancelPhone">선택취소</button>                      
 					</div>
-				</div><!--// 최근 전송내역 -->
-				<!-- 자주보내는 번호 -->
+				</div>
 				<div class="history_cont hascont">
 					<div class="histroy_trans" id="bookMarkMsgArea">
 						<ul id="bookMsgUl">
 									<li>
 										<p>데이터 로딩중입니다.</p>
 									</li>
-							<%-- <c:choose>
-								<c:when test="${not empty resultBookMarkMsgList}">
-									<c:forEach var="bookMarkMsgList" items="${resultBookMarkMsgList}" varStatus="status">
-										<li id="bookMarkLi">
-											<input type="checkbox" id="bokAddrChk_${status.count}" name="bookAddrChk" value="<c:out value='${bookMarkMsgList.addrPhoneNo}'/>">
-											<label for="addrChk_${status.count}" class="label">최근 전송내역</label>
-											<p><c:out value="${bookMarkMsgList.addrPhoneNo}"/></p>
-											<button type="button" id="bookMarkAddrDel"><img src="/publish/images/popup/close3.png"  alt="전화번호 삭제"></button>
-										</li>
-									</c:forEach>
-								</c:when>
-								<c:otherwise>
-									<li>
-										<p>등록된 자주 보내는 번호 내역이 없습니다.</p>
-									</li>
-								</c:otherwise>
-							</c:choose> --%>
 						</ul> 
 					</div>
 					<div class="popup_btn_wrap2 hisroy_btn" style="width: 230px;">
@@ -3634,55 +2583,19 @@
 						<button type="button" id="bookMarkAddPhone">선택추가</button>
 						<button type="button" id="bookMarkCancelPhone">선택취소</button>                      
 					</div>
-				</div><!--// 자주보내는 번호  -->         
+				</div>
 			</div>
 		</div>
-	</div><!--// 전송내역 팝업 -->
-	<!-- 엑셀 불러오기 -->
+	</div>
+
 	<form id="excelForm" name="excelForm" method="post">
 		<div class="tooltip-wrap">
-<!-- 			<div class="popup-com import_layer popup02" tabindex="0" data-tooltip-con="popup02" data-focus="popup02" data-focus-prev="popup02-close"> -->
 			<div class="popup-com adr_layer popup02" tabindex="0" data-tooltip-con="popup02" data-focus="popup02" data-focus-prev="popup02-close" style="width: 1000px;">
 			  		<%@include file="/WEB-INF/jsp/web/msgdata/include/msgDataIncludeExcel.jsp" %>
-				<!-- <div class="popup_heading">
-					<p><span>엑셀</span> 불러오기</p>
-					<button type="button" class="tooltip-close" data-focus="popup02-close"><img src="/publish/images/content/layerPopup_close.png" alt="팝업 닫기"></button>
-				</div>
-				<div class="layer_in">
-					엑셀파일 불러오기
-					<div class="hascont">
-						<div class="titBox">
-							<p>- 최대 2만 건까지 등록할 수 있습니다.</p>
-							<p>- [엑셀 불러오기]시 문서의 A, B, C, D열을 불러옵니다.(지원하는 파일 형식 : xls, xlsx)</p>
-							<p>- 이름 : 20byte, [*1*]~[*4*] : 40byte까지 입력 가능합니다.</p>
-							<p>- 휴대폰 항목은 숫자, 하이픈(-)만 인식하며, 번호 앞에 0이 생략되어도 정상 등록됩니다.
-							</p>
-							<button type="button" class="excel_btn" onclick="location.href='/cmm/fms/FileDown.do?atchFileId=FILE_000000000011651&fileSn=1'"><i></i>샘플파일 다운로드</button>
-							<button type="button" class="excel_btn" onclick="location.href='/download/msg/엑셀주소록_등록양식.xlsx'"><i class="downroad"></i>샘플파일 다운로드</button>
-						</div>
-						<div class="attachedFile">
-							<label for="" class="attachedFile_label">첨부파일</label>
-							<input type="text" id="excelNm" readonly>
-							<input type="file" id="excelFile" accept=".xls, .xlsx" onchange="excelExport(event); return false;" style="display:none"/>
-							<button type="button" class="btnType btnType6 c3">찾아보기</button>
-							<p><span class="vMiddle">*</span> 첨부된 파일은 <span class="c_e40000">[추가]버튼을 클릭</span>하셔야 받는 사람에 등록됩니다.</p>
-						</div>
-					</div>// 엑셀파일 불러오기
-					<div class="popup_btn_wrap2">
-						<button type="button" class="tooltip-close" data-focus="popup02-close"  data-focus-next="popup02" id="excelAdd">추가</button>
-						<button type="button" class="tooltip-close" data-focus="popup02-close"  data-focus-next="popup02">닫기</button>                      
-					</div>
-				</div>
-				
-				
-				
-				 -->
-				 
 			</div>
-		</div><!--// 엑셀 불러오기 -->
+		</div>
 	</form>
-	
-	<!-- 이미지첨부 불러오기 시작-->
+
 	<form id="imgForm" name="imgForm" method="post">
 		<div class="tooltip-wrap">
 			<div class="popup-com import_layer popupImg" tabindex="0" data-tooltip-con="popupImg" data-focus="popupImg" data-focus-prev="popupImg-close">
@@ -3691,7 +2604,6 @@
 					<button type="button" class="tooltip-close" data-focus="popupImg-close"><img src="/publish/images/content/layerPopup_close.png" alt="팝업 닫기"></button>
 				</div>
 				<div class="layer_in">
-					<!-- 첨부파일 불러오기 -->
 					<div class="hascont">
 						<div class="titBox">
 							<p>- 첨부할 이미지를 불러옵니다.(지원하는 파일 형식 : jpg, jpeg)</p>
@@ -3709,7 +2621,7 @@
 							<button type="button" class="btnType btnType6 upImg">찾아보기</button>
 							<p><span class="vMiddle">*</span> 첨부된 파일은 <span class="c_e40000">[추가]버튼을 클릭</span>하셔야 내용에 이미지가 등록됩니다.</p>
 						</div>
-					</div><!--// 첨부파일 불러오기 -->
+					</div>
 					<div class="popup_btn_wrap2">
 						<button type="button" class="tooltip-close" data-focus="popupImg-close"  data-focus-next="popupImg" id="imgAdd" onclick="imgExport(event); return false;">이미지 등록</button>
 						<button type="button" class="tooltip-close closeResizeImg" data-focus="popupImg-close"  data-focus-next="popupImg">닫기</button>                      
@@ -3718,13 +2630,11 @@
 			</div>
 		</div>
 	</form>
-	<!--// 이미지 첨부 불러오기 끝 -->
-	
-	<!-- 주소록 불러오기 -->
+
 	<div class="tooltip-wrap">
 		<div class="popup-com import_layer popup06 adr_call_popup" tabindex="0" data-tooltip-con="popup06" data-focus="popup06" data-focus-prev="popup06-close" style="width: 1000px">
 			<div class="popup_heading">
-				<p><span>주소록 불러오기</p>
+				<p><span>주소록 불러오기</span></p>
 				<button type="button" onClick="javascript:addrClose(); return false;">
 				<img src="/publish/images/content/layerPopup_close.png" alt="팝업 닫기"></button>
 			</div>
@@ -3749,9 +2659,6 @@
 								<div id="addrGroupLoad">
 								</div>
 							</div>
-							<!-- <div class="popup_btn">
-								<button type="button" class="btnType" onClick="javascript:fnSelectAddrGrpList(); return false;">선택 그룹 추가</button>
-							</div> -->
 						</div>
 						<div class="adr_pop_right">
 							<div class="clearfix">
@@ -3767,26 +2674,20 @@
 									<input type="text" id="searchAddrKeyword" name="searchAddrKeyword" placeholder="검색어를 입력하세요"  onfocus="this.placeholder=''" onblur="this.placeholder='검색어를 입력하세요'" >
 									<button type="button" class="btnType btnType17"  onClick="javascrit:fnAddrSearch(); return false;">검색</button>
 								</div>
-								<!-- table -->
-								<div class="adr_excel adr_pop_list2 callAddr_box" style="max-width:722px">
-								</div>
-								<!--// table -->
+								<div class="adr_excel adr_pop_list2 callAddr_box" style="max-width:722px"></div>
 							</div>
 							<div class="popup_btn_wrap2">
-<!-- 								<button type="button" onClick="javascript:addrToList(); return false;">추가</button> -->
 								<button type="button" onClick="javascript:addrToList_advc('all'); return false;">전체추가</button>
 								<button type="button" onClick="javascript:addrToList_advc('select'); return false;">선택추가</button>
 								<button type="button" onClick="javascript:addrClose(); return false;">닫기</button>
 							</div>
-							<%-- 주소록 레이어 팝업 닫기 실행 코드 --%>
-							<input type="hidden" name="btnAddrClose" id="btnAddrClose" class="tooltip-close closeAddr" data-focus="popup06-close" />         
+							<input type="hidden" name="btnAddrClose" id="btnAddrClose" class="tooltip-close closeAddr" data-focus="popup06-close" />
 						</div>
 					</form>
 				</div>
 			</div>
 		</div>
-	</div><!--// 주소록 불러오기 -->
-	<!-- 주소록에 등록 data-tooltip:duplication_layer -->
+	</div>
 	<div class="tooltip-wrap">
 		<div class="popup-com addressregi_layer" tabindex="0" data-tooltip-con="addressregi_layer" data-focus="addressregi_layer" data-focus-prev="addressregi_layer-close" style="width: 680px;">
 			<div class="popup_heading">
@@ -3821,36 +2722,6 @@
 			</div>
 		</div>
 	</div>
-	<!--// 주소록에 등록 팝업 -->
-	
-	<!-- 이벤트 잔여 캐시로 발송 가능한 문자 건수 정보 -->
-	<%-- <div class="tooltip-wrap">
-		<div class="popup-com import_layer popupEventSendInfo" tabindex="0" data-tooltip-con="popupEventSendInfo" data-focus="popupEventSendInfo" data-focus-prev="popupEventSendInfo-close">
-			<div class="popup_heading">
-				<p>이벤트 캐시 전송 가능 건수</p>
-				<button type="button" class="tooltip-close" data-focus="popupEventSendInfo-close"><img src="/publish/images/content/layerPopup_close.png" alt="팝업 닫기"></button>
-			</div>
-			<div class="layer_in">
-				<!-- 발송 건수 정보 표시 -->
-				<div class="hascont">
-					<div class="titBox">
-						<p> 첫 결제 이벤트 잔액(캐시) : <fmt:formatNumber value="${eventRemainCash}" pattern="#,###.#"/> 원</p>
-						<p id="evnShortMsg">단문(<fmt:formatNumber value="${eventMberInfo.eventShortPrice}" pattern="#,###.#"/>) : <fmt:formatNumber value="${(eventRemainCash / eventMberInfo.eventShortPrice) - ((eventRemainCash / eventMberInfo.eventShortPrice) % 1) }" pattern="#,###"/> 건</p>
-						<p id="evnShortMsg">장문(<fmt:formatNumber value="${eventMberInfo.eventLongPrice}" pattern="#,###.#"/>) : <fmt:formatNumber value="${(eventRemainCash / eventMberInfo.eventLongPrice) - ((eventRemainCash / eventMberInfo.eventLongPrice) % 1)}" pattern="#,###"/> 건</p>
-						<p id="evnShortMsg">그림 1장(<fmt:formatNumber value="${eventMberInfo.eventPicturePrice}" pattern="#,###.#"/>) : <fmt:formatNumber value="${(eventRemainCash / eventMberInfo.eventPicturePrice) - ((eventRemainCash / eventMberInfo.eventPicturePrice) % 1)}" pattern="#,###"/> 건</p>
-						<p id="evnShortMsg">그림 2장(<fmt:formatNumber value="${eventMberInfo.eventPicture2Price}" pattern="#,###.#"/>) : <fmt:formatNumber value="${(eventRemainCash / eventMberInfo.eventPicture2Price) - ((eventRemainCash / eventMberInfo.eventPicture2Price) % 1)}" pattern="#,###"/> 건</p>
-						<p id="evnShortMsg">그림 3장(<fmt:formatNumber value="${eventMberInfo.eventPicture3Price}" pattern="#,###.#"/>) : <fmt:formatNumber value="${(eventRemainCash / eventMberInfo.eventPicture3Price) - ((eventRemainCash / eventMberInfo.eventPicture3Price) % 1)}" pattern="#,###"/> 건</p>
-						
-					</div>
-				</div><!--// 발송 건수 정보 표시 -->
-				<div class="popup_btn_wrap2">
-					<button type="button" class="tooltip-close closePopupEventSendInfo" data-focus="popupEventSendInfo-close"  data-focus-next="popupEventSendInfo">닫기</button>                      
-				</div>
-			</div>
-		</div>
-	</div> --%>
-	
-	<!-- 이벤트 잔여 캐시 정보 표시 팝업 -->
 	<div class="tooltip-wrap">
 		<div class="popup-com event_cash_popup popup07" tabindex="0" data-tooltip-con="popup07" data-focus="popup07" data-focus-prev="popup07-close">
 			<div class="popup_heading">
@@ -3898,8 +2769,7 @@
 			</div>
 		</div>
 	</div>
-	
-	
+
     <form id="msgForm" name="msgForm" method="post">
 		<input type="hidden" id="msgType" name="msgType" value="4" /><!-- 문자 종류 : 단문 - 4, 장문/그림 - 6 -->
 		<input type="hidden" id="imgFilePath" name="imgFilePath" value="" /><!-- 선택 이미지 파일 경로 정보 , 로 구분 최대 3장까지 -->
@@ -3933,21 +2803,17 @@
 		<input type="hidden" id="txtReplYn" name="txtReplYn" value="N"/><!-- 변환문자 유무 -->
 		<input type="hidden" id="shortMsgCnt" name="shortMsgCnt" value="0"/><!-- 단문 변환문자 건수  -->
 		<input type="hidden" id="longMsgCnt" name="longMsgCnt" value="0"/><!-- 장문 변환문자 건수 -->
-		
 		<!-- 주소록 등록 -->
 		<input type="hidden" id="addrGrpId" name="addrGrpId" value=""/>
 		<input type="hidden" id="addrGrpNm" name="addrGrpNm" value=""/>
-		
 		<!-- 이벤트 관련 변수 -->
 		<input type="hidden" id="eventStatus" name="eventStatus" value="<c:out value='${eventMberInfo.eventStatus}'/>"/>
 		<input type="hidden" id="eventRemainCash" name="eventRemainCash" value="<c:out value='${eventRemainCash}'/>"/>
 		<input type="hidden" id="eventYn" name="eventYn" value="N"/>
-		
 		<!-- 스팸문자 관련 변수 -->
 		<input type="hidden" id="spamKeyword" name="spamKeyword"/>
 		<input type="hidden" id="spamStatus" name="spamStatus"/>
 		<input type="hidden" id="exceptSpamYn" name="exceptSpamYn" value="<c:out value='${exceptSpamYn}'/>"/>
-		
 		<!-- 이벤트 캐시 부족시 일반캐시로 계산을 위해 저장, 우영두 2023.03.28 -->
 		<input type="hidden" id="norsPrice" name="norsPrice" value="<c:out value="${norShortPrice}"/>"/><!-- 단문문자 단가(이벤트 단가 제외) -->
 		<input type="hidden" id="normPrice" name="normPrice" value="<c:out value="${norLongPrice}"/>"/><!-- 장문문자 단가(이벤트 단가 제외) -->
@@ -3957,14 +2823,9 @@
 		<input type="hidden" id="evnPhoneNumCnt" name="evnPhoneNumCnt" value="0"/><!-- 치환 이벤트 발송 수신자 갯수 -->
 		<input type="hidden" id="evnShortMsgCnt" name="evnShortMsgCnt" value="0"/><!-- 치환 이벤트 단문 건수 -->
 		<input type="hidden" id="evnLongMsgCnt" name="evnLongMsgCnt" value="0"/><!-- 치환 이벤트 장문 건수 -->
-		
 		<input type="hidden" id="blineCode" name="blineCode" value="<c:out value="${blineCode}"/>"/>
-		
 		<input type="hidden" id="tabDision" value="<c:out value="${tabDision}"/>"/>
-		
 		<input type="hidden" id="send_adYn" name="send_adYn" value="${tabDision eq 'tab02' ? 'Y' : 'N'}"/>
-		
-	<!-- <div class="top_content current" id="tab1_1"> -->
 		<div class="heading">
 			<h2>
 				<c:choose>
@@ -3977,22 +2838,10 @@
 					</c:otherwise>
 				</c:choose>
 			</h2>
-			<!-- /web/mjon/msgdata/selectMsgDataSMLViewAjax.do -->
-			<!-- MsgDataSMLView.jsp -->
 			<button type="button" class="button info" onclick="infoPop('selectMsgDataView1');">사용안내</button>
 	    </div>
 	    <div class="send_general">
 	        <div class="send_left">
-	        	<%-- <dl class="myprice_view">
-	        		<dt>나의 요금제</dt>
-	        		<dd>
-	        			<ul>
-							<li><span class="type">단문</span><span class="price"><c:out value="${shortPrice}"/></span>원</li>
-							<li><span class="type">장문</span><span class="price"><c:out value="${longPrice}"/></span>원</li>
-							<li><span class="type">그림</span><span class="price"><c:out value="${picturePrice}"/></span>원 <span class="count">(2장 : <c:out value="${picture2Price}"/>원 / 3장 : <c:out value="${picture3Price}"/>원)</span></li>
-						</ul>
-	        		</dd>
-	        	</dl> --%>
 	            <table class="tType1">
 	                <caption>일반 문자보내기 분류 번호 내용 등을 입력하는 표</caption>
 	                <colgroup>
@@ -4000,35 +2849,29 @@
 	                    <col style="width: auto;">
 	                </colgroup>
 	                <tbody>
-	                    <!-- <tr>
-	                        <th scope="row">문자분류</th>
-	                        <td class="send_cf">
-	                            <input class="send_adYnN" id="send_adYnN" type="radio"name="send_adYn" value="N" checked="checked">
-	                            <label class="send_adYnN" for="send_adYnN">일반</label>
-	                            <input id="send_adYnY" type="radio" name="send_adYn" value="Y">
-	                            <label for="send_adYnY">광고</label>
-	                        </td>
-	                    </tr> -->
 	                    <tr>
                             <th scope="row">발신번호</th>
                             <td class="put_num">
-                            	<div class="form_wrap left">
-	                                <label for="callFrom" class="label"></label>
-	                                <select id="callFromList" name="callFromList" class="sel_number">
-	                                	<c:choose>
-	                                		<c:when test="${not empty resultPhonList}">
-	                                			<c:forEach var="phonList" items="${resultPhonList}" varStatus="status">
-													<option value="${phonList}">${phonList}</option>
-												</c:forEach>
-	                                		</c:when>
-	                                		<c:otherwise>
-	                                			<option value="">등록된 발신 번호가 없습니다.</option>
-	                                		</c:otherwise>
-	                                	</c:choose>
-									</select>
-	                                <button type="button" class="btnType btnType6" onclick="location.href='/web/user/sendNumberManage.do'">번호등록</button>
-                                </div>
-                            </td>
+                                <label for="callFrom" class="label"></label>
+                                <select id="callFromList" name="callFromList" class="sel_number">
+                                	<c:choose>
+                                		<c:when test="${not empty resultPhonList}">
+                                			<c:forEach var="result" items="${resultPhonList}" varStatus="status">
+												<option value="${result.phoneNumber}">
+														${result.phoneNumber}
+													(${result.phmLineType eq 'M' ? '무선' : result.phmLineType eq 'R' ? '대표' : result.phmLineType eq 'L' ? '유선' : '-'})
+												</option>
+											</c:forEach>
+                                		</c:when>
+                                		<c:otherwise>
+                                			<option value="">등록된 발신 번호가 없습니다.</option>
+                                		</c:otherwise>
+                                	</c:choose>
+								</select>
+                                <button type="button" class="btnType btnType6" onclick="location.href='/web/user/sendNumberManage.do'">번호등록</button>
+								<div class="badge blue sender_auth_verified" style="display:none;"><i></i>인증완료</div>
+								<div class="badge yellow auth_popup_open sender_auth_required"><i></i>인증 확인</div>
+							</td>
                         </tr>
 						<tr class="msg_title">
                             <th scope="row">제목</th>
@@ -4047,15 +2890,12 @@
                            		</ul>
                             </td>
                         </tr>
-						<!-- 문자 내용 영역 -->
 						<tr>
                             <th scope="row" class="vTop">내용</th>
                             <td class="putText">
                                 <div class="clearfix">
                                     <div class="put_left short">
-                    					<!-- 업로드한 이미지의 썸네일 영역 -->
                                     	<ul class="thumb_wrap none liOnImg"></ul>
-                   						<!-- //업로드한 이미지의 썸네일 영역 -->
                                         <label for="smsTxtArea" class="label"></label>
                                         <c:if test="${tabDision eq 'tab02'}">
                                         	<div class="ad_txt"><p>(광고)</p></div>
@@ -4068,7 +2908,6 @@
 											<div name="afterDeny">
 												<p>
 												<span class="fwMd" id="msgLeng">
-												
 												<c:choose>
 													<c:when test="${tabDision eq 'tab02'}">
 														27
@@ -4096,7 +2935,6 @@
 													<a href="#none">사랑</a>
 													<a href="#none">그외</a>
 												</div>
-												<!-- 특수문자 부분 -->
 												<div class="cnt_character on">
 													<div class="box_character">
 														<c:forEach var="symbolList" items="${symbolList}" varStatus="status">
@@ -4106,7 +2944,6 @@
 														</c:forEach>
 													</div>
 												</div>
-												<!-- 웃음 -->
 												<div class="cnt_character emt_character">
 													<div class="box_character">
 														<c:forEach var="symbolList" items="${symbolList}" varStatus="status">
@@ -4116,8 +2953,6 @@
 														</c:forEach>
 													</div>
 												</div>
-												<!-- //웃음 -->
-												<!-- 슬픔 -->
 												<div class="cnt_character emt_character">
 													<div class="box_character">
 														<c:forEach var="symbolList" items="${symbolList}" varStatus="status">
@@ -4127,8 +2962,6 @@
 														</c:forEach>
 													</div>
 												</div>
-												<!-- //슬픔 -->
-												<!-- 분노 -->
 												<div class="cnt_character emt_character">
 													<div class="box_character">
 														<c:forEach var="symbolList" items="${symbolList}" varStatus="status">
@@ -4138,8 +2971,6 @@
 														</c:forEach>
 													</div>
 												</div>
-												<!-- //분노 -->
-												<!-- 사랑 -->
 												<div class="cnt_character emt_character">
 													<div class="box_character">
 														<c:forEach var="symbolList" items="${symbolList}" varStatus="status">
@@ -4149,8 +2980,6 @@
 														</c:forEach>
 													</div>
 												</div>
-												<!-- //사랑 -->
-												<!-- 그외 -->
 												<div class="cnt_character emt_character emt_etc ">
 													<div class="box_character">
 														<c:forEach var="symbolList" items="${symbolList}" varStatus="status">
@@ -4160,7 +2989,6 @@
 														</c:forEach>
 													</div>
 												</div>
-												<!-- //그외 -->
 												<button type="button" class="btn_close" onclick="miniPopup(this);">닫기</button>
 											</div>
 										</div>
@@ -4184,7 +3012,6 @@
 											</div>
 										</div>
                                         <button type="button" class="btnType btnType7" onclick="javascript:fnGoMyMsg(); return false;">내문자함</button>
-                                        <%-- <button type="button" data-tooltip="popupImg"  class="btnType btnType7">이미지 첨부</button> --%>
                                         <button type="button" class="btnType btnType8" onclick="openPhotoEditor();">이미지 불러오기</button>
 	                                    <div class="send_btnWrap">
 	                                        <button type="button" class="btnType btnType9" onclick="javascript:fn_saveMyMsg(); return false;">문자저장</button>
@@ -4199,31 +3026,17 @@
                             <th scope="row" class="vTop">받는사람</th>
                             <td class="putText">
                                 <div class="clearfix receipt_num">
-<!-- 									<div class="receipt_num_top"> -->
-<!-- 										<label for="callTo" class="label">받는 번호입력</label> -->
-
-<!-- 										<input type="text" id="callTo" name="callTo" oninput="this.value = this.value.replace(/[^0-9.]/g, '').replace(/(\..*)\./g, '$1');"  placeholder="번호를 입력하세요" 
-											onfocus="this.placeholder=''" onblur="this.placeholder='번호를 입력하세요'" style="width:340px;"> -->
-
-<!-- 										<button type="button" class="btnType btnType6 addCallToF">번호추가</button> -->
-<!-- 										<span><span class="vMiddle">*</span> 중복번호는 한번만 추가됩니다.</span> -->
-<!-- 									</div> -->
-
 									<div class="receipt_num_top">
 										<label for="" class="label">받는 번호입력</label>
-										<!-- <input type="text" placeholder="번호를 입력하세요" onfocus="this.placeholder=''" onblur="this.placeholder='번호를 입력하세요'" style="width:340px;"> -->
-<!-- 												oninput="this.value = this.value.replace(/[^0-9.\n]/g, '').replace(/(\..*)\./g, '$1');" -->
 										<textarea name="callTo" id="callTo" cols="30" rows="10" class="receipt_num" 
 												placeholder="번호를 입력하세요" 
 												onfocus="this.placeholder=''" 
 												onblur="this.placeholder='번호를 입력하세요'" 
 												style="width:340px;"></textarea>
-										<!-- <button type="button" class="btnType btnType6">번호추가</button> -->
 										<div class="btn_popup_wrap">
 											<button type="button" class="btnType btnType6 btn_add_number addCallToF">번호추가<i class="qmMark"></i></button>
 											<span style="display:block;margin:10px 0 0 0;"><span class="vMiddle">*</span> 중복번호는 한번만 추가됩니다.</span>
 											<div class="error_hover_cont send_hover_cont">
-<!-- 												<p>휴대폰 번호 입력 시 해당 휴대폰 번호에 대한 형식이 어긋나거나 휴대폰 번호에 오류가 있는지 등을 검사하는 기능</p> -->
 												<p>줄바꿈(Enter) 기준으로 핸드폰 번호 입력이 가능합니다.</p>
 												<span>
 													(예시)<br/>
@@ -4237,23 +3050,6 @@
 									</div>
                                     <div class="receipt_num_midde">
                                         <div class="listType list01 callList_box">
-                                        <!-- 
-											<div class="list_table list_head">
-												<div class="cb_wrap">
-													<label for="" class="label"></label>
-													<input type="checkbox">
-												</div>
-												<div class="list_table_num">
-													<p>번호</p>
-													<img src="/publish/images/sortUp.png">
-													<img src="/publish/images/sortDown.png">
-												</div>
-												<div class="list_table_name">
-													<p>이름</p>
-													<img src="/publish/images/sortUp.png">
-													<img src="/publish/images/sortDown.png">
-												</div>
-											</div> -->
 										</div>
 										<div class="put_right">
 											<div class="btn_popup_wrap spc_wrap">
@@ -4271,13 +3067,6 @@
 											<div class="btn_popup_wrap">
 												<button type="button" data-tooltip="popup05" class="btnType btnType7 tab2" onclick="getMjMsgSentListAll('1');">재전송</button>
 											</div>
-											<!-- <div class="btn_popup_wrap check_validity_wrap">
-												<button type="button" class="btnType btnType7 check_validity">오류 검사 <i class="qmMark"></i></button>
-												<div class="error_hover_cont send_hover_cont">
-													<p>휴대폰 번호 입력 시 해당 휴대폰 번호에 대한 형식이 어긋나거나 휴대폰 번호에 오류가 있는지 등을 검사하는 기능</p>
-													<span>(예시) 010-1234-0001(O) / 010-12345-0001(X)</span>
-												</div>
-											</div> -->
 										</div>
 									</div>
 									<div class="list_bottom">
@@ -4287,7 +3076,6 @@
 										</div>
 										<div class="list_bottom_right">
 											<p>총 <span class="c_e40000" id="rowTotCnt">0</span>건 / 중복 <span class="c_002c9a" id="rowDupCnt">0</span>건</p>
-											<%--<button type="button" class="address_reg2 registAddr">주소록에 등록</button>--%>
 											<button type="button" class="address_reg2 addressregi_btn">주소록에 등록</button>
 										</div>
 									</div>
@@ -4314,16 +3102,6 @@
 										<div>
 											<input type="radio" id="radio_bill_1" name="radio_bill" checked="checked">
 											<label for="radio_bill_1">보유잔액</label>
-											<%-- <label for="radio_bill_1">내 충전금 (<span id="spUserMoney">
-												<c:choose>
-													<c:when test="${not empty userMoney}">
-														<c:out value="${commaPrice}"/>
-													</c:when>
-													<c:otherwise>
-														0
-													</c:otherwise>
-												</c:choose>
-											</span>원)</label> --%>
 											<label for="userMoney" class="label">보유잔액</label>
 											<input type="text" id="userMoney" name="userMoney" value="<c:out value='${commaPrice}'/>" readonly>
 											<span class="won">원</span>
@@ -4333,8 +3111,7 @@
 												<div><button type="button" data-tooltip="popup07" class="btnType btnType3 btn_event_cash">이벤트 잔액</button></div>
 										</c:if>
 									</div>
-									
-									<!--발송금액 아래 등급별 가격안내 추가-->
+
 									<div class="my_price_wrap" id="myPriceWrap" style="display: none;">
 										<div class="center-line"></div>
 										<ul>
@@ -4357,26 +3134,10 @@
 												(2장 : <fmt:formatNumber value="${picture2Price}" pattern="###.#" />원 / 3장 : <fmt:formatNumber value="${picture3Price}" pattern="###.#" />원)
 											</li>
 										</ul>
-									</div>	
-									<!-- End -->								
-									
+									</div>
                                 </div>
                             </th>
                         </tr>
-                        <%-- <tr>
-                            <th scope="row">추천인 ID</th>
-                            <td class="check_num">
-                                <input id="radio5" type="radio"name="recom" checked="checked">
-                                <label for="radio5">없음</label>
-                                <input id="radio6" type="radio" name="recom">
-                                <label for="radio6">있음</label>
-                                <div>
-                                    <label for="recommId" class="label"></label>
-                                    <input type="text" size="20" id="recommId" name="recommId" value="" placeholder="추천인 아이디 입력" onfocus="this.placeholder=''" onblur="this.placeholder='추천인 아이디 입력'">
-                                    <button type="button" class="btnType btnType9" id="recommBtn">추천인 확인</button>
-                                </div>
-                            </td>
-                        </tr> --%>            
                     </tbody>
                 </table>
             </div>
@@ -4406,7 +3167,6 @@
 									<button type="button" class="fontMinus"><img src="/publish/images/content/font_minus.png"></button>
 								</div>
 							</div>
-							<!-- 텍스트 미리보기 -->
 							<div class="text_preview">
 								<div class="preiew_img preViewShort"></div>
 								<div class="preview_auto">
@@ -4424,7 +3184,6 @@
 								</c:choose>
 								</div>
 							</div>
-							<!-- //텍스트 미리보기 -->
 						</div>
 					</div>
 					<p class="addText">※ 단말기 설정에 따라 다르게 보일 수 있습니다</p>
@@ -4508,7 +3267,6 @@
                 </div>
              </div>
         </div>
-    <!-- </div> --><!--// tab content1 -->
 </form>
 <form id="latestMsgForm" name="latestMsgForm" method="post">
 	<input type="hidden" id="userId" name="userId" value="${LoginVO.id}"/>
@@ -4531,3 +3289,4 @@
 	<input type="hidden" name="smsTxt" value=""/>
 </form>
 
+<%@ include file="include/authNumberPopup.jspf"%>
src/main/webapp/WEB-INF/jsp/web/msgdata/MsgDataView.jsp
--- src/main/webapp/WEB-INF/jsp/web/msgdata/MsgDataView.jsp
+++ src/main/webapp/WEB-INF/jsp/web/msgdata/MsgDataView.jsp
@@ -6,6 +6,13 @@
 
 <!-- <script src="/publish/js/content.js"></script> -->
 
+<style type="text/css">
+/* pop_msg_spam의 is-notice가 있는지 여부에 따라 분기처리
+ is-notice가 없으면 spam_notice_area, spam_result_btn 숨김처리 */
+.pop_msg_spam:not(.is-notice) .spam_notice_area,
+.pop_msg_spam:not(.is-notice) .spam_result_btn { display: none; }
+</style>
+
 <script type="text/javascript">
 
 var loginVO = '${loginVO}';
@@ -58,7 +65,7 @@
 </script>
 
 
-<script type="text/javascript" src="<c:url value='/js/web/msgdata/msgDataView.js?date=20241218'/>"></script>
+<script type="text/javascript" src="<c:url value='/js/web/msgdata/msgDataView.js?date=20260820'/>"></script>
 <script type="text/javascript" src="<c:url value='/js/web/msgdata/init.js?date=20241218'/>"></script>
 
 
@@ -83,7 +90,7 @@
 				<button type="button" class="tooltip-close" onclick="msgSuccessClose(this);"><img src="/publish/images/content/layerPopup_close.png" alt="팝업 닫기"></button>
 			</div>
 			<div class="layer_in">
-				<div class="msg_text">발송 성공 : <strong>1</strong> 건,수신거부 : <span>0</span>건의<br>문자가 발송 되었습니다.</div>
+				<div class="msg_text">발송 성공: <strong>1</strong> 건, 수신거부: <span>0</span>건의<br>문자가 발송 되었습니다.</div>
 			</div>
 			<div class="popup_btn">
 				<button type="button" onclick="msgResultLink(); return false;">문자 발송결과 바로가기</button>                      
@@ -108,16 +115,23 @@
 	</div>
 	<div class="tooltip-wrap">
 		<!-- 문자발송 스팸 이용정지 레이어팝업 -->
-		<div class="popup-com pop_msg_spam">
+		<div class="popup-com pop_msg_spam" style="width:540px;">
 			<div class="popup_heading">
 				<p>문자 전송 결과</p>
 				<button type="button" class="tooltip-close" onclick="msgSpamClose(this);"><img src="/publish/images/content/layerPopup_close.png" alt="팝업 닫기"></button>
 			</div>
-			<div class="layer_in">
+			<div class="layer_in" style="padding:50px 30px;">
 				<div class="msg_text"></div>
-			</div>
-			<div class="popup_btn">
-				<button type="button" class="tooltip-close" onclick="msgSpamClose(this);">확인</button>                      
+				<div class="spam_notice_area">
+					<p class="lightred_box fill" style="margin:30px 0 0 0;">
+						문자 내용에 <span class="fwBold c_e40000">금칙어 또는 링크</span>가 탐지되어 발송이 <span class="fwBold c_e40000">보류</span>되었습니다. <br>
+						<span class="desc_text">검토를 거친 이후, 정상적인 문자는 그대로 발송됩니다.</span>
+					</p>
+				</div>
+				<div class="popup_btn" style="margin:30px 0 0 0;">
+					<button type="button" class="btnType btnType2 tooltip-close spam_result_btn" onclick="msgResultLink(); return false;">문자 발송결과 바로가기</button>
+					<button type="button" class="btnType fill_gray tooltip-close" onclick="msgSpamClose(this);">확인</button>
+				</div>
 			</div>
 		</div>
 	</div>
 
src/main/webapp/WEB-INF/jsp/web/msgdata/include/authNumberPopup.jspf (added)
+++ src/main/webapp/WEB-INF/jsp/web/msgdata/include/authNumberPopup.jspf
@@ -0,0 +1,414 @@
+<%@ page pageEncoding="UTF-8" %>
+
+<script>
+  var authState = window.msgSenderAuthState || {
+    type: null,
+    phone: null,
+    email: null,
+    continuation: null,
+    checking: false,
+    sending: false,
+    preserveContinuation: false
+  };
+  window.msgSenderAuthState = authState;
+
+  var limitTimers = window.msgSenderAuthTimers || {};
+  window.msgSenderAuthTimers = limitTimers;
+
+  $(function () {
+    $(".auth_popup_open").off("click.senderAuth").on("click.senderAuth", function () {
+      openSelectedSenderAuthPopup();
+    });
+
+    $("#callFromList").off("change.senderAuth").on("change.senderAuth", function () {
+      authState.continuation = null;
+      refreshSenderAuthStatus();
+    });
+
+    $(".required_auth_btn").off("click.senderAuth").on("click.senderAuth", function () {
+      closeAuthPopup(".required_auth_popup");
+      openSelectedSenderAuthPopup();
+    });
+
+    $(".required_auth_popup .tooltip-close, .auth_number_popup .tooltip-close")
+      .off("click.senderAuth").on("click.senderAuth", function () {
+      if (!authState.preserveContinuation) authState.continuation = null;
+    });
+
+    $(".auth_number_send").off("click.senderAuth").on("click.senderAuth", function () {
+      sendAuthNumber($(this));
+    });
+
+    $(".auth_submit").off("click.senderAuth").on("click.senderAuth", function () {
+      verifyAuthNumber();
+    });
+
+    refreshSenderAuthStatus();
+  });
+
+  function getSelectedSenderPhone() {
+    return $.trim($("#callFromList").val() || "").replace(/[^0-9]/g, "");
+  }
+
+  function senderAuthAjax(url, data, onSuccess, onComplete) {
+    $.ajax({
+      type: "POST",
+      url: url,
+      data: JSON.stringify(data),
+      contentType: "application/json; charset=UTF-8",
+      dataType: "json",
+      success: function (response) {
+        if (!response || response.success !== true) {
+          alert(response && response.message ? response.message : "발신번호 인증 처리에 실패했습니다.");
+          return;
+        }
+        onSuccess(response.data, response.message);
+      },
+      error: function () {
+        alert("발신번호 인증 처리 중 오류가 발생했습니다.");
+      },
+      complete: function () {
+        if (onComplete) onComplete();
+      }
+    });
+  }
+
+  function ensureSenderAuthenticated(continuation) {
+    const phone = getSelectedSenderPhone();
+    if (!phone) {
+      alert("발신번호를 확인해주세요.");
+      return;
+    }
+    if (authState.checking) return;
+
+    authState.checking = true;
+    senderAuthAjax("/web/mjon/msgdata/sender-auth/status.do", {callFrom: phone}, function (data) {
+      updateSenderAuthBadge(data.verified);
+      if (data.verified) {
+        continuation();
+        return;
+      }
+      authState.continuation = continuation;
+      openTooltip("required_auth_popup");
+    }, function () {
+      authState.checking = false;
+    });
+  }
+
+  function refreshSenderAuthStatus() {
+    const phone = getSelectedSenderPhone();
+    if (!phone) {
+      updateSenderAuthBadge(false);
+      return;
+    }
+    senderAuthAjax("/web/mjon/msgdata/sender-auth/status.do", {callFrom: phone}, function (data) {
+      updateSenderAuthBadge(data.verified);
+    });
+  }
+
+  function updateSenderAuthBadge(verified) {
+    $(".sender_auth_verified").toggle(verified === true);
+    $(".sender_auth_required").toggle(verified !== true);
+  }
+
+  function openSelectedSenderAuthPopup() {
+    const phone = getSelectedSenderPhone();
+    if (!phone) {
+      alert("발신번호를 확인해주세요.");
+      return;
+    }
+    senderAuthAjax("/web/mjon/msgdata/sender-auth/status.do", {callFrom: phone}, function (data) {
+      updateSenderAuthBadge(data.verified);
+      if (data.verified) {
+        alert("이미 인증이 완료된 발신번호입니다.");
+        if (authState.continuation) {
+          const continuation = authState.continuation;
+          authState.continuation = null;
+          continuation();
+        }
+        return;
+      }
+      openAuthPopup(data.channel, phone, data.maskedDestination);
+    });
+  }
+
+  function closeAuthPopup(selector) {
+    authState.preserveContinuation = true;
+    $(selector + " .tooltip-close").first().trigger("click");
+    authState.preserveContinuation = false;
+  }
+
+  function openAuthPopup(type, phone, email) {
+    const popupElement = $(".auth_number_popup");
+
+    resetAuthPopup();
+
+    authState.type = type;
+    authState.phone = phone;
+    authState.email = email || "";
+
+    popupElement.find(".auth_phone").val(phone);
+    if (type === "email") {
+      popupElement.find(".auth_title").html(
+              '문자 발송을 위해 <span class="c_002c9a fwBold">발신번호 인증</span>이 필요합니다.' +
+              '<br><span class="c_002c9a fwBold">회원가입 담당자 이메일</span>로 전송된 인증번호를 입력해 주세요.'
+      );
+      popupElement.find(".auth_email_row").show();
+      popupElement.find(".auth_email").val(email || "");
+      popupElement.find(".desc_text").text("인증번호 전송을 눌러 담당자 이메일로 인증번호를 받아주세요.");
+      popupElement.find(".auth_notice").text("Daum 메일은 정상 인증이 어려울 수 있습니다. 회원정보에서 이메일을 변경하여 시도해 주세요.");
+    } else {
+      popupElement.find(".auth_title").html('문자 발송을 위해 <span class="c_002c9a fwBold">발신번호 인증</span>이 필요합니다.' + '<br>발신번호로 전송된 인증번호를 입력해주세요.');
+      popupElement.find(".auth_email_row").hide();
+      popupElement.find(".auth_email").val("");
+      popupElement.find(".desc_text").text("인증번호 전송을 눌러 발신번호로 인증번호를 받아주세요.");
+      popupElement.find(".auth_notice").html(
+              "인증한 발신번호는 로그인 세션이 유지되는 동안 재인증 없이 발송할 수 있습니다." +
+              "<br>(발신번호 변경 시 변경된 발신번호 추가 인증 필요)"
+      );
+    }
+
+    openTooltip("auth_number_popup");
+  }
+
+  function sendAuthNumber(buttonElement) {
+    if (buttonElement.hasClass("disabled") || authState.sending) {
+      return;
+    }
+
+    const popupElement = buttonElement.closest(".auth_number_popup");
+    const descElement = popupElement.find(".desc_text");
+    authState.sending = true;
+    buttonElement.addClass("disabled");
+
+    senderAuthAjax("/web/mjon/msgdata/sender-auth/code.do", {callFrom: authState.phone}, function (data, message) {
+      authState.type = data.channel;
+      authState.email = data.maskedDestination || "";
+      if (data.channel === "email") popupElement.find(".auth_email").val(authState.email);
+      alert(message || "인증번호가 발송되었습니다.");
+
+      startLimitTimer({
+        key: "auth_resend",
+        seconds: data.resendAfter || 30,
+        onTick: function (remaining) {
+          if (remaining > 0) buttonElement.text("재전송 (" + remaining + ")");
+        },
+        onComplete: function () {
+          buttonElement.removeClass("disabled").text("재전송");
+        }
+      });
+
+      startLimitTimer({
+        key: "auth_expire",
+        seconds: data.expiresIn || 180,
+        onTick: function (remaining) {
+          descElement.html('<span class="c_e40000 fwBold">' + formatTime(remaining) + "</span> " + getAuthGuideText());
+        },
+        onComplete: function () {
+          descElement.html('<span class="c_e40000 fwBold">시간 초과</span> 인증번호를 다시 요청해 주세요.');
+        }
+      });
+    }, function () {
+      authState.sending = false;
+      if (!limitTimers.auth_resend) buttonElement.removeClass("disabled");
+    });
+  }
+
+  function verifyAuthNumber() {
+    const popupElement = $(".auth_number_popup");
+    const authNumber = $.trim(popupElement.find(".auth_number_input").val());
+
+    if (!authNumber) {
+      alert("인증번호를 입력해주세요.");
+      popupElement.find(".auth_number_input").focus();
+      return;
+    }
+
+    if (!/^\d{6}$/.test(authNumber)) {
+      alert("인증번호 숫자 6자리를 입력해주세요.");
+      popupElement.find(".auth_number_input").focus();
+      return;
+    }
+
+    if (!authState.type || !authState.phone) {
+      alert("인증 정보를 확인할 수 없습니다.");
+      return;
+    }
+    const submitButton = popupElement.find(".auth_submit");
+    if (submitButton.hasClass("disabled")) return;
+    submitButton.addClass("disabled");
+
+    senderAuthAjax("/web/mjon/msgdata/sender-auth/verify.do", {
+      callFrom: authState.phone,
+      code: authNumber
+    }, function (data, message) {
+      stopLimitTimer("auth_resend");
+      stopLimitTimer("auth_expire");
+      updateSenderAuthBadge(true);
+      closeAuthPopup(".auth_number_popup");
+      alert(message || "발신번호 인증이 완료되었습니다.");
+
+      if (authState.continuation) {
+        const continuation = authState.continuation;
+        authState.continuation = null;
+        continuation();
+      }
+    }, function () {
+      submitButton.removeClass("disabled");
+    });
+  }
+
+  function getAuthGuideText() {
+    if (authState.type === "email") {
+      return "메일이 도착하지 않으면 스팸함 확인 후 재전송을 눌러 주세요.";
+    }
+
+    return "인증번호가 도착하지 않으면 재전송을 눌러 주세요.";
+  }
+
+  function resetAuthPopup() {
+    const popupElement = $(".auth_number_popup");
+
+    stopLimitTimer("auth_resend");
+    stopLimitTimer("auth_expire");
+
+    authState.type = null;
+    authState.phone = null;
+    authState.email = null;
+    authState.sending = false;
+
+    popupElement.find(".auth_number_input").val("");
+    popupElement.find(".auth_number_send").removeClass("disabled").text("인증번호 전송");
+    popupElement.find(".auth_submit").removeClass("disabled");
+    popupElement.find(".auth_email").val("");
+    popupElement.find(".auth_email_row").hide();
+  }
+
+  function startLimitTimer(options) {
+    const key = options.key;
+    let remaining = options.seconds;
+
+    stopLimitTimer(key);
+
+    if (options.onStart) {
+      options.onStart(remaining);
+    }
+    if (options.onTick) {
+      options.onTick(remaining);
+    }
+
+    limitTimers[key] = setInterval(function () {
+      remaining--;
+      if (remaining <= 0) {
+        stopLimitTimer(key);
+        if (options.onTick) {
+          options.onTick(0);
+        }
+        if (options.onComplete) {
+          options.onComplete();
+        }
+        return;
+      }
+      if (options.onTick) {
+        options.onTick(remaining);
+      }
+    }, 1000);
+  }
+
+  function stopLimitTimer(key) {
+    if (limitTimers[key]) {
+      clearInterval(limitTimers[key]);
+      delete limitTimers[key];
+    }
+  }
+
+  function formatTime(seconds) {
+    const minutes = String(Math.floor(seconds / 60)).padStart(2, "0");
+    const remainSeconds = String(seconds % 60).padStart(2, "0");
+
+    return minutes + ":" + remainSeconds;
+  }
+
+  function openTooltip(name) {
+    $('[data-tooltip="' + name + '"]').trigger("click");
+  }
+
+</script>
+
+<div class="mask"></div>
+<div class="tooltip-wrap">
+  <div class="popup-com auth_number_popup" tabindex="0" data-tooltip-con="auth_number_popup" data-focus="auth_number_popup" data-focus-prev="auth_number_popup-close" style="width:640px;">
+    <div class="popup_heading">
+      <p>발신번호 인증</p>
+      <button type="button" class="tooltip-close" data-focus="auth_number_popup-close"><img src="/publish/images/content/layerPopup_close.png" alt="팝업 닫기"></button>
+    </div>
+
+    <div class="layer_in">
+      <p class="title_text auth_title"></p>
+
+      <table class="layer_tType1">
+        <colgroup>
+          <col style="width:155px;">
+          <col style="width:auto;">
+        </colgroup>
+
+        <tbody>
+        <tr>
+          <th>인증대상 발신번호</th>
+          <td><input type="text" class="readonly w100 auth_phone" readonly></td>
+        </tr>
+
+        <tr class="auth_email_row" style="display:none;">
+          <th>인증번호 수신 이메일</th>
+          <td><input type="text" class="readonly w100 auth_email" readonly></td>
+        </tr>
+
+        <tr>
+          <th>인증번호 입력</th>
+          <td>
+            <div class="form_wrap column">
+              <div class="form_row">
+                <input type="text" class="auth_number_input" placeholder="인증번호 입력" maxlength="6" inputmode="numeric"><button type="button" class="btnType btnType6 auth_number_send">인증번호 전송</button>
+              </div>
+
+              <span class="desc_text"></span>
+            </div>
+          </td>
+        </tr>
+        </tbody>
+      </table>
+
+      <p class="gray_box fill auth_notice"></p>
+
+      <div class="btn_wrap popup">
+        <button type="button" class="btnType fill_blue auth_submit">인증</button>
+        <button type="button" class="btnType fill_gray tooltip-close">취소</button>
+      </div>
+    </div>
+  </div>
+</div>
+
+<div class="tooltip-wrap">
+  <div class="popup-com required_auth_popup" tabindex="0" data-tooltip-con="required_auth_popup" data-focus="required_auth_popup" data-focus-prev="required_auth_popup-close" style="width:540px;">
+    <div class="popup_heading">
+      <p>발신번호 인증 필요</p>
+      <button type="button" class="tooltip-close" data-focus="required_auth_popup-close"><img src="/publish/images/content/layerPopup_close.png" alt="팝업 닫기"></button>
+    </div>
+
+    <div class="layer_in">
+      <p class="title_text">
+        선택하신 발신번호는 <span class="c_002c9a fwBold">아직 인증되지 않았습니다.</span>
+        <br>문자를 발송하려면 먼저 발신번호 인증이 필요합니다.
+        <br><span class="desc_text">[인증하기]를 누르면 인증 절차가 진행되고, 완료 후 발송하실 수 있습니다.</span>
+      </p>
+
+      <div class="btn_wrap popup">
+        <button type="button" class="btnType fill_blue required_auth_btn">인증하기</button>
+        <button type="button" class="btnType fill_gray tooltip-close">취소</button>
+      </div>
+    </div>
+  </div>
+</div>
+
+<button type="button" class="auth_number_popup_open" data-tooltip="auth_number_popup" style="display:none;" aria-hidden="true"></button>
+<button type="button" class="required_auth_popup_open" data-tooltip="required_auth_popup" style="display:none;" aria-hidden="true"></button>
src/main/webapp/WEB-INF/jsp/web/pop/testSmsSendPop.jsp
--- src/main/webapp/WEB-INF/jsp/web/pop/testSmsSendPop.jsp
+++ src/main/webapp/WEB-INF/jsp/web/pop/testSmsSendPop.jsp
@@ -217,21 +217,26 @@
 					var resultBlockCnt = Number(data.object.resultBlockSts);
 					
 					
-					//차단한 번호 제외하고 발송건수가 1 미만인 경우는 발송이 안된 것
-					if(resultCnt < 1) {
-						alert("테스트 문자가 발송되지 않았습니다. \n"+ resultBlockCnt + "건의 번호가 수신거부 상태입니다.");
-					} else if (resultCnt >= 1) {
-						alert("테스트 문자가 발송되었습니다.");
-					}
-					
 					//내 충전금액 변경해 주기 - 헤더, 충전금액 두군데 수정
 					var afterCash = opener.numberWithCommas(data.object.afterCash);
-					
+
 					$("#hdUserMoney",opener.document).text(afterCash); // 헤더 보유잔액 부분
 					$("#spUserMoney",opener.document).text(afterCash); // 문자발송 내 충전금
 					$("#userMoney",opener.document).text(afterCash);  // 문자발송 내 충전금
 					$("#userMoney",opener.document).attr('placeholder', afterCash);  // 문자발송 내 충전금
-					
+
+					//차단한 번호 제외하고 발송건수가 1 미만인 경우는 발송이 안된 것
+					if(resultCnt < 1) {
+						alert("테스트 문자가 발송되지 않았습니다. \n"+ resultBlockCnt + "건의 번호가 수신거부 상태입니다.");
+					} else if (resultCnt >= 1) {
+						//업무시간 중 스팸 키워드가 감지된 경우, 매칭된 스팸문구를 안내
+						if(data.object.businessHourSpam == true){
+							alert("테스트 문자가 접수 되었습니다.\n\n* 문자 내용에 금칙어 또는 링크가 탐지되어 발송이 보류되었습니다.\n검토를 거친 이후, 정상적인 문자는 그대로 발송됩니다.");
+						}else{
+							alert("테스트 문자가 발송되었습니다.");
+						}
+					}
+
 					window.close();
 				}else{
 					alert(data.message);
 
src/main/webapp/WEB-INF/jsp/web/user/include/secure/certPhone.jspf (added)
+++ src/main/webapp/WEB-INF/jsp/web/user/include/secure/certPhone.jspf
@@ -0,0 +1,403 @@
+<%@ page pageEncoding="UTF-8" %>
+<script>
+    var certPhoneState = {
+        registered: [],
+        candidates: [],
+        maxCount: 3,
+        deleteTarget: ''
+    };
+
+    $(function() {
+        resetAddPopup();
+        resetDeletePopup();
+        loadCertPhoneData();
+
+        $(document).on('click', '#phonePlus', function(e) {
+            if (certPhoneState.registered.length >= certPhoneState.maxCount) {
+                e.preventDefault();
+                e.stopImmediatePropagation();
+                alert('인증 휴대폰번호는 최대 ' + certPhoneState.maxCount + '개까지 등록할 수 있습니다.');
+                return false;
+            }
+            resetCertPhoneAuthFlow();
+            resetAddPopup();
+            $('#phonePlusPopupOpen').trigger('click');
+        });
+
+        $(document).on('click', '#certPhoneAddExistingSend', function() {
+            sendCertPhoneCode('ADD_EXISTING', $('#certPhoneAddExistingSelect').val(), $(this),
+                $('#certPhoneAddExistingTimer'), $('#certPhoneAddExistingDesc'));
+        });
+
+        $(document).on('click', '#certPhoneAddNext', function() {
+            verifyCertPhoneCode('ADD_EXISTING', $('#certPhoneAddExistingSelect').val(),
+                $('#certPhoneAddExistingCode').val(), function() {
+                    $('.auth_add_number_popup .stage01').hide();
+                    $('.auth_add_number_popup .stage').eq(0).removeClass('active');
+                    $('.auth_add_number_popup .stage').eq(1).addClass('active');
+                    $('.auth_add_number_popup .stage02').show();
+                });
+        });
+
+        $(document).on('click', '#certPhoneAddCandidateSend', function() {
+            var phoneNo = normalizeCertPhone($('#certPhoneAddCandidateSelect').val());
+            if (isRegisteredCertPhone(phoneNo)) {
+                alert('이미 등록된 인증 휴대폰번호입니다.');
+                return;
+            }
+            sendCertPhoneCode('ADD_NEW', phoneNo, $(this),
+                $('#certPhoneAddCandidateTimer'), $('#certPhoneAddCandidateDesc'));
+        });
+
+        $(document).on('click', '#certPhoneAddRegister', function() {
+            var phoneNo = normalizeCertPhone($('#certPhoneAddCandidateSelect').val());
+            if (isRegisteredCertPhone(phoneNo)) {
+                alert('이미 등록된 인증 휴대폰번호입니다.');
+                return;
+            }
+            verifyCertPhoneCode('ADD_NEW', phoneNo, $('#certPhoneAddCandidateCode').val(), function() {
+                $.ajax({
+                    type: 'POST',
+                    url: '/cert/phone/insertCertPhone.do',
+                    dataType: 'json',
+                    data: {
+                        mbtlnum: phoneNo,
+                        certAlias: $.trim($('#certPhoneAddAlias').val())
+                    },
+                    success: function(res) {
+                        if (!isCertPhoneSuccess(res)) {
+                            alert(getCertPhoneMessage(res, '휴대폰번호 등록에 실패했습니다.'));
+                            return;
+                        }
+                        alert('인증 휴대폰번호를 등록했습니다.');
+                        closeCertPhonePopup($('.auth_add_number_popup'));
+                        refreshCertPhoneSections();
+                    },
+                    error: function() {
+                        alert('휴대폰번호 등록 중 오류가 발생했습니다.');
+                    }
+                });
+            });
+        });
+
+        $(document).on('click', '.cert-phone-edit', function() {
+            var $button = $(this);
+            var $row = $button.closest('tr');
+            var $aliasCell = $row.find('.alias');
+
+            if (!$row.find('.cert-phone-alias-input').length) {
+                var currentAlias = $aliasCell.find('.alias_text').text();
+                var $input = $('<input>', {
+                    type: 'text',
+                    maxlength: 20,
+                    'class': 'input_text cert-phone-alias-input',
+                    value: currentAlias
+                });
+                $aliasCell.empty().append($input);
+                $button.text('저장');
+                $input.focus();
+                return;
+            }
+
+            $.ajax({
+                type: 'POST',
+                url: '/cert/phone/updateCertPhone.do',
+                dataType: 'json',
+                data: {
+                    mbtlnum: $row.data('phone'),
+                    certAlias: $.trim($row.find('.cert-phone-alias-input').val())
+                },
+                success: function(res) {
+                    if (!isCertPhoneSuccess(res)) {
+                        alert(getCertPhoneMessage(res, '별칭 수정에 실패했습니다.'));
+                        return;
+                    }
+                    loadCertPhoneData();
+                },
+                error: function() {
+                    alert('별칭 수정 중 오류가 발생했습니다.');
+                }
+            });
+        });
+
+        $(document).on('click', '.cert-phone-delete', function() {
+            certPhoneState.deleteTarget = $(this).closest('tr').data('phone');
+            resetCertPhoneAuthFlow();
+            resetDeletePopup();
+        });
+
+        $(document).on('click', '#certPhoneDeleteSend', function() {
+            sendCertPhoneCode('DELETE_EXISTING', $('#certPhoneDeleteExistingSelect').val(), $(this),
+                $('#certPhoneDeleteTimer'), $('#certPhoneDeleteDesc'));
+        });
+
+        $(document).on('click', '#certPhoneDeleteConfirm', function() {
+            verifyCertPhoneCode('DELETE_EXISTING', $('#certPhoneDeleteExistingSelect').val(),
+                $('#certPhoneDeleteCode').val(), function() {
+                    $.ajax({
+                        type: 'POST',
+                        url: '/cert/phone/deleteCertPhone.do',
+                        dataType: 'json',
+                        data: { mbtlnum: certPhoneState.deleteTarget },
+                        success: function(res) {
+                            if (!isCertPhoneSuccess(res)) {
+                                alert(getCertPhoneMessage(res, '휴대폰번호 삭제에 실패했습니다.'));
+                                return;
+                            }
+                            alert('인증 휴대폰번호를 삭제했습니다.');
+                            closeCertPhonePopup($('.auth_del_number_popup'));
+                            refreshCertPhoneSections();
+                        },
+                        error: function() {
+                            alert('휴대폰번호 삭제 중 오류가 발생했습니다.');
+                        }
+                    });
+                });
+        });
+
+        $(document).on('click', '.auth_manage_popup .tooltip-close', function() {
+            stopCertPhoneTimers($(this).closest('.auth_manage_popup'));
+            resetCertPhoneAuthFlow();
+        });
+    });
+
+    function loadCertPhoneData() {
+        $.ajax({
+            url: '/web/user/selectAuthPhoneNumber.do',
+            dataType: 'json',
+            success: function(res) {
+                if (!res || !res.success || !res.data) {
+                    drawCertPhoneList([]);
+                    return;
+                }
+                certPhoneState.registered = res.data.registered || [];
+                certPhoneState.candidates = res.data.candidates || [];
+                certPhoneState.maxCount = res.data.maxCount || 3;
+                $('#certPhoneMaxCount').text(certPhoneState.maxCount);
+                drawCertPhoneList(certPhoneState.registered);
+                fillCertPhoneSelects();
+            },
+            error: function() {
+                drawCertPhoneList([]);
+            }
+        });
+    }
+
+    function drawCertPhoneList(list) {
+        var $tbody = $('#phoneTable tbody').empty();
+        if (!list || !list.length) {
+            $tbody.append($('<tr>').append($('<td>', { colspan: 5, text: '등록된 인증 휴대폰번호가 없습니다.' })));
+            return;
+        }
+
+        $.each(list, function(index, item) {
+            var isBasic = item.phoneType === 'BASIC';
+            var phoneNo = normalizeCertPhone(item.mbtlnum);
+            var $row = $('<tr>').attr('data-phone', phoneNo).data('phone', phoneNo);
+            $row.append($('<td>', { text: formatCertPhone(phoneNo) }));
+            $row.append($('<td>', { text: isBasic ? '기본' : '추가' }));
+            $row.append($('<td>', { 'class': 'alias' }).append(
+                $('<span>', { 'class': 'alias_text', text: isBasic ? '대표번호' : (item.certAlias || '') })
+            ));
+            $row.append($('<td>', { text: isBasic ? '-' : (item.frstRegistPnttm || '-') }));
+
+            var $manage = $('<td>');
+            if (isBasic) {
+                $manage.append($('<b>', { 'class': 'c_666 fwMd', text: '가입 휴대폰 번호' }));
+            } else {
+                var $buttons = $('<div>', { 'class': 'btn_area center' });
+                $buttons.append($('<button>', {
+                    type: 'button',
+                    'class': 'btn btn_blue border btn_28 cert-phone-edit',
+                    text: '수정'
+                }));
+                $buttons.append($('<button>', {
+                    type: 'button',
+                    'class': 'btn btn_lightgray fill btn_28 cert-phone-delete',
+                    'data-tooltip': 'auth_del_number_popup',
+                    text: '삭제'
+                }));
+                $manage.append($buttons);
+            }
+            $row.append($manage);
+            $tbody.append($row);
+        });
+    }
+
+    function fillCertPhoneSelects() {
+        fillCertPhoneSelect($('#certPhoneAddExistingSelect'), certPhoneState.registered, true);
+        fillCertPhoneSelect($('#certPhoneDeleteExistingSelect'), certPhoneState.registered, true);
+        fillCertPhoneSelect($('#certPhoneAddCandidateSelect'), certPhoneState.candidates, false);
+        $('#certPhoneAddCandidateSend, #certPhoneAddRegister').prop('disabled', certPhoneState.candidates.length === 0);
+    }
+
+    function fillCertPhoneSelect($select, list, showType) {
+        $select.empty();
+        if (!list || !list.length) {
+            $select.append($('<option>', { value: '', text: '선택 가능한 번호가 없습니다.' }));
+            $select.prop('disabled', true);
+            return;
+        }
+        $.each(list, function(index, item) {
+            var phoneNo = normalizeCertPhone(item.mbtlnum);
+            var type = item.phoneType === 'BASIC' ? '기본' : '추가';
+            $select.append($('<option>', {
+                value: phoneNo,
+                text: formatCertPhone(phoneNo) + (showType ? ' (' + type + ')' : '')
+            }));
+        });
+        $select.prop('disabled', false);
+    }
+
+    function sendCertPhoneCode(purpose, phoneNo, $button, $timer, $desc) {
+        if (!phoneNo || $button.prop('disabled')) {
+            return;
+        }
+        $.ajax({
+            type: 'POST',
+            url: '/cert/phone/sendSysMsgDataAjax.do',
+            dataType: 'json',
+            data: { mbtlnum: phoneNo, purpose: purpose },
+            success: function(res) {
+                if (!isCertPhoneSuccess(res)) {
+                    alert(getCertPhoneMessage(res, '인증번호 발송에 실패했습니다.'));
+                    return;
+                }
+                var remaining = res.object && res.object.remainingSends != null ? res.object.remainingSends : 0;
+                $desc.html('인증번호를 발송했습니다. 3분 이내에 입력해주세요. (남은 재발송 <b class="fwBold">' + remaining + '</b>회)');
+                startCertPhoneTimer($button, $timer, $desc, remaining);
+            },
+            error: function() {
+                alert('인증번호 발송 중 오류가 발생했습니다.');
+            }
+        });
+    }
+
+    function verifyCertPhoneCode(purpose, phoneNo, checkNo, onSuccess) {
+        checkNo = $.trim(checkNo);
+        if (!phoneNo) {
+            alert('휴대폰번호를 선택해 주세요.');
+            return;
+        }
+        if (!/^\d{6}$/.test(checkNo)) {
+            alert('인증번호 6자리를 입력해 주세요.');
+            return;
+        }
+        $.ajax({
+            type: 'POST',
+            url: '/cert/phone/selectSysMsgLogCheck.do',
+            dataType: 'json',
+            data: { mbtlnum: phoneNo, checkNo: checkNo, purpose: purpose },
+            success: function(res) {
+                if (!isCertPhoneSuccess(res)) {
+                    alert(getCertPhoneMessage(res, '인증번호가 일치하지 않습니다.'));
+                    return;
+                }
+                onSuccess();
+            },
+            error: function() {
+                alert('인증번호 확인 중 오류가 발생했습니다.');
+            }
+        });
+    }
+
+    function startCertPhoneTimer($button, $timer, $desc, remaining) {
+        clearInterval($timer.data('timer'));
+        var time = 180;
+        $timer.show().text('03:00');
+        $button.prop('disabled', true).addClass('disabled');
+
+        var timerId = setInterval(function() {
+            time--;
+            var min = Math.floor(time / 60);
+            var sec = time % 60;
+            $timer.text((min < 10 ? '0' : '') + min + ':' + (sec < 10 ? '0' : '') + sec);
+            if (time <= 0) {
+                clearInterval(timerId);
+                $timer.removeData('timer').hide();
+                $desc.html('입력 시간이 지났습니다. 새로운 인증번호를 받아주세요. (남은 재발송 <b class="fwBold">' + remaining + '</b>회)');
+            }
+        }, 1000);
+
+        $timer.data('timer', timerId);
+        setTimeout(function() {
+            if (remaining > 0) {
+                $button.prop('disabled', false).removeClass('disabled').text('재전송');
+            }
+        }, 30000);
+    }
+
+    function resetAddPopup() {
+        stopCertPhoneTimers($('.auth_add_number_popup'));
+        $('.auth_add_number_popup .stage').removeClass('active').eq(0).addClass('active');
+        $('.auth_add_number_popup .stage01').show();
+        $('.auth_add_number_popup .stage02').hide();
+        $('#certPhoneAddExistingCode, #certPhoneAddCandidateCode, #certPhoneAddAlias').val('');
+        $('#certPhoneAddExistingSend, #certPhoneAddCandidateSend').prop('disabled', false).removeClass('disabled').text('인증요청');
+        fillCertPhoneSelects();
+    }
+
+    function resetDeletePopup() {
+        stopCertPhoneTimers($('.auth_del_number_popup'));
+        $('#certPhoneDeleteTargetText').text(formatCertPhone(certPhoneState.deleteTarget) + ' ');
+        $('#certPhoneDeleteCode').val('');
+        $('#certPhoneDeleteSend').prop('disabled', false).removeClass('disabled').text('인증요청');
+        fillCertPhoneSelect($('#certPhoneDeleteExistingSelect'), certPhoneState.registered, true);
+    }
+
+    function stopCertPhoneTimers($scope) {
+        $scope.find('.timer').each(function() {
+            clearInterval($(this).data('timer'));
+            $(this).removeData('timer').hide();
+        });
+    }
+
+    function resetCertPhoneAuthFlow() {
+        $.ajax({ type: 'POST', url: '/cert/phone/resetAuthFlow.do' });
+    }
+
+    function refreshCertPhoneSections() {
+        loadCertPhoneData();
+        if (typeof selectSecondAuthMethodLog === 'function') {
+            selectSecondAuthMethodLog(1);
+        }
+    }
+
+    function closeCertPhonePopup($popup) {
+        $popup.find('.tooltip-close').first().trigger('click');
+    }
+
+    function isCertPhoneSuccess(res) {
+        return res && res.status === 'OK';
+    }
+
+    function getCertPhoneMessage(res, fallback) {
+        if (!res) return fallback;
+        if (res.message) return res.message;
+        if (res.object && res.object.msg) return res.object.msg;
+        return fallback;
+    }
+
+    function normalizeCertPhone(phoneNo) {
+        return phoneNo == null ? '' : String(phoneNo).replace(/[^0-9]/g, '');
+    }
+
+    function isRegisteredCertPhone(phoneNo) {
+        var normalizedPhoneNo = normalizeCertPhone(phoneNo);
+        if (!normalizedPhoneNo) {
+            return false;
+        }
+        return $.grep(certPhoneState.registered, function(item) {
+            return normalizeCertPhone(item.mbtlnum) === normalizedPhoneNo;
+        }).length > 0;
+    }
+
+    function formatCertPhone(phoneNo) {
+        var value = normalizeCertPhone(phoneNo);
+        if (value.length === 11) return value.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3');
+        if (value.length === 10) return value.replace(/(\d{3})(\d{3})(\d{4})/, '$1-$2-$3');
+        return value || '-';
+    }
+
+
+</script>
 
src/main/webapp/WEB-INF/jsp/web/user/include/secure/secondAuthLog.jspf (added)
+++ src/main/webapp/WEB-INF/jsp/web/user/include/secure/secondAuthLog.jspf
@@ -0,0 +1,102 @@
+<%@ page pageEncoding="UTF-8" %>
+<script>
+    $(function() {
+        selectSecondAuthLog(1);
+    });
+
+    function selectSecondAuthLog(pageIndex) {
+        $.ajax({
+            url: '/web/log/selectSecondAuthLog.do',
+            data: {
+                pageIndex: pageIndex
+            },
+            success: function(res) {
+                if (!res || !res.success || !res.data) {
+                    drawSecondAuthLogList([]);
+                    $('#secondAuthPagination').empty();
+                    return;
+                }
+                drawSecondAuthLogList(res.data.list);
+                drawPagination(res.data, $('#secondAuthPagination'), selectSecondAuthLog);
+            },
+            error: function() {
+                drawSecondAuthLogList([]);
+                $('#secondAuthPagination').empty();
+            }
+        });
+    }
+
+    function drawSecondAuthLogList(resultList) {
+        var html = '';
+
+        if (!resultList || resultList.length === 0) {
+            html = '<tr><td colspan="5">조회된 로그인 내역이 없습니다.</td></tr>';
+        } else {
+            $.each(resultList, (index, result) => html += drawSecondAuthLogListItem(index, result));
+        }
+
+        $('#secondAuthLogBody').html(html);
+    }
+    function drawSecondAuthLogListItem(index, result) {
+        let loginResult = result.loginResult === '성공'
+            ? escapeHtml(result.loginResult)
+            : '<b class="fwMd c_e40000">' + escapeHtml(result.loginResult + (result.reason ? ' (' + result.reason + ')' : '')) + '</b>';
+
+        var html = '';
+        html += '<tr>';
+        html += '<td>' + escapeHtml(result.frstRegistPnttm) + '</td>';
+        html += '<td>' + escapeHtml(result.accessIp) + '</td>';
+        html += '<td>' + getLoginEnvironment(result.userAgent) + '</td>';
+        html += '<td>' + escapeHtml(result.receive) + '</td>';
+        html += '<td>' + loginResult + '</td>';
+        html += '</tr>';
+
+        return html;
+    }
+
+    function getLoginEnvironment(userAgent) {
+        if (!userAgent) {
+            return '-';
+        }
+
+        return /Android|iPhone|iPad|iPod|Mobile/i.test(userAgent) ? '모바일' : 'PC';
+    }
+
+    function escapeHtml(value) {
+        if (value === null || value === undefined) {
+            return '-';
+        }
+
+        return $('<div>').text(value).html();
+    }
+
+</script>
+
+<div class="title_area">
+    <p class="dashboard_title">로그인 내역 <span class="small_text">로그인 내역은 최대 3개월까지만 보관됩니다.</span></p>
+</div>
+
+<div class="table_wrap">
+    <table>
+        <colgroup>
+            <col style="width:auto;">
+            <col style="width:auto;">
+            <col style="width:10%;">
+            <col style="width:18%;">
+            <col style="width:18%;">
+        </colgroup>
+        <thead>
+        <tr>
+            <th scope="col">로그인 일시</th>
+            <th scope="col">로그인 IP</th>
+            <th scope="col">환경</th>
+            <th scope="col">휴대폰번호</th>
+            <th scope="col">인증결과</th>
+        </tr>
+        </thead>
+        <tbody id="secondAuthLogBody">
+        </tbody>
+    </table>
+</div>
+
+<ul id="secondAuthPagination" class="pagination"></ul>
 
src/main/webapp/WEB-INF/jsp/web/user/include/secure/secondAuthMethodLog.jspf (added)
+++ src/main/webapp/WEB-INF/jsp/web/user/include/secure/secondAuthMethodLog.jspf
@@ -0,0 +1,106 @@
+<%@ page pageEncoding="UTF-8" %>
+<script>
+    $(function() {
+        selectSecondAuthMethodLog(1);
+    });
+
+    function selectSecondAuthMethodLog(pageIndex) {
+        $.ajax({
+            url: '/web/user/selectAuthMethodChangeHistory.do',
+            data: {
+                pageIndex: pageIndex
+            },
+            success: function(res) {
+                if (!res || !res.success || !res.data) {
+                    drawSecondAuthMethodLogList([]);
+                    return;
+                }
+
+                drawSecondAuthMethodLogList(res.data.list);
+                drawPagination(res.data, $('#secondAuthMethodPagination'), selectSecondAuthMethodLog);
+            },
+            error: function() {
+                drawSecondAuthMethodLogList([]);
+            }
+        });
+    }
+
+    function drawSecondAuthMethodLogList(resultList) {
+        var html = '';
+
+        if (!resultList || resultList.length === 0) {
+            html = '<tr><td colspan="5">인증수단 변경 이력이 없습니다.</td></tr>';
+        } else {
+            $.each(resultList, function(index, result) {
+                html += '<tr>';
+                html += '<td>' + escapeSecondAuthMethodHtml(result.frstRegistPnttm) + '</td>';
+                html += '<td>' + getSecondAuthMethodActionName(result.actionType) + '</td>';
+                html += '<td>' + escapeSecondAuthMethodHtml(result.phoneNo) + '</td>';
+                html += '<td>' + getSecondAuthMethodVerifyName(result.verifyType) + '</td>';
+                html += '<td>' + escapeSecondAuthMethodHtml(result.accessIp) + '</td>';
+                html += '</tr>';
+            });
+        }
+
+        $('#secondAuthMethodLogBody').html(html);
+    }
+
+    function getSecondAuthMethodActionName(actionType) {
+        var names = {
+            REGISTER: '등록',
+            UPDATE: '변경',
+            DELETE: '삭제'
+        };
+        return escapeSecondAuthMethodHtml(names[actionType] || actionType);
+    }
+
+    function getSecondAuthMethodVerifyName(verifyType) {
+        var names = {
+            SMS: 'SMS 인증',
+            KMC: 'KMC 본인인증',
+            INFO_CHANGE: '회원정보 변경'
+        };
+        return escapeSecondAuthMethodHtml(names[verifyType] || verifyType);
+    }
+
+    function escapeSecondAuthMethodHtml(value) {
+        if (value === null || value === undefined || value === '') {
+            return '-';
+        }
+        return $('<div>').text(value).html();
+    }
+</script>
+
+<div class="title_area">
+    <p class="dashboard_title">인증수단 변경 이력
+        <span class="small_text">등록·변경·삭제 이력은 <b>최대 1년</b>간 보관됩니다.</span>
+    </p>
+</div>
+
+<div class="table_wrap" id="secondAuthMethodLogTable">
+    <table>
+        <colgroup>
+            <col style="width:auto;">
+            <col style="width:10%;">
+            <col style="width:auto;">
+            <col style="width:15%;">
+            <col style="width:15%;">
+        </colgroup>
+        <thead>
+        <tr>
+            <th scope="col">처리일시</th>
+            <th scope="col">처리유형</th>
+            <th scope="col">휴대폰번호</th>
+            <th scope="col">본인확인</th>
+            <th scope="col">처리 IP</th>
+        </tr>
+        </thead>
+        <tbody id="secondAuthMethodLogBody">
+        <tr>
+            <td colspan="5">인증수단 변경 이력을 조회하고 있습니다.</td>
+        </tr>
+        </tbody>
+    </table>
+</div>
+
+<ul id="secondAuthMethodPagination" class="pagination"></ul>
src/main/webapp/WEB-INF/jsp/web/user/mberSecureLogin.jsp
--- src/main/webapp/WEB-INF/jsp/web/user/mberSecureLogin.jsp
+++ src/main/webapp/WEB-INF/jsp/web/user/mberSecureLogin.jsp
@@ -1,982 +1,271 @@
-<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
-<%@ taglib prefix="ui" uri="http://egovframework.gov/ctl/ui"%>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
+<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="ui" uri="http://egovframework.gov/ctl/ui" %>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
 
 
 <script language=javascript>
 
+    function drawPagination(pagination, element, onClick) {
+        var html = '';
 
-//보안 로그인
-$(document).ready(function(){
-	
-	// 허용 IP 등록
-	$(".btn_allow_ip_add").click(function () {
-		
-		var ip = $('#cennectIp').val();
-		
-		if (confirm("현재 접속중인 IP["+ip+"]를 접속 허용 IP로 등록하시겠습니까?")) {
-
-			$.ajax({
-				type: "POST",
-				url: "/cert/ip/insertCennetCertIp.do",
-				data: null,
-				dataType:'json',
-				async: false,
-				success: function (returnData) {
-					
-					if(returnData.status == 'OK'){
-						alert("접속 허용 IP["+ip+"] 등록이 완료되었습니다.");
-						location.reload();  
-					}
-					else
-					{
-						alert("오류가 발생하였습니다.");
-					}
-				},
-				error: function (e) { alert("오류가 발생하였습니다."); console.log("ERROR : ", e); }
-			});
-			
-		} else {}
-	});
-
-	
-	// secuLoginFlag 값에 따라 해당하는 탭에 'on' 클래스 추가
-    if ('${mberManageVO.secuLoginFlag}' === 'Y') {
-        $('.security_tab a').filter(function() {
-            return $(this).text() === 'ON';
-        }).addClass('on');
-    	$('.security_tab').addClass('on');
-    } else {
-        $('.security_tab a').filter(function() {
-            return $(this).text() === 'OFF';
-        }).addClass('on');
-    	$('.security_tab').addClass('off');
-    }
-	// on/off 시 confirm 창 노출
-	$('.security_tab').click(function (e) {
-		if ($(this).find("a.on").text().trim() == "ON") {
-			if (!confirm("가입자 휴대폰번호로 본인인증 후 해제가 가능합니다. 해제 시 계정정보 유출에 따른 피해가 발생할 수 있으므로 이를 권장하지 않습니다.")) {
-				$(this).find("a.on").siblings("a").removeClass("on");
-			} else {
-				openKMCISWindow();
-// 				$(this).find("a.on").removeClass("on").siblings("a").addClass("on");
-// 				$(this).removeClass("on").addClass("off");
-			}
-		} else if($(this).find("a.on").text().trim() == "OFF") {
-			if (!confirm("보안로그인 설정 시 미리 등록한 IP로 접속하거나 휴대폰 SMS 추가 인증을 진행하셔야 합니다.")) {
-				$(this).find("a.on").siblings("a").removeClass("on");
-				
-			} else {
-				$(this).find("a.on").removeClass("on").siblings("a").addClass("on");
-				$(this).removeClass("off").addClass("on");
-				callTo();
-			}
-		}
-		
-	});
-	
-	// 인증번호 발송 버튼
-	$('#certReqBtn').on('click', function(){
-		var certReqPhone = $('#certReqPhone').val();
-		
-		if (certReqPhone==''){
-			alert("핸드폰번호를 입력해주세요.");
-			return;
-		}
-
-		// 2. 휴대폰 번호 유효성 검사
-		const phoneRegex = /^(010(\d{3}\d{4}|\d{4}\d{4})|011(\d{3}\d{4}|\d{4}\d{4}))$/;
-		if (!phoneRegex.test(certReqPhone)) {
-			alert("유효한 핸드폰 번호를 입력해주세요.");
-			return;
-		}
-		
-		var params = {"mbtlnum" : certReqPhone }
-		$.ajax({
-			type: "POST",
-			url : "<c:url value='/cert/phone/sendSysMsgDataAjax.do' />", 
-			data: params,
-			dataType:'json',
-			async: false,
-			success: function (returnData) {
-				console.log('returnData : ', returnData);
-				if(returnData.status == 'OK'){
-					alert(returnData.object.msg);
-					$("#certReqPhone").prop("disabled", true);
-				}
-				else if(returnData.status == 'CONFLICT')
-				{
-					alert(returnData.message);
-					return false;
-				}
-				else
-				{
-					alert("오류가 발생하였습니다.");
-				}
-			},
-			error: function (e) { alert("오류가 발생하였습니다."); console.log("ERROR : ", e); }
-		});
-	});
-	
-	// 인증 버튼
-	$('#certConfirmBtn').on('click', function(){
-		var certReqPhone = $('#certReqPhone').val();
-		var certNumber = $('#certNumber').val();
-		
-		if (certNumber==''){
-			alert("인증번호를 입력해주세요.");
-			return;
-		}
-		var params = {"mbtlnum" : certReqPhone, "checkNo" : certNumber }
-		$.ajax({
-			type: "POST",
-			url : "<c:url value='/cert/phone/selectSysMsgLogCheck.do' />", 
-			data: params,
-			dataType:'json',
-			async: false,
-			success: function (returnData) {
-				console.log('returnData : ', returnData);
-				if(returnData.status == 'OK'){
-//					alert(returnData.object.msg);
-					regCertPhone(params);
-				}
-				else if(returnData.status == 'BAD_REQUEST'){
-					alert(returnData.message);
-				}
-				else
-				{
-					alert("오류가 발생하였습니다.");
-				}
-			},
-			error: function (e) { alert("오류가 발생하였습니다."); console.log("ERROR : ", e); }
-		});
-	});
-	
-	
-
-	$('#showLoginBtn').on('click', function() {
-		if ($('#securityLogin').is(':visible')) {
-			$('#securityLogin').hide();
-		} else {
-			$('#securityLogin').show();
-		}
-	});
-	
-	
-
-	// IP 추가 버튼 클릭 시 tr 추가
-	$(".btn_ip_plus").click(function () {
-
-		var chk = false;
-
-			$.ajax({
-				type: "POST",
-				url: "/cert/ip/selectMberCertIpList.do",
-				data: null,
-				dataType:'json',
-				async: false,
-				success: function (returnData) {
-					console.log('returnData : ', returnData);
-					if (returnData.status === "OK") {
-						const objects = returnData.object; // 배열 데이터
-
-						// 배열의 개수 구하기
-						const count = objects.length;
-						console.log('count :', count);
-						if(count > 9){
-							alert("접속 IP 등록은 10개까지 가능합니다.");
-							chk = true;
-							return false;
-						}
-					} else {
-						alert("데이터를 불러오는 데 실패했습니다.");
-					}
-				},
-				error: function (e) { alert("오류가 발생하였습니다."); console.log("ERROR : ", e); }
-			});
-		
-		if(chk){
-			return false;
-		}
-		
-		
-		// 추가할 HTML 구조
-		const ipTr = `
-			<tr class="ip-new">
-				<td><input type="text" class="input_text"
-					oninput="this.value = this.value.replace(/[^0-9.]/g, '');" maxlength="15"/></td>
-				<td><input type="text" maxLength="25" class="input_text"></td>
-				<td>-</td>
-				<td>
-					<button type="button" class="btnType btnType5" id="ipRegBtn">등록</button>
-				</td>
-			</tr>
-		`;
-
-		// 이미 추가된 `.ip-row`가 있는지 확인
-		if ($("#ipTable tbody .ip-new").length === 0) {
-			$("#ipTable tbody").prepend(ipTr); // 중복되지 않으면 추가
-		} else {
-			alert("이미 추가된 항목이 있습니다.");
-		}
-	});
-	
-	
-	// IP 등록버튼
-	$(document).on('click', '#ipRegBtn', function () {
-		// 현재 클릭된 버튼의 부모 tr 요소
-		const $currentRow = $(this).closest('tr');
-
-		// 입력값 가져오기
-		const ipValue = $currentRow.find('td:eq(0) input').val(); // 첫 번째 열 (IP)
-		const memoValue = $currentRow.find('td:eq(1) input').val(); // 두 번째 열 (메모)
-
-		// 예외 처리
-		if (!ipValue) {
-			alert("등록할 IP를 입력해주세요.");
-			$currentRow.find('td:eq(0) input').focus();
-			return;
-		}
-		
-		
-		const ipRegex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
-		
-		if (!ipRegex.test(ipValue)) {
-			alert("유효하지 않은 IP 주소입니다.");
-			return false;
-		}
-
-		var params = {
-			"certIp" : ipValue
-			, "certMemo" : memoValue
-		}
-		
-		if(!confirm("IP를 등록 하시겠습니까?")){
-			return false;
-		}
-		$.ajax({
-			type: "POST",
-			url: "/cert/ip/insertCertIp.do",
-			data: params,
-			dataType:'json',
-			async: false,
-			success: function (returnData) {
-				if(returnData.status == 'OK'){
-					findAllCertIp();
-				}else if(returnData.status == 'CONFLICT'){
-					alert(returnData.message);
-					return false;
-				}
-				else
-				{
-					alert("오류가 발생하였습니다.");
-				}
-			},
-			error: function (e) { alert("오류가 발생하였습니다."); console.log("ERROR : ", e); }
-		});
-	});
-
-
-	$(document).on('click', '.phoneModiBtn', function () {
-		// 현재 클릭된 버튼의 tr 요소를 가져옴
-		const $tr = $(this).closest('tr');
-
-		// 별칭과 메모 td 요소를 가져옴
-		const $aliasTd = $tr.find('td').eq(1); // 두 번째 컬럼(별칭)
-		const $memoTd = $tr.find('td').eq(2); // 세 번째 컬럼(메모)
-
-		// 기존 텍스트 값 저장
-		const aliasValue = $aliasTd.text().trim(); // 별칭 값
-		const memoValue = $memoTd.text().trim();  // 메모 값
-		
-		console.log('aliasValue : ', aliasValue);
-		console.log('memoValue : ', memoValue);
-
-		// td 내용을 input 필드로 변경
-		$aliasTd.html('<input type="text" class="input_text" maxlength="12" value="' + aliasValue + '">');
-		$memoTd.html('<input type="text" class="input_text" maxlength="25" value="' + memoValue + '">');
-
-		// aliasTd 내의 input 필드에 포커스 설정
-		const $aliasInput = $aliasTd.find('.input_text');
-		$aliasInput.focus().val($aliasInput.val()); // 포커스 후 커서를 맨 끝으로 이동
-		
-		$(this).removeClass("phoneModiBtn").attr("onclick", "fn_phoneSave(this);").text("저장");
-	});
-
-	$(document).on('click', '#phonePlus', function () {
-
-		$.ajax({
-			type: "POST",
-			url: "/cert/phone/selectMberCertPhoneList.do",
-			data: null,
-			dataType:'json',
-			async: false,
-			success: function (returnData) {
-				console.log('returnData : ', returnData);
-				if (returnData.status === "OK") {
-					const objects = returnData.object; // 배열 데이터
-
-					// 배열의 개수 구하기
-					const count = objects.length;
-					console.log('count :', count);
-					if(count < 5){
-						$('#phonePlusPopupOpen').click()
-					}else{
-						alert("인증 휴대폰 번호는 5개까지 등록 가능합니다.");
-						return false;
-					}
-				} else {
-					alert("데이터를 불러오는 데 실패했습니다.");
-				}
-			},
-			error: function (e) { alert("오류가 발생하였습니다."); console.log("ERROR : ", e); }
-		});
-	});
-
-	
-
-	findAllCertIp();
-	findAllCertPhone();
-	
-
-	console.log('${mberCertLoginLogVO.secuLoginFlag}');
-	var secuLoginFlag = '${mberCertLoginLogVO.secuLoginFlag}';
-	if(secuLoginFlag == 'Y'){
-		$('#secuOffBtn').click();
-	}else if(secuLoginFlag == 'N'){
-		$('#secuOnBtn').click();
-		
-	}
-	// 새로고침 시 값을 초기화
-	history.replaceState({}, null, location.pathname);
-	
-});
-
-function fn_phoneSave(obj){
-	// 현재 클릭된 버튼의 tr 요소를 가져옴
-	const $tr = $(obj).closest('tr'); // 'this' 대신 전달된 'obj' 사용
-
-	// 각 td 요소에서 데이터를 추출
-	const mbtlnum = $tr.find('td').eq(0).text().trim(); // 첫 번째 컬럼(번호)
-	const certAlias = $tr.find('td').eq(1).find('input').val().trim(); // 두 번째 컬럼(별칭)
-	const certMemo = $tr.find('td').eq(2).find('input').val().trim(); // 세 번째 컬럼(메모)
-
-	// 콘솔 출력
-	console.log('mbtlnum : ', mbtlnum);
-	console.log('certAlias : ', certAlias);
-	console.log('certMemo : ', certMemo);
-	
-
-	var params = {
-		"mbtlnum" : mbtlnum
-		, "certAlias" : certAlias
-		, "certMemo" : certMemo
-	}
-
-	$.ajax({
-		type: "POST",
-		url: "/cert/phone/updateCertPhone.do",
-		data: params,
-		dataType:'json',
-		async: false,
-		success: function (returnData) {
-			if(returnData.status == 'OK'){
-				findAllCertPhone();
-			}
-			else
-			{
-				alert("오류가 발생하였습니다.");
-			}
-		},
-		error: function (e) { alert("오류가 발생하였습니다."); console.log("ERROR : ", e); }
-	});
-	
-	
-}
-
-
-function regCertPhone(params){
-	
-	$.ajax({
-		type: "POST",
-		url: "/cert/phone/insertCertPhone.do",
-		data: params,
-		dataType:'json',
-		async: false,
-		success: function (returnData) {
-			console.log('returnData : ', returnData);
-			if(returnData.status == 'OK'){
-				$('.tooltip-close').click()
-				alert(returnData.message);
-				findAllCertPhone();
-			}else if(returnData.status == 'CONFLICT'){
-				alert(returnData.message);
-				return false;
-			}
-			else
-			{
-				alert("오류가 발생하였습니다.");
-			}
-		},
-		error: function (e) { alert("오류가 발생하였습니다."); console.log("ERROR : ", e); }
-	});
-	
-	
-}
-
-function findAllCertIp(){
-	
-	$.ajax({
-		type: "POST",
-		url: "/cert/ip/selectMberCertIpList.do",
-		data: null,
-		dataType:'json',
-		async: false,
-		success: function (returnData) {
-			if (returnData.status === "OK") {
-				const objects = returnData.object; // 배열 데이터
-				
-				// tbody의 기존 내용 삭제
-				const $tbody = $("#ipTable tbody");
-				$tbody.empty();
-				if (objects.length === 0) {
-					// 데이터가 없을 때 tr과 td 추가
-					const $emptyRow = $("<tr></tr>").append(
-						$("<td></td>")
-							.attr("colspan", 4)
-							.text("등록된 IP 주소가 없습니다.")
-							.addClass("empty-row") // 스타일을 추가할 경우
-					);
-					$tbody.append($emptyRow);
-				} else {
-					// 배열 데이터를 기반으로 tr 생성 후 tbody에 추가
-					$.each(objects, function(index, obj) {
-						const $tr = $("<tr></tr>"); // tr 요소 생성
-	
-						// 각 td 요소 생성
-						const $certIp = $("<td></td>").text(obj.certIp || "");
-						const $certMemo = $("<td></td>").text(obj.certMemo || "");
-						const $frstRegistPnttm = $("<td></td>").text(obj.frstRegistPnttm || "등록되지 않음");
-						const $deleteBtn = $("<button></button>")
-							.addClass("btnType btn_text btn_lightgray fill btn_28")
-							.text("삭제")
-							.attr("type", "button")
-							.attr("id", "ipDelBtn")
-							.on("click", function() {
-								deleteRow(obj.certIp); // 삭제 버튼 클릭 시 실행
-							});
-	
-						// 관리 버튼을 감쌀 td 생성
-						const $deleteTd = $("<td></td>").append($deleteBtn);
-	
-						// tr에 td 추가
-						$tr.append($certIp, $certMemo, $frstRegistPnttm, $deleteTd);
-	
-						// tbody에 tr 추가
-						$tbody.append($tr);
-					});
-				}
-			} else {
-				alert("데이터를 불러오는 데 실패했습니다.");
-			}
-		},
-		error: function (e) { alert("오류가 발생하였습니다."); console.log("ERROR : ", e); }
-	});
-}
-
-function findAllCertPhone(){
-	
-	$.ajax({
-		type: "POST",
-		url: "/cert/phone/selectMberCertPhoneList.do",
-		data: null,
-		dataType:'json',
-		async: false,
-		success: function (returnData) {
-			console.log('returnData : ', returnData);
-			if (returnData.status === "OK") {
-				const objects = returnData.object; // 배열 데이터
-				
-				// tbody의 기존 내용 삭제
-				const $tbody = $("#phoneTable tbody");
-				$tbody.empty();
-
-				// 배열 데이터를 기반으로 tr 생성 후 tbody에 추가
-				$.each(objects, function(index, obj) {
-					const $tr = $("<tr></tr>"); // tr 요소 생성
-
-					// 각 td 요소 생성
-					const $mbtlnum = $("<td></td>").text(obj.mbtlnum || "");
-					const $certAlias = $("<td></td>").text(obj.certAlias || "-");
-					const $certMemo = $("<td></td>").text(obj.certMemo || "-");
-					const $frstRegistPnttm = $("<td></td>").text(obj.frstRegistPnttm || "등록되지 않음");
-					
-
-					const $buttonTd = $("<td></td>");
-
-					if (obj.frstRegistPnttm !== "-") {
-						const $modiBtn = $("<button></button>")
-							.addClass("btnType btnType5 btn_edit phoneModiBtn")
-							.text("수정")
-							.attr("type", "button")
-							.attr("style", "margin:0 5px 0 0;");
-	
-						
-						const $deleteBtn = $("<button></button>")
-							.addClass("btnType btn_text btn_lightgray fill btn_28")
-							.text("삭제")
-							.attr("type", "button")
-							.attr("id", "phoneDelBtn")
-							.on("click", function() {
-								fn_PDelRow(obj.mbtlnum); // 삭제 버튼 클릭 시 실행
-							});
-						$buttonTd.append($modiBtn, $deleteBtn);
-					}else{
-						// 등록일시가 없으면 빈 td로 유지
-						$buttonTd.text("-");
-					}
-					// 관리 버튼을 감쌀 td 생성
-
-					// tr에 td 추가
-					$tr.append($mbtlnum, $certAlias, $certMemo, $frstRegistPnttm, $buttonTd);
-
-					// tbody에 tr 추가
-					$tbody.append($tr);
-				});
-			} else {
-				alert("데이터를 불러오는 데 실패했습니다.");
-			}
-		},
-		error: function (e) { alert("오류가 발생하였습니다."); console.log("ERROR : ", e); }
-	});
-}
-
-function fn_PDelRow(p_mbtlnum){
-	console.log('p_mbtlnum : ', p_mbtlnum);
-	var params = {
-		"mbtlnum" : p_mbtlnum
-	}
-	if(!confirm("인증 휴대폰번호를 삭제 하시겠습니까?")){
-		return false;
-	}
-	$.ajax({
-		type: "POST",
-		url: "/cert/phone/deleteCertPhone.do",
-		data: params,
-		dataType:'json',
-		async: false,
-		success: function (returnData) {
-			if(returnData.status == 'OK'){
-				findAllCertPhone();
-			}
-			else
-			{
-				alert("오류가 발생하였습니다.");
-			}
-		},
-		error: function (e) { alert("오류가 발생하였습니다."); console.log("ERROR : ", e); }
-	}); 
-	
-}
-//IP 삭제
-function deleteRow(p_ip){
-
-
-	var myIp = $('#cennectIp').val();
-	console.log('p_ip : ', p_ip);
-	
-	
-	var params = {
-		"certIp" : p_ip
-	}
-	if(!confirm("IP를 삭제 하시겠습니까?")){
-		return false;
-	}
-	$.ajax({
-		type: "POST",
-		url: "/cert/ip/deleteCertIp.do",
-		data: params,
-		dataType:'json',
-		async: false,
-		success: function (returnData) {
-			if(returnData.status == 'OK'){
-				if(myIp == p_ip){
-					location.reload();
-					
-				}
-				
-				findAllCertIp();
-			}
-			else
-			{
-					alert("오류가 발생하였습니다.");
-			}
-		},
-		error: function (e) { alert("오류가 발생하였습니다."); console.log("ERROR : ", e); }
-	}); 
-}
-
-
-
-
-
-
-//휴대푠 번호 등록 [시작]--------------------------------------------------------------------------------------------
-window.name = "kmcis_web_sample";
-var KMCIS_window;
-//휴대폰 인증팝업 열기
- function openKMCISWindow(){ 
- //본인인증시도 로그 
- certAccessLog();
-	var UserAgent = navigator.userAgent;
-	/* 모바일 접근 체크*/
-	// 모바일일 경우 (변동사항 있을경우 추가 필요)
-	if (UserAgent.match(/iPhone|iPod|Android|Windows CE|BlackBerry|Symbian|Windows Phone|webOS|Opera Mini|Opera Mobi|POLARIS|IEMobile|lgtelecom|nokia|SonyEricsson/i) != null || UserAgent.match(/LG|SAMSUNG|Samsung/) != null) {
-		document.reqKMCISForm.target = 'KMCISWindow';  // 모바일
-	} else { // 모바일이 아닐 경우
-		KMCIS_window = window.open('', 'KMCISWindow', 'width=425, height=550, resizable=0, scrollbars=no, status=0, titlebar=0, toolbar=0, left=435, top=250' );
-	
-		if(KMCIS_window == null){
-			alert(" ※ 윈도우 XP SP2 또는 인터넷 익스플로러 7 사용자일 경우에는 \n    화면 상단에 있는 팝업 차단 알림줄을 클릭하여 팝업을 허용해 주시기 바랍니다. \n\n※ MSN,야후,구글 팝업 차단 툴바가 설치된 경우 팝업허용을 해주시기 바랍니다.");
-		}
-	
-		document.reqKMCISForm.target = 'KMCISWindow';
-	}
-	
-	document.reqKMCISForm.action = 'https://www.kmcert.com/kmcis/web/kmcisReq.jsp';
-	document.reqKMCISForm.submit();
-}
-
-///web/user/selectSecurityAuthn.do 리다이렉트 URL
-
-//자식창에서 호출
-function callTo() {
-	$.ajax({
-        url: '/web/user/mberSecureLoginAjax.do',
-        type: 'POST',
-        /*contentType: 'application/json',
-         data: JSON.stringify({
-        	secuLoginFlag: '${mberManageVO.secuLoginFlag}'
-        }), */
-        success: function(response) {
-			if(response === 'success'){
-				alert('변경이 완료되었습니다.');
-				location.reload();
-			}else{
-				alert('변경에 실패했습니다. 다시 시도해 주세요.');
-				location.reload();
-			}
-    		
-        },
-        error: function(error) {
-        	alert("error");
+        if (!pagination || pagination.totalPages === 0) {
+            element.empty();
+            return;
         }
-    });
-}
 
-//자식창에서 호출
-function callToTwo(msg, winRef) {
-	if (winRef) {
-        winRef.close();
+        var currentPage = pagination.currentPage;
+        var totalPages = pagination.totalPages;
+        var pageSize = 10;
+        var firstPage = Math.floor((currentPage - 1) / pageSize) * pageSize + 1;
+        var lastPage = Math.min(firstPage + pageSize - 1, totalPages);
+
+        html += '<li class="page_first"><button type="button" data-page="1"' + (currentPage === 1 ? ' disabled' : '') + '><img src="/publish/images/content/page_first.png" alt="첫 페이지"></button></li>';
+        html += '<li class="page_prev"><button type="button" data-page="' + Math.max(1, firstPage - 1) + '"' + (firstPage === 1 ? ' disabled' : '') + '><img src="/publish/images/content/page_prev.png" alt="이전 페이지"></button></li>';
+
+        for (var page = firstPage; page <= lastPage; page++) {
+            if (page === currentPage) {
+                html += '<li class="on"><button type="button" title="현재페이지">' + page + '</button></li>';
+            } else {
+                html += '<li><button type="button" data-page="' + page + '" title="' + page + '페이지">' + page + '</button></li>';
+            }
+        }
+
+        html += '<li class="page_next"><button type="button" data-page="' + Math.min(totalPages, lastPage + 1) + '"' + (lastPage === totalPages ? ' disabled' : '') + '><img src="/publish/images/content/page_next.png" alt="다음 페이지"></button></li>';
+        html += '<li class="page_last"><button type="button" data-page="' + totalPages + '"' + (currentPage === totalPages ? ' disabled' : '') + '><img src="/publish/images/content/page_last.png" alt="마지막 페이지"></button></li>';
+
+        element.html(html);
+
+        element.find('button[data-page]:not(:disabled)').on('click', function () {
+            onClick($(this).data('page'));
+        });
     }
-	alert(msg);
-	localtion.reload();
-}
-
-
-function fn_phonePopClean(){
-	console.log('ddd')
-	$('#certReqPhone').val('');
-	$('#certNumber').val('');
-}
-
-function linkPage(pageNo){
-	var listForm = document.listForm ;
-	listForm.pageIndex.value = pageNo ;
-	listForm.submit();
-}
-
-//휴대푠 번호 등록 [끝]--------------------------------------------------------------------------------------------
 
 </script>
-<form name="reqKMCISForm" method="post" action="#">
-	<input type="hidden" id="tr_url" name="tr_url" value = "${certVO.tr_url}">
-	<input type="hidden" id="tr_add" name="tr_add" value = "${certVO.tr_add}">
-	<input type="hidden" id="tr_cert" name="tr_cert" value = "${certVO.tr_cert}">
-	<input type="hidden" name="tr_ver"      value = "V2">
-</form>
-<form name="listForm" action="<c:url value='/web/user/mberSecureLogin.do'/>" method="post">
-	<input name="pageIndex" type="hidden" value="<c:out value='${mberCertLoginLogVO.pageIndex}'/>"/>
-</form>
-	<div class="mask"></div>
+<div class="mask"></div>
 
-	<!-- 휴대폰인증 팝업 -->
-	<div class="tooltip-wrap">
-		<div class="popup-com certify_layer popup05" tabindex="0" data-tooltip-con="popup05" data-focus="popup05" data-focus-prev="popup05-close" style="width: 500px;">
-			<div class="popup_heading">
-				<p>휴대폰 인증</p>
-				<button type="button" class="tooltip-close" data-focus="popup05-close" onclick="fn_phonePopClean()"><img src="/publish/images/content/layerPopup_close.png" alt="팝업 닫기"></button>
-			</div>
-			<div class="layer_in">
-				<div class="hascont">
-					<table class="layer_tType1">
-						<caption>본인인증(step1) 표</caption>
-						<colgroup>
-							<col style="width: 95px">
-							<col style="width: auto">
-						</colgroup>
-						<tbody>
-							<tr>
-								<th>핸드폰번호</th>
-								<td>
-									<label for="" class="label">핸드폰번호 입력</label>
-									<input type="text" placeholder="‘-’없이 번호만 입력 " id="certReqPhone" onfocus="this.placeholder=''" 
-										onblur="this.placeholder='‘-’없이 번호만 입력 '" class="inputLight" style="width: 200px;"
-										oninput="this.value = this.value.replace(/[^0-9]/g, '');" maxlength="14"/>
-									<button type="button" id="certReqBtn" class="btnType btnType6" style="width:90px">인증요청</button>
-								</td>
-							</tr>
-							<tr>
-								<th>인증번호</th>
-								<td>
-									<label for="" class="label">인증번호 입력</label>
-									<input type="text" placeholder="인증번호 6자리 입력" id="certNumber" maxlength="7" onfocus="this.placeholder=''" onblur="this.placeholder='인증번호 6자리 입력'" class="inputLight" style="width: 200px;">
-									<button type="button" id="certConfirmBtn" class="btnType btnType6" style="width:90px">확인</button>
-								</td>
-							</tr>
-						</tbody>
-					</table>
-					<div class="popup_btn_wrap2">
-						<button type="button" class="tooltip-close" data-focus="popup05-close" data-focus-next="popup05">닫기</button>
-					</div>
-				</div>
-			</div>
-		</div>
-	</div>
-	<!--// 휴대폰인증 팝업 -->
+<div class="tooltip-wrap">
+    <div class="popup-com auth_manage_popup auth_add_number_popup" tabindex="0" data-tooltip-con="auth_add_number_popup"
+         data-focus="auth_add_number_popup" data-focus-prev="auth_add_number_popup-close" style="width:550px;">
+        <div class="popup_heading">
+            <p>인증 휴대폰번호 추가</p>
+            <button type="button" class="tooltip-close" data-focus="auth_add_number_popup-close"><img
+                    src="/publish/images/content/layerPopup_close.png" alt="팝업 닫기"></button>
+        </div>
+        <div class="layer_in" style="padding:20px 30px 30px 30px;">
+
+            <ul class="stage_area">
+                <li class="stage active"><p><strong>1</strong>본인확인</p></li>
+                <li class="stage"><p><strong>2</strong>번호 선택·인증</p></li>
+            </ul>
+
+            <div class="stage_content stage01">
+                <div class="gray_box fill">
+                    인증 휴대폰번호를 추가하려면 기존 등록된 휴대폰 번호로 본인 확인을 해야합니다.
+                </div>
+
+
+                <table class="layer_tType1">
+                    <caption>본인인증(step1) 표</caption>
+                    <colgroup>
+                        <col style="width: 95px;">
+                        <col style="width: auto;">
+                    </colgroup>
+                    <tbody>
+                    <tr>
+                        <th>본인확인</th>
+                        <td>
+                            <div class="form_row">
+                                <select id="certPhoneAddExistingSelect" class="select_number"></select>
+                                <button type="button" id="certPhoneAddExistingSend" class="btnType btnType6 btn_auth_send">인증요청</button>
+                            </div>
+                        </td>
+                    </tr>
+                    <tr>
+                        <th>인증번호</th>
+                        <td>
+                            <div class="form_row">
+                                <input type="text" id="certPhoneAddExistingCode" maxlength="6" inputmode="numeric"
+                                       placeholder="인증번호 6자리 입력" class="inputLight">
+                                <div class="timer" id="certPhoneAddExistingTimer">03:00</div>
+                            </div>
+                        </td>
+                    </tr>
+                    </tbody>
+                </table>
+                <div class="desc_text" id="certPhoneAddExistingDesc">인증번호를 받지 못했다면 인증요청을 다시 눌러주세요. (남은 재발송 <b class="fwBold">3</b>회)</div>
+                <div class="popup_btn_wrap2">
+                    <button type="button" id="certPhoneAddNext" class="btnType btnType2 btn_next">다음</button>
+                    <button type="button" class="tooltip-close" data-focus="auth_add_number_popup-close"
+                            data-focus-next="auth_add_number_popup">닫기
+                    </button>
+                </div>
+            </div>
+
+            <div class="stage_content stage02">
+                <div class="gray_box fill">
+                    승인 완료된 발신번호 중 휴대폰번호만 선택할 수 있습니다.
+                </div>
+
+
+                <table class="layer_tType1">
+                    <caption>본인인증(step1) 표</caption>
+                    <colgroup>
+                        <col style="width: 95px">
+                        <col style="width: auto">
+                    </colgroup>
+                    <tbody>
+                    <tr>
+                        <th>휴대폰번호</th>
+                        <td>
+                            <div class="form_row">
+                                <select id="certPhoneAddCandidateSelect" class="select_number"></select>
+                                <button type="button" id="certPhoneAddCandidateSend" class="btnType btnType6 btn_auth_send">인증요청</button>
+                            </div>
+                        </td>
+                    </tr>
+                    <tr>
+                        <th>인증번호</th>
+                        <td>
+                            <div class="form_row">
+                                <input type="text" id="certPhoneAddCandidateCode" maxlength="6" inputmode="numeric"
+                                       placeholder="인증번호 6자리 입력" class="inputLight">
+                                <div class="timer" id="certPhoneAddCandidateTimer">03:00</div>
+                            </div>
+                        </td>
+                    </tr>
+                    <tr>
+                        <th>별칭</th>
+                        <td>
+                            <div class="form_row">
+                                <input type="text" id="certPhoneAddAlias" maxlength="20"
+                                       placeholder="예: 업무용(선택)" class="inputLight">
+                            </div>
+                        </td>
+                    </tr>
+                    </tbody>
+                </table>
+                <div class="desc_text" id="certPhoneAddCandidateDesc">유선번호·대표번호는 등록할 수 없습니다.</div>
+                <div class="popup_btn_wrap2">
+                    <button type="button" id="certPhoneAddRegister" class="btnType btnType2"
+                            data-focus="auth_add_number_popup-close" data-focus-next="auth_add_number_popup">등록
+                    </button>
+                    <button type="button" class="tooltip-close" data-focus="auth_add_number_popup-close"
+                            data-focus-next="auth_add_number_popup">취소
+                    </button>
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>
+
+<div class="tooltip-wrap">
+    <div class="popup-com auth_manage_popup auth_del_number_popup" tabindex="0" data-tooltip-con="auth_del_number_popup"
+         data-focus="auth_del_number_popup" data-focus-prev="auth_del_number_popup-close" style="width:550px;">
+        <div class="popup_heading">
+            <p>인증 휴대폰번호 삭제</p>
+            <button type="button" class="tooltip-close" data-focus="auth_del_number_popup-close"><img
+                    src="/publish/images/content/layerPopup_close.png" alt="팝업 닫기"></button>
+        </div>
+        <div class="layer_in" style="padding:20px 30px 30px 30px;">
+            <div class="gray_box fill"><span id="certPhoneDeleteTargetText"></span>을 인증수단에서 삭제합니다. 삭제 후에는 이 번호로 인증번호를 받을 수 없습니다.</div>
+            <table class="layer_tType1">
+                <caption>인증 휴대폰번호 삭제 본인확인 표</caption>
+                <colgroup><col style="width:95px"><col style="width:auto"></colgroup>
+                <tbody>
+                <tr>
+                    <th>본인확인</th>
+                    <td><div class="form_row">
+                        <select id="certPhoneDeleteExistingSelect" class="select_number"></select>
+                        <button type="button" id="certPhoneDeleteSend" class="btnType btnType6 btn_auth_send">인증요청</button>
+                    </div></td>
+                </tr>
+                <tr>
+                    <th>인증번호</th>
+                    <td><div class="form_row">
+                        <input type="text" id="certPhoneDeleteCode" maxlength="6" inputmode="numeric" placeholder="인증번호 6자리 입력" class="inputLight">
+                        <div class="timer" id="certPhoneDeleteTimer">03:00</div>
+                    </div></td>
+                </tr>
+                </tbody>
+            </table>
+            <div class="desc_text" id="certPhoneDeleteDesc">인증번호를 받지 못했다면 인증요청을 다시 눌러주세요. (남은 재발송 <b class="fwBold">3</b>회)</div>
+            <div class="popup_btn_wrap2">
+                <button type="button" id="certPhoneDeleteConfirm" class="btnType btnType2">삭제</button>
+                <button type="button" class="tooltip-close" data-focus="auth_del_number_popup-close"
+                        data-focus-next="auth_del_number_popup">닫기</button>
+            </div>
+        </div>
+    </div>
+</div>
+
+<%@ include file="include/secure/certPhone.jspf" %>
 
 <div class="inner">
-	<!-- send top -->
-	<div class="send_top">
-		<!-- tab button -->
-		<%@include file="/WEB-INF/jsp/web/user/mypageHeader.jsp"%>
-		<!--// tab button -->
-		<!-- 마이페이지 - 회원해지 -->
-		<div class="mypage_content current" id="tab5_7">
-					<div class="heading">
-						<h2>보안로그인</h2>
-					</div>
-					<div class="my_dashboard">
+    <div class="send_top">
+        <%@include file="/WEB-INF/jsp/web/user/mypageHeader.jsp" %>
+        <div class="mypage_content current" id="tab5_7">
+            <div class="heading">
+                <h2>보안로그인</h2>
+            </div>
+            <div class="my_dashboard">
 
-						<!-- 보안로그인 -->
-						<div class="security_login">
+                <!-- 보안로그인 -->
+                <div class="security_login">
 
-							<ul class="explanation_wrap box info">
-								<li>· 사이트 부정로그인으로 인한 피해를 방지할 수 있는 2차 로그인 인증 서비스입니다. </li>
-								<li>· 인증 휴대폰번호 추가 등록 또는 IP 접속 허용을 통해 편리한 사용이 가능합니다.</li>
-								<li>· 보안로그인 기능을 사용할 경우 로그인 시 등록한 휴대폰번호로 추가 인증이 진행됩니다.</li>
-							</ul>
+                    <ul class="explanation_wrap box info">
+                        <li>· 사이트 부정로그인으로 인한 피해를 방지할 수 있는 2차 로그인 인증 서비스입니다.</li>
+                        <li>· 인증 휴대폰번호는 가입 휴대폰번호와 발신번호 등록이 완료된 휴대폰번호 중에서 추가할 수 있습니다.</li>
+                        <li>· 인증 휴대폰번호 추가·삭제 시에는 이미 등록된 번호로 본인 확인을 거쳐야 합니다.</li>
+                    </ul>
 
 
-							<!-- 보안로그인 설정(허용 IP 아닌 경우) -->
-							<div class="security_set box">
-								<div class="title_wrap">
-									<p class="dashboard_title">보안로그인</p>
-									<div class="title_box ip_add_wrap">
-										<span>현재 접속중인 IP : <b><c:out value="${cennectIP }"/> <c:if test="${chkcertRegIP }"> [허용 IP] </c:if> </b></span>
-										<input type="hidden" id="cennectIp" value="<c:out value="${cennectIP }"/>"/>
-										<c:if test="${!chkcertRegIP }">
-											<button type="button" class="btnType btnType6 btn_allow_ip_add">허용 IP 등록</button>
-										</c:if>
-									</div>
-								</div>
+                    <div class="title_area">
+                        <p class="dashboard_title">인증 휴대폰번호 관리
+                            <span class="small_text">최대 <b id="certPhoneMaxCount">3</b>개까지 등록할 수 있습니다.</span>
+                        </p>
+                        <button type="button" id="phonePlus" class="btn_plus"><img src="/publish/images/content/mypage_plus.png" alt="더보기"></button>
+                        <button type="button" data-tooltip="auth_add_number_popup" id="phonePlusPopupOpen" style="display:none;"></button>
+                    </div>
 
-								<div class="set_area">
-								<c:if test="${not empty certLogRegDate }">
-									<p class="lately_date">최근 변경일시 : <span><c:out value="${certLogRegDate }"/></span></p>
-								</c:if>
+                    <div class="table_wrap" id="phoneTable">
+                        <table>
+                            <colgroup>
+                                <col style="width: auto;">
+                                <col style="width: 10%;">
+                                <col style="width: auto;">
+                                <col style="width: 15%;">
+                                <col style="width: 15%;">
+                            </colgroup>
+                            <thead>
+                            <tr>
+                                <th scope="col">휴대폰번호</th>
+                                <th scope="col">구분</th>
+                                <th scope="col">별칭</th>
+                                <th scope="col">등록일시</th>
+                                <th scope="col">관리</th>
+                            </tr>
+                            </thead>
+                            <tbody>
+                            <tr>
+                                <td colspan="5">인증 휴대폰번호를 조회하고 있습니다.</td>
+                            </tr>
+                            </tbody>
+                        </table>
+                    </div>
+                    <p class="desc_text">기본 번호(가입 휴대폰번호)는 회원정보 변경 메뉴에서 본인인증 후 변경할 수 있습니다.</p>
+                    <!-- //인증 휴대폰번호 관리 -->
 
-    								<input type="hidden" id="secuLoginFlag" value="${secuLoginFlag}" />
-									<div class="tab_depth1 security_tab">
-										<a href="#none" id="secuOnBtn">ON</a>
-										<a href="#none" id="secuOffBtn">OFF</a>
-									</div>
-								</div>
-							</div>
-							<!-- //보안로그인 설정(허용 IP 아닌 경우) -->
+                    <%@ include file="include/secure/secondAuthMethodLog.jspf" %>
 
-							<!-- 보안로그인 설정(허용 IP인 경우) -->
-<!-- 							<div class="security_set box"> -->
-<!-- 								<div class="title_wrap"> -->
-<!-- 									<p class="dashboard_title">보안로그인</p> -->
-<!-- 									<div class="title_box ip_add_wrap"> -->
-<!-- 										<span>현재 접속중인 IP : <b>000.000.000.00 [허용 IP]</b></span> -->
-<!-- 									</div> -->
-<!-- 								</div> -->
-
-<!-- 								<div class="set_area"> -->
-<!-- 									<p class="lately_date">최근 변경일시 : <span>2024-11-01 12:49</span></p> -->
-
-<!-- 									<div class="tab_depth1"> -->
-<!-- 										<a href="#none" class="on">ON</a> -->
-<!-- 										<a href="#none">OFF</a> -->
-<!-- 									</div> -->
-<!-- 								</div> -->
-<!-- 							</div> -->
-							<!-- //보안로그인 설정(허용 IP인 경우) -->
-
-							<!-- 인증 휴대폰번호 관리 -->
-							<div class="title_area">
-								<p class="dashboard_title">인증 휴대폰번호 관리</p>
-								<p class="qmMark">?</p>
-								<div class="hover_cont" style="width:330px;left:240px;top:25px;">
-									<p>인증에 사용할 휴대폰번호를 추가로 등록하여 관리할 수 있습니다. <br>
-										(등록 시 휴대폰 인증 필요)</p>
-								</div>
-								<button type="button" id="phonePlus" class="btn_plus"><img src="/publish/images/content/mypage_plus.png" alt="더보기"></button>
-								<input type="hidden" data-tooltip="popup05" id="phonePlusPopupOpen"/>
-							</div>
-
-							<div class="table_wrap" id="phoneTable">
-								<table>
-									<colgroup>
-										<col style="width: 140px;">
-										<col style="width: auto;">
-										<col style="width: auto;">
-										<col style="width: 160px;">
-										<col style="width: 140px;">
-									</colgroup>
-									<thead>
-										<tr>
-											<th scope="col">휴대폰번호</th>
-											<th scope="col">별칭</th>
-											<th scope="col">메모</th>
-											<th scope="col">등록일시</th>
-											<th scope="col">관리</th>
-										</tr>
-									</thead>
-									<tbody>
-										<tr>
-											<td colspan="5">등록된 IP 주소가 없습니다.</td>
-										</tr>
-										<!-- <tr>
-											<td>010-0000-0000</td>
-											<td class="td_memo">메모 내용</td>
-											<td>2024-11-04 11:54</td>
-											<td>
-												<button type="button" class="btnType btnType5 btn_edit" style="margin:0 5px 0 0;" onclick="memoEdit(this);">수정
-												</button><button type="button" class="btnType btn_text btn_lightgray fill btn_28">삭제</button>
-											</td>
-										</tr> -->
-									</tbody>
-								</table>
-							</div>
-							<!-- //인증 휴대폰번호 관리 -->
-
-							<!-- 접속 IP관리 -->
-							<div class="title_area">
-								<p class="dashboard_title">접속 IP 관리</p>
-								<p class="qmMark">?</p>
-								<div class="hover_cont" style="width:380px;left:160px;top:25px;">
-									<p>접속 IP를 미리 등록할 경우 2차 로그인 인증 없이 접속이 가능합니다. <br>
-										<span>(공용 PC는 보안상의 이유로 등록을 권장하지 않습니다.)</span></p>
-								</div>
-								<button type="button" class="btn_plus btn_ip_plus"><img src="/publish/images/content/mypage_plus.png" alt="더보기"></button>
-							</div>
-
-							<div class="table_wrap" id="ipTable">
-								<table>
-									<colgroup>
-										<col style="width: 26%;">
-										<col style="width: auto;">
-										<col style="width: 26%;">
-										<col style="width: 100px;">
-									</colgroup>
-									<thead>
-										<tr>
-											<th scope="col">허용IP</th>
-											<th scope="col">메모</th>
-											<th scope="col">등록일시</th>
-											<th scope="col">관리</th>
-										</tr>
-									</thead>
-									<tbody>
-										<tr>
-											<td colspan="4">등록된 IP 주소가 없습니다.</td>
-										</tr>
-<!-- 										<tr> -->
-<!-- 											<td>192.168.0.0</td> -->
-<!-- 											<td>dadfadfjadkfjalkdjfajflajdlfjaldjflajd</td> -->
-<!-- 											<td>2024-11-11 11:11</td> -->
-<!-- 											<td><button type="button" class="btnType btn_text btn_lightgray fill btn_28">삭제</button></td> -->
-<!-- 										</tr> -->
-									</tbody>
-								</table>
-							</div>
-							<!-- //접속 IP관리 -->
-
-
-							<!-- 로그인 내역 -->
-							<div class="title_area">
-								<p class="dashboard_title">로그인 내역 <span class="small_text">로그인 내역은 최대 3개월까지만 보관됩니다.</span></p>
-							</div>
-
-							<div class="table_wrap">
-								<table>
-									<colgroup>
-										<col style="width:calc(100% /3);">
-										<col style="width:calc(100% /3);">
-										<col style="width:calc(100% /3);">
-									</colgroup>
-									<thead>
-										<tr>
-											<th scope="col">로그인 일시</th>
-											<th scope="col">로그인 IP</th>
-											<th scope="col">로그인 환경</th>
-										</tr>
-									</thead>
-									<tbody>
-										<c:forEach var="result" items="${resultList}" varStatus="status">
-										<tr>
-											<td><c:out value="${result.loginDt}"/></td>
-											<td><c:out value="${result.loginIp}"/></td>
-											<td><c:out value="${result.loginDevice}"/></td>
-										</tr>
-										</c:forEach>
-									</tbody>
-								</table>
-							</div>
-							<!-- //로그인 내역 -->
-							<!-- pagination -->
-							<!-- <ul class="pagination">
-								<li class="page_first"><button><img src="/publish/images/content/page_first.png" alt=""></button></li>
-								<li class="page_prev"><button><img src="/publish/images/content/page_prev.png" alt=""></button></li>
-								<li class="on"><button>1</button></li>
-								<li><button>2</button></li>
-								<li><button>3</button></li>
-								<li><button>4</button></li>
-								<li><button>5</button></li>
-								<li><button>6</button></li>
-								<li><button>7</button></li>
-								<li><button>8</button></li>
-								<li><button>9</button></li>
-								<li><button>10</button></li>
-								<li class="page_next"><button><img src="/publish/images/content/page_next.png" alt=""></button></li>
-								<li class="page_last"><button><img src="/publish/images/content/page_last.png" alt=""></button></li>
-							</ul>pagination
- -->
-							<c:if test="${!empty resultList}">
-							   <ul class="pagination">
-							       <ui:pagination paginationInfo = "${paginationInfo}"  type="imageWeb" jsFunction="linkPage" />
-							   </ul>
-							</c:if>
-						</div>
-						<!-- //보안로그인 -->
-					</div>
-				</div>
-		<!--// 마이페이지 - 회원해지 -->
-	</div>
-	<!--// send top -->
+                    <%@ include file="include/secure/secondAuthLog.jspf" %>
+                </div>
+                <!-- //보안로그인 -->
+            </div>
+        </div>
+        <!--// 마이페이지 - 회원해지 -->
+    </div>
+    <!--// send top -->
 </div>
 <!--// content 영역 -->
 
src/main/webapp/WEB-INF/jsp/web/user/sendNumberEmployeeCertPopup.jspf (added)
+++ src/main/webapp/WEB-INF/jsp/web/user/sendNumberEmployeeCertPopup.jspf
This diff is skipped because there are too many other diffs.
src/main/webapp/WEB-INF/jsp/web/user/sendNumberListAjax.jsp
--- src/main/webapp/WEB-INF/jsp/web/user/sendNumberListAjax.jsp
+++ src/main/webapp/WEB-INF/jsp/web/user/sendNumberListAjax.jsp
This diff is skipped because there are too many other diffs.
src/main/webapp/WEB-INF/jsp/web/user/sendNumberManage.jsp
--- src/main/webapp/WEB-INF/jsp/web/user/sendNumberManage.jsp
+++ src/main/webapp/WEB-INF/jsp/web/user/sendNumberManage.jsp
This diff is skipped because there are too many other diffs.
src/main/webapp/WEB-INF/jsp/web/user/sendNumberManageRegister.jsp
--- src/main/webapp/WEB-INF/jsp/web/user/sendNumberManageRegister.jsp
+++ src/main/webapp/WEB-INF/jsp/web/user/sendNumberManageRegister.jsp
This diff is skipped because there are too many other diffs.
src/main/webapp/js/web/msgdata/msgDataView.js
--- src/main/webapp/js/web/msgdata/msgDataView.js
+++ src/main/webapp/js/web/msgdata/msgDataView.js
This diff is skipped because there are too many other diffs.
src/main/webapp/mail/mail_member_dormant.html
--- src/main/webapp/mail/mail_member_dormant.html
+++ src/main/webapp/mail/mail_member_dormant.html
This diff is skipped because there are too many other diffs.
src/main/webapp/pb/css/content.css
--- src/main/webapp/pb/css/content.css
+++ src/main/webapp/pb/css/content.css
This diff is skipped because there are too many other diffs.
src/main/webapp/pb/js/common.js
--- src/main/webapp/pb/js/common.js
+++ src/main/webapp/pb/js/common.js
This diff is skipped because there are too many other diffs.
 
src/main/webapp/pb/layout/_header.html (added)
+++ src/main/webapp/pb/layout/_header.html
This diff is skipped because there are too many other diffs.
 
src/main/webapp/pb/layout/_lnb.html (added)
+++ src/main/webapp/pb/layout/_lnb.html
This diff is skipped because there are too many other diffs.
src/main/webapp/publish/api_guide.html
--- src/main/webapp/publish/api_guide.html
+++ src/main/webapp/publish/api_guide.html
This diff is skipped because there are too many other diffs.
src/main/webapp/publish/api_send_history.html
--- src/main/webapp/publish/api_send_history.html
+++ src/main/webapp/publish/api_send_history.html
This diff is skipped because there are too many other diffs.
src/main/webapp/publish/css/button.css
--- src/main/webapp/publish/css/button.css
+++ src/main/webapp/publish/css/button.css
This diff is skipped because there are too many other diffs.
src/main/webapp/publish/css/common.css
--- src/main/webapp/publish/css/common.css
+++ src/main/webapp/publish/css/common.css
This diff is skipped because there are too many other diffs.
src/main/webapp/publish/css/content.css
--- src/main/webapp/publish/css/content.css
+++ src/main/webapp/publish/css/content.css
This diff is skipped because there are too many other diffs.
src/main/webapp/publish/css/font.css
--- src/main/webapp/publish/css/font.css
+++ src/main/webapp/publish/css/font.css
This diff is skipped because there are too many other diffs.
 
src/main/webapp/publish/css/jquery-ui.css (added)
+++ src/main/webapp/publish/css/jquery-ui.css
This diff is skipped because there are too many other diffs.
src/main/webapp/publish/css/main.css
--- src/main/webapp/publish/css/main.css
+++ src/main/webapp/publish/css/main.css
This diff is skipped because there are too many other diffs.
src/main/webapp/publish/css/popupLayer.css
--- src/main/webapp/publish/css/popupLayer.css
+++ src/main/webapp/publish/css/popupLayer.css
This diff is skipped because there are too many other diffs.
 
src/main/webapp/publish/email_form_terms_2026_clause.html (deleted)
--- src/main/webapp/publish/email_form_terms_2026_clause.html
This diff is skipped because there are too many other diffs.
 
src/main/webapp/publish/font/Pretendard-Black.subset.woff (Binary) (added)
+++ src/main/webapp/publish/font/Pretendard-Black.subset.woff
Binary file is not shown
 
src/main/webapp/publish/font/Pretendard-Black.subset.woff2 (Binary) (added)
+++ src/main/webapp/publish/font/Pretendard-Black.subset.woff2
Binary file is not shown
 
src/main/webapp/publish/font/Pretendard-Bold.subset.woff (Binary) (added)
+++ src/main/webapp/publish/font/Pretendard-Bold.subset.woff
Binary file is not shown
 
src/main/webapp/publish/font/Pretendard-Bold.subset.woff2 (Binary) (added)
+++ src/main/webapp/publish/font/Pretendard-Bold.subset.woff2
Binary file is not shown
 
src/main/webapp/publish/font/Pretendard-ExtraBold.subset.woff (Binary) (added)
+++ src/main/webapp/publish/font/Pretendard-ExtraBold.subset.woff
Binary file is not shown
 
src/main/webapp/publish/font/Pretendard-ExtraBold.subset.woff2 (Binary) (added)
+++ src/main/webapp/publish/font/Pretendard-ExtraBold.subset.woff2
Binary file is not shown
 
src/main/webapp/publish/font/Pretendard-ExtraLight.subset.woff (Binary) (added)
+++ src/main/webapp/publish/font/Pretendard-ExtraLight.subset.woff
Binary file is not shown
 
src/main/webapp/publish/font/Pretendard-ExtraLight.subset.woff2 (Binary) (added)
+++ src/main/webapp/publish/font/Pretendard-ExtraLight.subset.woff2
Binary file is not shown
 
src/main/webapp/publish/font/Pretendard-Light.subset.woff (Binary) (added)
+++ src/main/webapp/publish/font/Pretendard-Light.subset.woff
Binary file is not shown
 
src/main/webapp/publish/font/Pretendard-Light.subset.woff2 (Binary) (added)
+++ src/main/webapp/publish/font/Pretendard-Light.subset.woff2
Binary file is not shown
 
src/main/webapp/publish/font/Pretendard-Medium.subset.woff (Binary) (added)
+++ src/main/webapp/publish/font/Pretendard-Medium.subset.woff
Binary file is not shown
 
src/main/webapp/publish/font/Pretendard-Medium.subset.woff2 (Binary) (added)
+++ src/main/webapp/publish/font/Pretendard-Medium.subset.woff2
Binary file is not shown
 
src/main/webapp/publish/font/Pretendard-Regular.subset.woff (Binary) (added)
+++ src/main/webapp/publish/font/Pretendard-Regular.subset.woff
Binary file is not shown
 
src/main/webapp/publish/font/Pretendard-Regular.subset.woff2 (Binary) (added)
+++ src/main/webapp/publish/font/Pretendard-Regular.subset.woff2
Binary file is not shown
 
src/main/webapp/publish/font/Pretendard-SemiBold.subset.woff (Binary) (added)
+++ src/main/webapp/publish/font/Pretendard-SemiBold.subset.woff
Binary file is not shown
 
src/main/webapp/publish/font/Pretendard-SemiBold.subset.woff2 (Binary) (added)
+++ src/main/webapp/publish/font/Pretendard-SemiBold.subset.woff2
Binary file is not shown
 
src/main/webapp/publish/font/Pretendard-Thin.subset.woff (Binary) (added)
+++ src/main/webapp/publish/font/Pretendard-Thin.subset.woff
Binary file is not shown
 
src/main/webapp/publish/font/Pretendard-Thin.subset.woff2 (Binary) (added)
+++ src/main/webapp/publish/font/Pretendard-Thin.subset.woff2
Binary file is not shown
src/main/webapp/publish/guide.html
--- src/main/webapp/publish/guide.html
+++ src/main/webapp/publish/guide.html
This diff is skipped because there are too many other diffs.
src/main/webapp/publish/index.html
--- src/main/webapp/publish/index.html
+++ src/main/webapp/publish/index.html
This diff is skipped because there are too many other diffs.
src/main/webapp/publish/index_2025.html
--- src/main/webapp/publish/index_2025.html
+++ src/main/webapp/publish/index_2025.html
This diff is skipped because there are too many other diffs.
src/main/webapp/publish/js/common.js
--- src/main/webapp/publish/js/common.js
+++ src/main/webapp/publish/js/common.js
This diff is skipped because there are too many other diffs.
 
src/main/webapp/publish/js/jquery-ui.min.js (added)
+++ src/main/webapp/publish/js/jquery-ui.min.js
This diff is skipped because there are too many other diffs.
src/main/webapp/publish/js/main.js
--- src/main/webapp/publish/js/main.js
+++ src/main/webapp/publish/js/main.js
This diff is skipped because there are too many other diffs.
src/main/webapp/publish/js/popupLayer.js
--- src/main/webapp/publish/js/popupLayer.js
+++ src/main/webapp/publish/js/popupLayer.js
This diff is skipped because there are too many other diffs.
src/main/webapp/publish/layout/_header.html
--- src/main/webapp/publish/layout/_header.html
+++ src/main/webapp/publish/layout/_header.html
This diff is skipped because there are too many other diffs.
src/main/webapp/publish/mypage1.html
--- src/main/webapp/publish/mypage1.html
+++ src/main/webapp/publish/mypage1.html
This diff is skipped because there are too many other diffs.
 
src/main/webapp/publish/mypage5_bak.html (added)
+++ src/main/webapp/publish/mypage5_bak.html
This diff is skipped because there are too many other diffs.
src/main/webapp/publish/mypage_security_login.html
--- src/main/webapp/publish/mypage_security_login.html
+++ src/main/webapp/publish/mypage_security_login.html
This diff is skipped because there are too many other diffs.
 
src/main/webapp/publish/mypage_security_login_bak.html (added)
+++ src/main/webapp/publish/mypage_security_login_bak.html
This diff is skipped because there are too many other diffs.
src/main/webapp/publish/security_login.html
--- src/main/webapp/publish/security_login.html
+++ src/main/webapp/publish/security_login.html
This diff is skipped because there are too many other diffs.
src/main/webapp/publish/sub1.html
--- src/main/webapp/publish/sub1.html
+++ src/main/webapp/publish/sub1.html
This diff is skipped because there are too many other diffs.
 
src/main/webapp/publish/sub1_2026.html (added)
+++ src/main/webapp/publish/sub1_2026.html
This diff is skipped because there are too many other diffs.
 
src/main/webapp/publish/sub1_bak.html (added)
+++ src/main/webapp/publish/sub1_bak.html
This diff is skipped because there are too many other diffs.
Add a comment
List