merge 충돌수정
This commit is contained in:
@@ -2,11 +2,10 @@ server:
|
||||
servlet:
|
||||
context-path: /
|
||||
session:
|
||||
# 물리 세션 타임아웃은 DB PortalProperty(Portal/session.timeout.minutes)로 관리한다.
|
||||
# 로그인 성공 시 PortalAuthenticationSuccessHandler 가
|
||||
# session.setMaxInactiveInterval(session.timeout.minutes * 60) 으로 적용 → 물리=논리 일치.
|
||||
# 익명/로그인 전 세션은 컨테이너 기본값으로 fallback (weblogic.xml <timeout-secs>1800).
|
||||
# timeout: 10m
|
||||
# 세션 타임아웃 10분 고정 (DB property 관리 폐지).
|
||||
# WebLogic 배포 시에는 weblogic.xml <timeout-secs>600 이 동일 값을 적용한다.
|
||||
# CSRF 토큰은 세션에 저장(HttpSessionCsrfTokenRepository)되므로 수명도 이 값과 동일하다.
|
||||
timeout: 10m
|
||||
cookie:
|
||||
name: JSESSIONID_PORTAL
|
||||
encoding:
|
||||
|
||||
@@ -56,6 +56,19 @@
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- API 테스트베드(/api/call-api) 감사 로그: 요청지/헤더/마스킹된 본문 기록, 1년(365일) 보관 -->
|
||||
<appender name="API_TESTER_AUDIT" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_PATH}/apitester-audit.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_PATH}/backup/apitester-audit.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
|
||||
<maxFileSize>200MB</maxFileSize>
|
||||
<maxHistory>365</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>${CONSOLE_EFFECTIVE_LEVEL}</level>
|
||||
@@ -68,6 +81,10 @@
|
||||
<appender-ref ref="HTTP_SESSION" />
|
||||
</logger>
|
||||
|
||||
<logger name="eapim.portal.apitester.audit" level="INFO" additivity="false">
|
||||
<appender-ref ref="API_TESTER_AUDIT" />
|
||||
</logger>
|
||||
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="ROLLING"/>
|
||||
|
||||
+3993
-299
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -26,6 +26,9 @@
|
||||
|
||||
var startTs = 0;
|
||||
var targetOpblock = null;
|
||||
// Execute 클릭 후에만 true. spec 로딩 등 실행 외 응답이 responseInterceptor 로
|
||||
// 들어와도 그리드를 만들지 않기 위한 게이트 (실행 전 UI 미노출 보장).
|
||||
var pendingExecute = false;
|
||||
|
||||
function now() {
|
||||
return (global.performance && performance.now) ? performance.now() : Date.now();
|
||||
@@ -206,6 +209,7 @@
|
||||
|
||||
// 네트워크/CORS 실패 렌더: responseInterceptor 로 오지 않고 store 에만 error 로 남는 케이스.
|
||||
function renderNetworkError(msg) {
|
||||
pendingExecute = false;
|
||||
var grid = gridFor(targetOpblock);
|
||||
if (!grid) return;
|
||||
grid.classList.remove("djb-empty");
|
||||
@@ -250,6 +254,7 @@
|
||||
}
|
||||
|
||||
startTs = now();
|
||||
pendingExecute = true;
|
||||
var grid = gridFor(targetOpblock);
|
||||
if (grid) {
|
||||
grid.classList.add("djb-empty");
|
||||
@@ -264,10 +269,13 @@
|
||||
*/
|
||||
function renderResponse(res) {
|
||||
if (!res) return;
|
||||
// spec 로딩 응답(/…/swagger.json)은 responseInterceptor 로도 들어온다. 이때 렌더하면
|
||||
// 실행 전인데 응답 본문에 OpenAPI spec 이 그려지므로 무시한다. 사용자 API 호출은
|
||||
// 프록시(/api/call-api)로 나가므로 url 에 swagger.json 이 없다.
|
||||
// Execute 를 누른 적 없으면 무시 — spec 로딩(/…/swagger.json) 응답도
|
||||
// responseInterceptor 로 들어오는데, 이때 렌더하면 실행 전 응답 본문에
|
||||
// OpenAPI spec 이 그려진다. url 검사는 버전에 따라 res.url 이 비어 무력화될
|
||||
// 수 있어 실행 게이트를 1차 방어로 둔다.
|
||||
if (!pendingExecute) return;
|
||||
if (res.url && res.url.indexOf("swagger.json") !== -1) return;
|
||||
pendingExecute = false;
|
||||
var ms = Math.max(0, Math.round(now() - startTs));
|
||||
var status = res.status || 0;
|
||||
var statusText = res.statusText || "";
|
||||
|
||||
@@ -671,6 +671,139 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Testbed 탭: 앱 선택 패널과 Swagger UI 사이 여백 제거
|
||||
#testbed-tab {
|
||||
gap: 0;
|
||||
|
||||
#swagger-ui .info {
|
||||
margin: $spacing-lg 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Testbed 앱 선택 패널 (DJPGPT0001)
|
||||
.testbed-app-panel {
|
||||
margin: $spacing-sm 0 0;
|
||||
padding: $spacing-lg;
|
||||
background: $white;
|
||||
border: 1px solid $border-gray;
|
||||
border-radius: $border-radius-lg;
|
||||
box-shadow: $shadow-sm;
|
||||
|
||||
.testbed-app-panel__head {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
flex-wrap: wrap;
|
||||
gap: $spacing-xs $spacing-sm;
|
||||
margin-bottom: $spacing-md;
|
||||
}
|
||||
|
||||
.testbed-app-panel__title {
|
||||
position: relative;
|
||||
padding-left: $spacing-md;
|
||||
font-size: $font-size-base;
|
||||
font-weight: $font-weight-bold;
|
||||
color: $text-dark;
|
||||
letter-spacing: -0.01em;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 4px;
|
||||
height: 15px;
|
||||
background: $primary-blue;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.testbed-app-panel__desc {
|
||||
font-size: $font-size-sm;
|
||||
color: $text-gray;
|
||||
}
|
||||
|
||||
.testbed-app-field {
|
||||
position: relative;
|
||||
max-width: 380px;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: $spacing-md;
|
||||
top: 50%;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
margin-top: -6px;
|
||||
border-right: 2px solid $text-gray;
|
||||
border-bottom: 2px solid $text-gray;
|
||||
transform: rotate(45deg);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
#apps {
|
||||
width: 100%;
|
||||
height: 46px;
|
||||
padding: 0 42px 0 $input-padding-x;
|
||||
font-family: $font-family-primary;
|
||||
font-size: $font-size-sm;
|
||||
color: $text-dark;
|
||||
background: $gray-bg;
|
||||
border: 1px solid $border-gray;
|
||||
border-radius: $border-radius-md;
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
cursor: pointer;
|
||||
transition: $transition-fast;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
border-color: $text-light;
|
||||
background: $white;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
background: $white;
|
||||
border-color: $primary-blue;
|
||||
box-shadow: 0 0 0 3px rgba($primary-blue, 0.14);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: $text-light;
|
||||
background: $gray-bg;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.testbed-app-notice {
|
||||
margin: $spacing-md 0 0;
|
||||
padding: 11px $spacing-md;
|
||||
font-size: $font-size-sm;
|
||||
line-height: $line-height-normal;
|
||||
color: $text-gray;
|
||||
background: $light-bg;
|
||||
border: 1px solid rgba($primary-blue, 0.18);
|
||||
border-left: 3px solid $primary-blue;
|
||||
border-radius: $border-radius-sm;
|
||||
|
||||
&::before {
|
||||
content: "ⓘ ";
|
||||
color: $primary-blue;
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-sm) {
|
||||
padding: $spacing-md;
|
||||
|
||||
.testbed-app-field {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// API Overview Card (Flat Style)
|
||||
.api-overview-card {
|
||||
background: transparent;
|
||||
@@ -777,7 +910,11 @@
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
// [editor-content 정렬] Summernote(관리자) 작성 콘텐츠는 editor-content.css 가 전담 스타일링하므로
|
||||
// 자체 테이블/폰트 스타일은 .editor-content 미적용 영역(샘플 pre/code)에만 건다.
|
||||
// 과거 회귀: :not(.editor-content) 를 지워 .detail-content 로 되돌리고,
|
||||
// mainApiDetail.html 의 editor-content 클래스 3곳을 제거하면 기존 스타일로 복원됨
|
||||
.detail-content:not(.editor-content) {
|
||||
font-size: $font-size-sm;
|
||||
color: $text-gray;
|
||||
line-height: $line-height-normal;
|
||||
@@ -987,8 +1124,16 @@
|
||||
margin-bottom: $spacing-sm;
|
||||
}
|
||||
|
||||
// [editor-content 정렬] Summernote 콘텐츠는 editor-content.css 모바일 규칙 사용,
|
||||
// 넘치는 표 대비 가로 스크롤만 부여. 과거 회귀 시 이 블록 삭제
|
||||
.detail-content.editor-content {
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
// Detail Content - 모바일에서 가로 스크롤 지원
|
||||
.detail-content {
|
||||
// 과거 회귀: :not(.editor-content) 제거
|
||||
.detail-content:not(.editor-content) {
|
||||
font-size: $font-size-xs;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
|
||||
@@ -1037,7 +1037,7 @@ $o2leg-err-fg: #a23b3b;
|
||||
// -- Section 2: 사전 준비 ----------------------------------------------------
|
||||
&__prereq-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
@@ -1108,18 +1108,28 @@ $o2leg-err-fg: #a23b3b;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
|
||||
// 연속 배치된 grid 사이 간격을 내부 gap(24px)과 동일하게 유지
|
||||
// (언어별 코드 블럭이 두 grid 에 나뉘어 있어 간격이 달라 보이는 문제 방지)
|
||||
+ .oauth2-2legged__step-grid {
|
||||
margin-top: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
&__endpoint-box {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 8px;
|
||||
padding: 16px 24px;
|
||||
margin-bottom: 20px;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid $o2leg-border;
|
||||
border-radius: 12px;
|
||||
|
||||
.oauth2-2legged__method,
|
||||
.oauth2-2legged__endpoint-content-type {
|
||||
justify-self: start;
|
||||
}
|
||||
}
|
||||
|
||||
&__method {
|
||||
|
||||
@@ -86,7 +86,9 @@
|
||||
<div class="api-simple-description" th:if="${apiSpecInfo.apiSimpleDescription != null}">
|
||||
<p th:text="${apiSpecInfo.apiSimpleDescription}">Simple API description</p>
|
||||
</div>
|
||||
<div class="detail-content" th:utext="${apiSpecInfo.description}">
|
||||
<!--/* [editor-content] 관리자포탈 Summernote 콘텐츠 스타일 정렬(editor-content.css).
|
||||
과거 스타일(자체 detail-content SCSS)로 회귀하려면 editor-content 클래스만 제거 */-->
|
||||
<div class="detail-content editor-content" th:utext="${apiSpecInfo.description}">
|
||||
Detailed API description
|
||||
</div>
|
||||
</div>
|
||||
@@ -99,7 +101,8 @@
|
||||
<div class="org-section-header org-section-header--agreement">
|
||||
<h3>Request Specification</h3>
|
||||
</div>
|
||||
<div class="detail-content" th:utext="${apiSpecInfo.apiRequestSpec}">
|
||||
<!--/* [editor-content] 회귀 시 editor-content 클래스 제거 */-->
|
||||
<div class="detail-content editor-content" th:utext="${apiSpecInfo.apiRequestSpec}">
|
||||
Request spec
|
||||
</div>
|
||||
</div>
|
||||
@@ -119,7 +122,8 @@
|
||||
<div class="org-section-header org-section-header--agreement">
|
||||
<h3>Response Specification</h3>
|
||||
</div>
|
||||
<div class="detail-content" th:utext="${apiSpecInfo.apiResponseSpec}">
|
||||
<!--/* [editor-content] 회귀 시 editor-content 클래스 제거 */-->
|
||||
<div class="detail-content editor-content" th:utext="${apiSpecInfo.apiResponseSpec}">
|
||||
Response spec
|
||||
</div>
|
||||
</div>
|
||||
@@ -294,7 +298,9 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const swaggerUrl = `/djb/testbed/apis/${apiId}/swagger.json`;
|
||||
// UI 전용 spec: 서버 주소가 responseType(sample/mock/gw) 설정에 따라 치환됨.
|
||||
// (swagger.json 은 외부 다운로드용으로 항상 GW 주소 고정)
|
||||
const swaggerUrl = `/djb/testbed/apis/${apiId}/swagger-ui.json`;
|
||||
|
||||
// Load Swagger UI scripts dynamically
|
||||
const loadScript = (src) => {
|
||||
|
||||
@@ -115,6 +115,24 @@
|
||||
form.checkId.checked = ((form.id.value = getCookie('saveid')) !== null);
|
||||
}
|
||||
|
||||
// CSRF 토큰 재발급 안전망: keepalive ping이 끊긴 경우(절전·네트워크 단절 등)
|
||||
// 세션이 만료됐어도 제출 직전 새 토큰을 받아 403(만료) 대신 정상 로그인되게 한다.
|
||||
// 재발급 실패 시에는 기존 토큰으로 그대로 제출한다.
|
||||
function refreshCsrfAndThen(form, next) {
|
||||
$.ajax({
|
||||
url: /*[[@{/api/session/csrf}]]*/ '/api/session/csrf',
|
||||
type: 'GET',
|
||||
success: function (data) {
|
||||
if (data && data.token) {
|
||||
if (form['_csrf']) { form['_csrf'].value = data.token; }
|
||||
var meta = document.querySelector('meta[name="_csrf"]');
|
||||
if (meta) { meta.content = data.token; }
|
||||
}
|
||||
},
|
||||
complete: function () { next(); }
|
||||
});
|
||||
}
|
||||
|
||||
// 로그인 전 중복 접속 확인 → 중복 시 기존 세션 강제 로그아웃 여부 질의
|
||||
function checkDuplicateAndLogin(form) {
|
||||
var loginId = form.id.value;
|
||||
@@ -157,26 +175,8 @@
|
||||
|
||||
$(function () {
|
||||
|
||||
// CSRF 토큰은 세션 기반 → 세션 만료(server.servlet.session.timeout=10m) 전에
|
||||
// 메인 페이지로 이동시켜 stale 토큰 제출(403) 방지. 이동은 서버 요청이라 세션 idle도 리셋됨.
|
||||
// 단, 입력 중에는 이탈 금지 → idle 타이머(활동 시 리셋, 입력값/포커스 있으면 보류).
|
||||
// 만료 시간은 하드코딩하지 않고 서버 세션 타임아웃(초)을 렌더 시점에 읽어 파생 → yml 변경 시 자동 반영.
|
||||
var SESSION_TIMEOUT_SEC = /*[[${#request.session.maxInactiveInterval}]]*/ 600;
|
||||
var LOGIN_IDLE_LIMIT_MS = Math.max(60, SESSION_TIMEOUT_SEC - 120) * 1000; // 세션 만료 2분 전
|
||||
var loginIdleTimer;
|
||||
function scheduleIdleRedirect() {
|
||||
clearTimeout(loginIdleTimer);
|
||||
loginIdleTimer = setTimeout(function () {
|
||||
var idEl = document.getElementById('id');
|
||||
var pwEl = document.getElementById('password');
|
||||
var busy = document.activeElement === idEl || document.activeElement === pwEl
|
||||
|| (idEl && idEl.value) || (pwEl && pwEl.value);
|
||||
if (busy) { scheduleIdleRedirect(); return; } // 입력 중/입력값 있음 → 이탈 보류
|
||||
window.location.href = /*[[@{/}]]*/ '/';
|
||||
}, LOGIN_IDLE_LIMIT_MS);
|
||||
}
|
||||
$('#id, #password, #checkId').on('input keydown focus click', scheduleIdleRedirect);
|
||||
scheduleIdleRedirect();
|
||||
// 세션 만료 대응은 head의 익명 keepalive ping(/api/session/ping)이 담당하고,
|
||||
// 제출 직전 refreshCsrfAndThen()이 CSRF 토큰 재발급 안전망 역할을 한다.
|
||||
|
||||
var successMsg = [[${success}]];
|
||||
console.log("Success message:", successMsg);
|
||||
@@ -225,7 +225,9 @@
|
||||
$('#loginLoading').removeClass('active');
|
||||
customPopups.showAlert('[[#{login.passLengthShort}]]');
|
||||
} else {
|
||||
checkDuplicateAndLogin(form);
|
||||
refreshCsrfAndThen(form, function () {
|
||||
checkDuplicateAndLogin(form);
|
||||
});
|
||||
}
|
||||
}
|
||||
form.classList.add('was-validated');
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -111,13 +111,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Call Back URL : OAuth2 3-legged 전용. 현재 미사용으로 주석 처리 (추후 OAuth2 도입 시 복원)
|
||||
<!--/* Call Back URL : OAuth2 3-legged 전용. 현재 미사용으로 주석 처리 (추후 OAuth2 도입 시 복원)
|
||||
<div class="s1-field">
|
||||
<label class="s1-label">Call Back URL</label>
|
||||
<input type="text" id="callbackUrl" name="callbackUrl" th:field="*{callbackUrl}" class="s1-input"
|
||||
placeholder="URL을 입력해 주세요.">
|
||||
</div>
|
||||
-->
|
||||
*/-->
|
||||
|
||||
<!-- 화이트 리스트 -->
|
||||
<div class="s1-field">
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<!-- Step 1 -->
|
||||
<div class="signup-step">
|
||||
<div class="signup-step__icon-box one">
|
||||
<!-- <svg width="40" height="40" viewBox="0 0 32 32" fill="none"
|
||||
<!--/* <svg width="40" height="40" viewBox="0 0 32 32" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M14 20C14 19.4477 14.4477 19 15 19H17C17.5523 19 18 19.4477 18 20V24C18 24.5523 17.5523 25 17 25H15C14.4477 25 14 24.5523 14 24V20Z"
|
||||
@@ -65,7 +65,7 @@
|
||||
d="M19 14C19 12.8954 19.8954 12 21 12C22.1046 12 23 12.8954 23 14C23 15.1046 22.1046 16 21 16C19.8954 16 19 15.1046 19 14Z"
|
||||
stroke="#0049B4" stroke-width="1.6" stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
</svg> -->
|
||||
</svg> */-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
||||
class="bi bi-person-add" viewBox="0 0 16 16">
|
||||
<path
|
||||
@@ -83,7 +83,7 @@
|
||||
<!-- Step 2 -->
|
||||
<div class="signup-step">
|
||||
<div class="signup-step__icon-box two">
|
||||
<!-- <svg width="40" height="40" viewBox="0 0 32 32" fill="none"
|
||||
<!--/* <svg width="40" height="40" viewBox="0 0 32 32" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M14 20C14 19.4477 14.4477 19 15 19H17C17.5523 19 18 19.4477 18 20V24C18 24.5523 17.5523 25 17 25H15C14.4477 25 14 24.5523 14 24V20Z"
|
||||
@@ -95,7 +95,7 @@
|
||||
stroke-linejoin="round" />
|
||||
<path d="M19 16L21 18L25 14" stroke="#0049B4" stroke-width="1.6"
|
||||
stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg> -->
|
||||
</svg> */-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
||||
class="bi bi-journal-check" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd"
|
||||
@@ -115,14 +115,14 @@
|
||||
<!-- Step 3 -->
|
||||
<div class="signup-step">
|
||||
<div class="signup-step__icon-box three">
|
||||
<!-- <svg width="40" height="40" viewBox="0 0 32 32" fill="none"
|
||||
<!--/* <svg width="40" height="40" viewBox="0 0 32 32" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="7" y="7" width="18" height="18" rx="2" stroke="#0049B4" stroke-width="1.6"
|
||||
stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M12 12H20" stroke="#0049B4" stroke-width="1.6" stroke-linecap="round" />
|
||||
<path d="M12 16H20" stroke="#0049B4" stroke-width="1.6" stroke-linecap="round" />
|
||||
<path d="M12 20H16" stroke="#0049B4" stroke-width="1.6" stroke-linecap="round" />
|
||||
</svg> -->
|
||||
</svg> */-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
||||
class="bi bi-code" viewBox="0 0 16 16">
|
||||
<path
|
||||
@@ -138,13 +138,13 @@
|
||||
<!-- Step 4 -->
|
||||
<div class="signup-step">
|
||||
<div class="signup-step__icon-box four">
|
||||
<!-- <svg width="40" height="40" viewBox="0 0 32 32" fill="none"
|
||||
<!--/* <svg width="40" height="40" viewBox="0 0 32 32" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21 8H24V16" stroke="#0049B4" stroke-width="1.6" stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
<path d="M8 8H11V16" stroke="#0049B4" stroke-width="1.6" stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
</svg> -->
|
||||
</svg> */-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
||||
class="bi bi-display" viewBox="0 0 16 16">
|
||||
<path
|
||||
@@ -160,14 +160,14 @@
|
||||
<!-- Step 5 -->
|
||||
<div class="signup-step">
|
||||
<div class="signup-step__icon-box five">
|
||||
<!-- <svg width="40" height="40" viewBox="0 0 32 32" fill="none"-->
|
||||
<!--/* <svg width="40" height="40" viewBox="0 0 32 32" fill="none"*/-->
|
||||
<!-- xmlns="http://www.w3.org/2000/svg">-->
|
||||
<!-- <rect x="7" y="7" width="18" height="18" rx="2" stroke="#0049B4" stroke-width="1.6"-->
|
||||
<!--/* <rect x="7" y="7" width="18" height="18" rx="2" stroke="#0049B4" stroke-width="1.6"*/-->
|
||||
<!-- stroke-linecap="round" stroke-linejoin="round" />-->
|
||||
<!-- <path d="M12 11H20" stroke="#0049B4" stroke-width="1.6" stroke-linecap="round" />-->
|
||||
<!-- <path d="M12 15L14.5 17.5L20 12" stroke="#0049B4" stroke-width="1.6"-->
|
||||
<!--/* <path d="M12 11H20" stroke="#0049B4" stroke-width="1.6" stroke-linecap="round" />*/-->
|
||||
<!--/* <path d="M12 15L14.5 17.5L20 12" stroke="#0049B4" stroke-width="1.6"*/-->
|
||||
<!-- stroke-linecap="round" stroke-linejoin="round" />-->
|
||||
<!-- </svg>-->
|
||||
<!--/* </svg>*/-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
||||
class="bi bi-ui-checks-grid" viewBox="0 0 16 16">
|
||||
<path
|
||||
@@ -183,7 +183,7 @@
|
||||
<!-- Step 6 -->
|
||||
<div class="signup-step">
|
||||
<div class="signup-step__icon-box six">
|
||||
<!-- <svg width="40" height="40" viewBox="0 0 32 32" fill="none"
|
||||
<!--/* <svg width="40" height="40" viewBox="0 0 32 32" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="7" y="9" width="18" height="16" rx="2" stroke="#0049B4" stroke-width="1.6"
|
||||
stroke-linecap="round" stroke-linejoin="round" />
|
||||
@@ -192,7 +192,7 @@
|
||||
<path d="M20 7V11" stroke="#0049B4" stroke-width="1.6" stroke-linecap="round" />
|
||||
<path d="M12 17H14" stroke="#0049B4" stroke-width="1.6" stroke-linecap="round" />
|
||||
<path d="M12 21H14" stroke="#0049B4" stroke-width="1.6" stroke-linecap="round" />
|
||||
</svg> -->
|
||||
</svg> */-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
||||
class="bi bi-airplane" viewBox="0 0 16 16">
|
||||
<path
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
<span class="oauth2-2legged__prereq-num">3</span>
|
||||
<div class="oauth2-2legged__prereq-body">
|
||||
<h3 class="oauth2-2legged__prereq-title">Scope 확인</h3>
|
||||
<p>호출하려는 API에 필요한 scope</p>
|
||||
<p>scope 는 고정값 "api" 사용</p>
|
||||
<p>권한이 부여됐는지 확인합니다.</p>
|
||||
</div>
|
||||
</article>
|
||||
@@ -136,7 +136,7 @@
|
||||
<text x="560" y="102" text-anchor="middle" font-size="12" font-weight="700"
|
||||
fill="#0049b4">① POST /dj/oauth/token</text>
|
||||
<text x="560" y="118" text-anchor="middle" font-size="11" font-weight="500"
|
||||
fill="#64748B">grant_type=client_credentials, client_id, client_secret, scope</text>
|
||||
fill="#64748B">grant_type=client_credentials, client_id, client_secret, scope=api</text>
|
||||
<line x1="240" y1="128" x2="880" y2="128" stroke="#0049b4" stroke-width="2"
|
||||
marker-end="url(#o2leg-arrow-primary)" />
|
||||
|
||||
@@ -145,11 +145,11 @@
|
||||
<line x1="880" y1="170" x2="240" y2="170" stroke="#64748B" stroke-width="2"
|
||||
marker-end="url(#o2leg-arrow-gray)" />
|
||||
<text x="560" y="188" text-anchor="middle" font-size="11" font-weight="500"
|
||||
fill="#64748B">{ access_token, token_type:"bearer", expires_in:86400, scope, jti
|
||||
fill="#64748B">{ access_token, token_type:"bearer", expires_in:86400, scope:"api", jti
|
||||
}</text>
|
||||
|
||||
<text x="560" y="216" text-anchor="middle" font-size="12" font-weight="700"
|
||||
fill="#0049b4">③ GET /api/v1/... · Authorization: Bearer <access_token></text>
|
||||
fill="#0049b4">③ GET /api/v1/... · X-AUTH-TOKEN: Bearer <access_token></text>
|
||||
<line x1="240" y1="226" x2="880" y2="226" stroke="#0049b4" stroke-width="2"
|
||||
marker-end="url(#o2leg-arrow-primary)" />
|
||||
|
||||
@@ -210,9 +210,9 @@
|
||||
<tr>
|
||||
<td><code>scope</code></td>
|
||||
<td><span
|
||||
class="oauth2-2legged__req-badge oauth2-2legged__req-badge--optional">선택</span>
|
||||
class="oauth2-2legged__req-badge oauth2-2legged__req-badge--required">필수</span>
|
||||
</td>
|
||||
<td>호출 권한 범위 (공백 구분)</td>
|
||||
<td>고정값 "api"</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -228,7 +228,7 @@ curl -X <span class="o2leg-y">POST</span> <span class="o2leg-c">'https://openapi
|
||||
-d <span class="o2leg-c">'grant_type=client_credentials'</span> \
|
||||
-d <span class="o2leg-c">'client_id=YOUR_CLIENT_ID'</span> \
|
||||
-d <span class="o2leg-c">'client_secret=YOUR_CLIENT_SECRET'</span> \
|
||||
-d <span class="o2leg-c">'scope=read.accounts'</span>
|
||||
-d <span class="o2leg-c">'scope=api'</span>
|
||||
|
||||
<span class="o2leg-g"># 응답: 200 OK + JSON</span></pre>
|
||||
</div>
|
||||
@@ -248,7 +248,7 @@ curl -X <span class="o2leg-y">POST</span> <span class="o2leg-c">'https://openapi
|
||||
<span class="o2leg-c">"access_token"</span>: <span class="o2leg-y">"eyJhbGciOiJSUzI1NiJ9..."</span>,
|
||||
<span class="o2leg-c">"token_type"</span>: <span class="o2leg-y">"bearer"</span>,
|
||||
<span class="o2leg-c">"expires_in"</span>: <span class="o2leg-p">86400</span>,
|
||||
<span class="o2leg-c">"scope"</span>: <span class="o2leg-y">"read.accounts"</span>,
|
||||
<span class="o2leg-c">"scope"</span>: <span class="o2leg-y">"api"</span>,
|
||||
<span class="o2leg-c">"jti"</span>: <span class="o2leg-y">"f47ac10b-58cc-4372-..."</span>
|
||||
}
|
||||
|
||||
@@ -284,7 +284,7 @@ curl -X <span class="o2leg-y">POST</span> <span class="o2leg-c">'https://openapi
|
||||
<tr>
|
||||
<td><code class="oauth2-2legged__field-name">scope</code></td>
|
||||
<td>string</td>
|
||||
<td>실제 부여된 권한 범위</td>
|
||||
<td>부여된 권한 범위 (api)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code class="oauth2-2legged__field-name">jti</code></td>
|
||||
@@ -301,13 +301,13 @@ curl -X <span class="o2leg-y">POST</span> <span class="o2leg-c">'https://openapi
|
||||
<section class="oauth2-2legged__step" aria-labelledby="o2leg-step3-title">
|
||||
<span class="oauth2-2legged__eyebrow">STEP 3</span>
|
||||
<h2 class="oauth2-2legged__h2" id="o2leg-step3-title">발급 토큰으로 API 호출</h2>
|
||||
<p class="oauth2-2legged__desc">Authorization 헤더에 Bearer 토큰을 실어 보호 자원 API 를 호출합니다.</p>
|
||||
<p class="oauth2-2legged__desc">X-AUTH-TOKEN 헤더에 Bearer 토큰을 실어 보호 자원 API 를 호출합니다.</p>
|
||||
|
||||
<div class="oauth2-2legged__step-grid">
|
||||
<div class="oauth2-2legged__panel">
|
||||
<h3 class="oauth2-2legged__panel-title">필수 헤더</h3>
|
||||
<div class="oauth2-2legged__header-box">
|
||||
<code class="oauth2-2legged__header-key">Authorization:</code>
|
||||
<code class="oauth2-2legged__header-key">X-AUTH-TOKEN:</code>
|
||||
<code class="oauth2-2legged__header-value">Bearer eyJhbGciOiJSUzI1NiJ9...</code>
|
||||
</div>
|
||||
|
||||
@@ -316,8 +316,7 @@ curl -X <span class="o2leg-y">POST</span> <span class="o2leg-c">'https://openapi
|
||||
<li>동일 토큰은 expires_in(기본 86400초) 동안 재사용</li>
|
||||
<li>만료 임박 시 재발급 후 교체 (예: TTL의 80% 시점)</li>
|
||||
<li>매 호출마다 토큰을 새로 발급하지 마세요</li>
|
||||
<li>권한이 다른 API 는 scope 별로 토큰을 분리 발급</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="oauth2-2legged__code-panel">
|
||||
@@ -325,7 +324,7 @@ curl -X <span class="o2leg-y">POST</span> <span class="o2leg-c">'https://openapi
|
||||
<pre class="oauth2-2legged__code-block"><span class="o2leg-cm"># 보호 자원 API 호출</span>
|
||||
curl -X <span class="o2leg-y">GET</span> \
|
||||
<span class="o2leg-c">'https://openapi.djbank.co.kr/api/v1/accounts'</span> \
|
||||
-H <span class="o2leg-c">'Authorization: Bearer eyJhbGciOiJSUzI..'</span> \
|
||||
-H <span class="o2leg-c">'X-AUTH-TOKEN: Bearer eyJhbGciOiJSUzI..'</span> \
|
||||
-H <span class="o2leg-c">'Accept: application/json'</span>
|
||||
|
||||
<span class="o2leg-g"># 응답</span>
|
||||
|
||||
@@ -398,12 +398,12 @@ valid = constantTimeEquals(received, expected)</pre>
|
||||
<tr>
|
||||
<td><code>400</code></td>
|
||||
<td>필수 헤더 누락</td>
|
||||
<td>실패 기록 + 재시도</td>
|
||||
<td>실패 기록</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>401</code></td>
|
||||
<td>서명 불일치 / timestamp 만료</td>
|
||||
<td>실패 기록 + 재시도</td>
|
||||
<td>실패 기록</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>5xx</code> · 타임아웃</td>
|
||||
@@ -412,8 +412,9 @@ valid = constantTimeEquals(received, expected)</pre>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="oauth2-2legged__warning">⚠ 2xx 이외 응답과 네트워크 오류는 <strong>일정 간격을 두고
|
||||
<p class="oauth2-2legged__warning">⚠ 5xx 응답·타임아웃과 네트워크 오류는 <strong>일정 간격을 두고
|
||||
재시도</strong>됩니다(기본 3회). 재시도로 인한 중복 수신은 <code>eventId</code> 멱등 처리로 방어하세요.</p>
|
||||
>>>>>>> 9981459691c836bfd33b6b81a8e6aa22ca12446a
|
||||
|
||||
<h4 class="oauth2-2legged__panel-subtitle">응답 가이드</h4>
|
||||
<ul class="oauth2-2legged__tips">
|
||||
|
||||
@@ -246,7 +246,7 @@
|
||||
<div class="error-container">
|
||||
<!-- Error Icon -->
|
||||
<div class="error-icon">
|
||||
<!-- 3D Speech Bubble Error Icon - Base64 encoded or use th:src for server image -->
|
||||
<!--/* 3D Speech Bubble Error Icon - Base64 encoded or use th:src for server image */-->
|
||||
<img th:src="@{/img/error_3d.png}" alt="에러">
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
|
||||
<body>
|
||||
<!--
|
||||
<!--/*
|
||||
API 선택 공용 모듈 (figma s2 디자인: 카테고리 캐러셀 탭 + 카드그리드 + 플로팅 카트 + 선택목록 모달)
|
||||
|
||||
사용처: 앱(API Key) 신청/수정 step2, Webhook 신청/수정 step2
|
||||
@@ -25,7 +25,7 @@
|
||||
- 추가 hidden 필드는 호출 페이지에서 form="apiSelectorForm" 속성으로 주입(예: apikey 수정 clientId).
|
||||
- API 목록: GET /apis/for_request (ROLE_API_KEY_REQUEST) AJAX.
|
||||
- 스타일: design s2-* (_apikey-register.scss step2 재작업분) 재사용.
|
||||
-->
|
||||
*/-->
|
||||
<th:block th:fragment="apiSelector(apiServices, selectedApis, formAction, saveAction)">
|
||||
|
||||
<!-- Category Carousel Tab Container -->
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
<p class="pop_text l_text2">신청사유</p>
|
||||
<div class="pop_textbox">
|
||||
<textarea name="reason" rows="5" cols="30" class="common_textareaType_1" placeholder="신청사유를 입력해 주세요."></textarea>
|
||||
<!-- <span>158/1,000 byte</span> -->
|
||||
<!--/* <span>158/1,000 byte</span> */-->
|
||||
</div>
|
||||
<div class="pop_btnbox">
|
||||
<button class="popup_button_gray btn_cancel">취소</button>
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
<!doctype html>
|
||||
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
|
||||
|
||||
<aside layout:fragment="apiAside" class="lnb">
|
||||
<nav th:with="services=${@apiServiceService.searchApiGroupsForLnb()}">
|
||||
<ul class="lnb_list_type">
|
||||
<li>
|
||||
<a href="#none">공통안내</a>
|
||||
<ul class="lnb_list_nav">
|
||||
<li> <a th:href="@{/apis/common}">API 개발 공통</a></li>
|
||||
<li> <a th:href="@{/apis/KAPAP004U3}">가상계좌 응답 코드</a></li>
|
||||
<li> <a th:href="@{/apis/KAPAP004U4}">가상계좌 배치 설계</a></li>
|
||||
<li> <a th:href="@{/apis/KAPAP004U5}">가상계좌 VAN사 코드</a></li>
|
||||
<li> <a th:href="@{/apis/KAPAP004U6}">펌뱅킹 응답코드</a></li>
|
||||
<li> <a th:href="@{/apis/KAPAP004U7}">펌뱅킹 배치 설계</a></li>
|
||||
<li> <a th:href="@{/apis/KAPAP004U8}">대출금리 응답코드</a></li>
|
||||
<li> <a th:href="@{/apis/KAPAP004U9}">케이뱅크 페이 응답코드</a></li>
|
||||
<li> <a th:href="@{/apis/KAPAP004U10}">케이뱅크 페이 복합과세 예제</a></li>
|
||||
<li> <a th:href="@{/apis/token-spec}">케이뱅크 OAuth 2.0 토큰 발급</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li th:each="apiService : ${services}">
|
||||
<a href="#none" th:text="${apiService.groupName}">API Group Name</a>
|
||||
<ul class="lnb_list_nav">
|
||||
<li th:each="api : ${apiService.apiGroupApiList}">
|
||||
<a th:href="@{/apis/detail(id=${api.apiId})}" th:text="${api.apiDesc}">API Description</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
@@ -1,5 +1,6 @@
|
||||
<!doctype html>
|
||||
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
|
||||
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
|
||||
|
||||
<head th:fragment="headFragment">
|
||||
<title th:text="#{title.html}">개발자 포털</title>
|
||||
@@ -27,6 +28,23 @@
|
||||
<!-- CSRF 토큰 (세션 기반). 정적 JS/AJAX에서 토큰·헤더명을 읽어 사용한다. -->
|
||||
<meta name="_csrf" th:content="${_csrf != null ? _csrf.token : ''}"/>
|
||||
<meta name="_csrf_header" th:content="${_csrf != null ? _csrf.headerName : 'X-XSRF-TOKEN'}"/>
|
||||
|
||||
<!-- 익명(비로그인) 세션 keepalive: 로그인/회원가입 등에서 페이지에 머무는 동안 주기적 ping으로
|
||||
세션 비활성 타이머를 리셋해 세션 저장 CSRF 토큰·회원가입 본인인증 상태의 만료(10분)를 방지한다.
|
||||
탭을 닫으면 ping이 멈춰 정상 만료. 인증 사용자는 헤더의 세션 타이머/heartbeat가 대신 처리한다. -->
|
||||
<script sec:authorize="isAnonymous()" th:inline="javascript">
|
||||
(function () {
|
||||
var PING_URL = /*[[@{/api/session/ping}]]*/ '/api/session/ping';
|
||||
var PING_INTERVAL_MS = 4 * 60 * 1000; // 세션 타임아웃(10분)의 절반 이하
|
||||
setInterval(function () {
|
||||
try {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', PING_URL, true);
|
||||
xhr.send();
|
||||
} catch (ignore) { /* keepalive 실패는 화면 동작에 영향 없음 */ }
|
||||
}, PING_INTERVAL_MS);
|
||||
})();
|
||||
</script>
|
||||
<meta content="max-age=0, public" http-equiv="Cache-Control"/>
|
||||
<meta content="index, follow" name="robots"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
|
||||
@@ -100,9 +118,9 @@
|
||||
<script th:src="@{/plugins/summernote/summernote-cleaner.js}"></script>
|
||||
<script th:src="@{/plugins/jquery-ui/jquery-ui.min.js}"></script>
|
||||
|
||||
<!-- LiveReload (개발 전용): DevTools 가 정적 리소스/템플릿 변경을 감지하면 브라우저를 자동 새로고침한다.
|
||||
<!--/* LiveReload (개발 전용): DevTools 가 정적 리소스/템플릿 변경을 감지하면 브라우저를 자동 새로고침한다.
|
||||
localhost 외 IP(예: 172.30.1.14)로 접속해도 동작하도록 접속 호스트 기준으로 livereload.js 를 로드한다.
|
||||
prod/stage 에는 DevTools 자체가 없으므로(developmentOnly) 개발 프로파일에서만 주입한다. -->
|
||||
prod/stage 에는 DevTools 자체가 없으므로(developmentOnly) 개발 프로파일에서만 주입한다. */-->
|
||||
<script th:if="${@environment.acceptsProfiles('local_rinjaemac','local')}"
|
||||
th:src="|//${#request.serverName}:35729/livereload.js|"></script>
|
||||
</head>
|
||||
|
||||
@@ -21,12 +21,12 @@
|
||||
<!-- Desktop Header Layout -->
|
||||
<div class="desktop-header">
|
||||
<div class="header-left">
|
||||
<!-- <div class="logo-wrapper">-->
|
||||
<!-- <a th:href="@{/}" class="logo-link">-->
|
||||
<!-- <img src="/img/logo/logo-djb.png" alt="DJBank" class="logo" width="114" height="32">-->
|
||||
<!-- </a>-->
|
||||
<!-- <a th:href="@{/}" class="logo-text">API Portal</a>-->
|
||||
<!-- </div>-->
|
||||
<!--/* <div class="logo-wrapper">*/-->
|
||||
<!--/* <a th:href="@{/}" class="logo-link">*/-->
|
||||
<!--/* <img src="/img/logo/logo-djb.png" alt="DJBank" class="logo" width="114" height="32">*/-->
|
||||
<!--/* </a>*/-->
|
||||
<!--/* <a th:href="@{/}" class="logo-text">API Portal</a>*/-->
|
||||
<!--/* </div>*/-->
|
||||
<div >
|
||||
<div class="logo">
|
||||
<a th:href="@{/}" class="logo-wrapper">
|
||||
@@ -63,15 +63,15 @@
|
||||
<div class="header-right">
|
||||
|
||||
<!-- Login State (Anonymous) -->
|
||||
<!-- <div class="auth-group" sec:authorize="isAnonymous()">-->
|
||||
<!--/* <div class="auth-group" sec:authorize="isAnonymous()">*/-->
|
||||
|
||||
<!-- <a th:href="@{/login}" class="login-btn login-btn-box">-->
|
||||
<!-- <img th:src="@{/img/user_icon.svg}" alt="User" class="user-icon">로그인</a>-->
|
||||
<!-- </div>-->
|
||||
<!--/* <a th:href="@{/login}" class="login-btn login-btn-box">*/-->
|
||||
<!--/* <img th:src="@{/img/user_icon.svg}" alt="User" class="user-icon">로그인</a>*/-->
|
||||
<!--/* </div>*/-->
|
||||
|
||||
<div class="auth-group" sec:authorize="isAnonymous()">
|
||||
<a th:href="@{/login}" class="login-btn login-btn-box">로그인</a>
|
||||
<a href="#" class="btn-signup">회원가입</a>
|
||||
<a th:href="@{/signup}" class="btn-signup">회원가입</a>
|
||||
</div>
|
||||
|
||||
<!-- Logout State (Authenticated) -->
|
||||
@@ -284,7 +284,7 @@
|
||||
var FORCE_LOGOUT_URL = /*[[@{/login?forceLogout=true}]]*/ '/login?forceLogout=true';
|
||||
var LOGOUT_URL = /*[[@{/actionLogout.do}]]*/ '/actionLogout.do';
|
||||
|
||||
var timeoutMinutes = /*[[${sessionTimeoutMinutes}]]*/ 15;
|
||||
var timeoutMinutes = /*[[${sessionTimeoutMinutes}]]*/ 10;
|
||||
var remainingSeconds = timeoutMinutes * 60;
|
||||
var WARNING_SECONDS = 60; // 만료 60초 전 연장 확인 모달
|
||||
var POLL_INTERVAL_MS = 30000; // 서버 잔여시간 동기화 주기
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
<!doctype html>
|
||||
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
|
||||
|
||||
<head th:fragment="headFragment">
|
||||
<title th:text="#{title.html}">개발자 포털</title>
|
||||
|
||||
<meta content="https://www.eactive.co.kr/" property="og:url"/>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta content="max-age=0, public" http-equiv="Cache-Control"/>
|
||||
<meta content="index, follow" name="robots"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
|
||||
<meta http-equiv="Cache-Control" content="no-cache">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
|
||||
<meta http-equiv="Expires" content="0">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/style2.css}">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/common2.css}">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/slick.css}">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/daterangepicker.css}">
|
||||
|
||||
<link rel="stylesheet" th:href="@{/plugins/codemirror/codemirror.css}" type="text/css"/>
|
||||
<link rel="stylesheet" th:href="@{/plugins/codemirror/theme/monokai.css}" type="text/css"/>
|
||||
<link rel="stylesheet" th:href="@{/plugins/summernote/summernote-lite.css}" type="text/css" />
|
||||
<link rel="stylesheet" th:href="@{/plugins/jquery-ui/jquery-ui.min.css}" />
|
||||
|
||||
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
|
||||
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
|
||||
<script th:src="@{/plugins/jquery/jquery-3.7.1.min.js}"></script>
|
||||
<script th:src="@{/js/lodash.js}"></script>
|
||||
|
||||
<!-- Portal 설정값 전역 노출 -->
|
||||
<script th:inline="javascript">
|
||||
window.PORTAL_CONFIG = {
|
||||
file: {
|
||||
maxSize: /*[[${@portalProperties.file.maxSize}]]*/ '8MB',
|
||||
maxSizeBytes: /*[[${@portalProperties.file.maxSizeBytes}]]*/ 8388608,
|
||||
allowedExtensions: /*[[${@portalProperties.file.allowedExtensions}]]*/ 'pdf,doc,docx,xls,xlsx,ppt,pptx,hwp,gif,jpg,jpeg,png'
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<script th:src="@{/js/common.js}"></script>
|
||||
<script th:src="@{/js/moment.min.js}"></script>
|
||||
<script th:src="@{/js/daterangepicker.js}"></script>
|
||||
<script th:src="@{/plugins/codemirror/codemirror.js}"></script>
|
||||
<script th:src="@{/plugins/codemirror/mode/clike.js}"></script>
|
||||
<script th:src="@{/plugins/codemirror/mode/javascript.js}"></script>
|
||||
<script th:src="@{/plugins/codemirror/addon/display/fullscreen.js}"></script>
|
||||
<script th:src="@{/plugins/codemirror/addon/display/placeholder.js}"></script>
|
||||
<script th:src="@{/plugins/summernote/summernote-lite.min.js}"></script>
|
||||
<script th:src="@{/plugins/summernote/summernote-cleaner.js}"></script>
|
||||
<script th:src="@{/plugins/jquery-ui/jquery-ui.min.js}"></script>
|
||||
<script th:src="@{/js/htmx.min.js}"></script>
|
||||
</head>
|
||||
|
||||
</html>
|
||||
@@ -14,7 +14,8 @@
|
||||
</container-descriptor>
|
||||
|
||||
<session-descriptor>
|
||||
<timeout-secs>1800</timeout-secs>
|
||||
<!-- 세션 타임아웃 10분 고정 (application.yml server.servlet.session.timeout=10m 과 동일 값 유지) -->
|
||||
<timeout-secs>600</timeout-secs>
|
||||
<cookie-name>JSESSIONID_PORTAL</cookie-name>
|
||||
<persistent-store-type>replicated_if_clustered</persistent-store-type>
|
||||
</session-descriptor>
|
||||
|
||||
Reference in New Issue
Block a user