diff --git a/script/portal/oracle/5_ptl_user_session.sql b/script/portal/oracle/5_ptl_user_session.sql
new file mode 100644
index 0000000..e0b3f2d
--- /dev/null
+++ b/script/portal/oracle/5_ptl_user_session.sql
@@ -0,0 +1,36 @@
+-- =============================================================================
+-- 세션 타이머 / 유휴 자동 로그아웃 / 중복로그인 처리용 사용자 세션 추적 테이블
+-- 대상: EMS 스키마 소유자(EMSAPP 등)로 접속하여 실행
+-- 엔티티: com.eactive.apim.portal.apps.session.entity.UserSession
+-- =============================================================================
+
+CREATE TABLE PTL_USER_SESSION
+(
+ SESSION_ID VARCHAR2(128) NOT NULL,
+ USER_ID VARCHAR2(36) NOT NULL,
+ LOGIN_ID VARCHAR2(200) NOT NULL,
+ LOGIN_TIME TIMESTAMP(6) NOT NULL,
+ LAST_ACCESS_TIME TIMESTAMP(6) NOT NULL,
+ IP_ADDRESS VARCHAR2(45),
+ USER_AGENT VARCHAR2(500),
+ FORCE_LOGOUT CHAR(1) DEFAULT 'N' NOT NULL,
+ CONSTRAINT PK_PTL_USER_SESSION PRIMARY KEY (SESSION_ID)
+);
+
+-- 중복로그인 강제 로그아웃 / 활성 세션 조회는 LOGIN_ID 기준
+CREATE INDEX IX_PTL_USER_SESSION_LOGIN ON PTL_USER_SESSION (LOGIN_ID);
+
+COMMENT ON TABLE PTL_USER_SESSION IS '포털 사용자 세션 추적 (타이머/유휴 로그아웃/중복로그인)';
+COMMENT ON COLUMN PTL_USER_SESSION.SESSION_ID IS 'HTTP 세션 ID (PK)';
+COMMENT ON COLUMN PTL_USER_SESSION.USER_ID IS '포털 사용자 ID';
+COMMENT ON COLUMN PTL_USER_SESSION.LOGIN_ID IS '로그인 ID (이메일, 소문자)';
+COMMENT ON COLUMN PTL_USER_SESSION.LOGIN_TIME IS '로그인 시각';
+COMMENT ON COLUMN PTL_USER_SESSION.LAST_ACCESS_TIME IS '마지막 접근 시각 (만료 판단 기준)';
+COMMENT ON COLUMN PTL_USER_SESSION.IP_ADDRESS IS '접속 IP';
+COMMENT ON COLUMN PTL_USER_SESSION.USER_AGENT IS 'User-Agent';
+COMMENT ON COLUMN PTL_USER_SESSION.FORCE_LOGOUT IS '강제 로그아웃 플래그 (Y/N)';
+
+-- (선택) 세션 타임아웃(분) 설정값을 미리 지정. 미삽입 시 최초 접근 때 기본값 15로 자동 생성됨.
+-- INSERT INTO PTL_PROPERTY (PROPERTY_GROUP_NAME, PROPERTY_NAME, PROPERTY_VALUE, PROPERTY_DESC)
+-- VALUES ('Portal', 'session.timeout.minutes', '15', '세션 타임아웃 시간 (분)');
+-- COMMIT;
diff --git a/src/main/java/com/eactive/apim/portal/apps/session/controller/SessionApiController.java b/src/main/java/com/eactive/apim/portal/apps/session/controller/SessionApiController.java
new file mode 100644
index 0000000..60dc7ce
--- /dev/null
+++ b/src/main/java/com/eactive/apim/portal/apps/session/controller/SessionApiController.java
@@ -0,0 +1,130 @@
+package com.eactive.apim.portal.apps.session.controller;
+
+import com.eactive.apim.portal.apps.session.entity.UserSession;
+import com.eactive.apim.portal.apps.session.service.UserSessionService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import java.time.format.DateTimeFormatter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * 세션 타이머/유휴 로그아웃/중복로그인 처리용 REST API.
+ *
+ *
+ * - GET /api/session/status - 잔여 시간/유효성 폴링 (인증 필요)
+ * - POST /api/session/heartbeat - 세션 연장 (lastAccessTime 갱신)
+ * - POST /api/session/check-duplicate - 로그인 전 중복 세션 확인 (CSRF 예외)
+ *
+ */
+@Slf4j
+@RestController
+@RequestMapping("/api/session")
+@RequiredArgsConstructor
+public class SessionApiController {
+
+ private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+ private final UserSessionService userSessionService;
+
+ /**
+ * 로그인 전 중복 세션 확인
+ */
+ @PostMapping("/check-duplicate")
+ public ResponseEntity