From 982187298c810b5eea9edf07724ea5abf0d399d3 Mon Sep 17 00:00:00 2001
From: Rinjae
Date: Mon, 7 Sep 2026 10:55:43 +0900
Subject: [PATCH] =?UTF-8?q?=EC=A0=95=EC=A0=81=20=EB=A6=AC=EC=86=8C?=
=?UTF-8?q?=EC=8A=A4=20=EC=84=9C=EB=B9=99=20=EB=B0=8F=20=EB=B9=8C=EB=93=9C?=
=?UTF-8?q?=20=ED=94=84=EB=A1=9C=EC=84=B8=EC=8A=A4=20=EA=B0=9C=EC=84=A0:?=
=?UTF-8?q?=20-=20OHS=20=ED=99=98=EA=B2=BD=20=EB=8C=80=EC=9D=91=20?=
=?UTF-8?q?=EC=A0=95=EC=A0=81=EC=9E=90=EC=9B=90=20=ED=95=B4=EC=8B=9C=20?=
=?UTF-8?q?=EB=B2=84=EC=A0=84=EB=8B=9D=20OFF=20=EC=84=A4=EC=A0=95=20?=
=?UTF-8?q?=EB=B0=98=EC=98=81=20-=20eapim-portal-static.zip=20=EC=83=9D?=
=?UTF-8?q?=EC=84=B1=20=EB=B0=8F=20CI/CD=20=EC=B6=94=EA=B0=80=20-=20OHS=20?=
=?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=83=98=ED=94=8C=20=EA=B0=80=EC=9D=B4?=
=?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Jenkinsfile.deploy | 3 +-
Jenkinsfile.test-build | 3 +-
build.gradle | 12 ++++
djb-docs/OHS-StaticResource-설정.md | 64 +++++++++++++++++++
.../PortalConfigWebDispatcherServlet.java | 35 ++++++----
src/main/resources/application-prod.yml | 10 +--
6 files changed, 110 insertions(+), 17 deletions(-)
create mode 100644 djb-docs/OHS-StaticResource-설정.md
diff --git a/Jenkinsfile.deploy b/Jenkinsfile.deploy
index 930f2ca..552586a 100644
--- a/Jenkinsfile.deploy
+++ b/Jenkinsfile.deploy
@@ -74,8 +74,9 @@ pipeline {
set -eu
cd build/libs
sha256sum eapim-portal.war > eapim-portal.war.sha256
+ sha256sum eapim-portal-static.zip > eapim-portal-static.zip.sha256
'''
- archiveArtifacts artifacts: 'build/libs/eapim-portal.war,build/libs/eapim-portal.war.sha256', fingerprint: true
+ archiveArtifacts artifacts: 'build/libs/eapim-portal.war,build/libs/eapim-portal.war.sha256,build/libs/eapim-portal-static.zip,build/libs/eapim-portal-static.zip.sha256', fingerprint: true
stash name: 'war', includes: 'build/libs/eapim-portal.war'
}
}
diff --git a/Jenkinsfile.test-build b/Jenkinsfile.test-build
index 2935c2e..a57fc43 100644
--- a/Jenkinsfile.test-build
+++ b/Jenkinsfile.test-build
@@ -96,8 +96,9 @@ pipeline {
sha256sum "$f" > "$f.sha256"
md5sum "$f" > "$f.md5"
done
+ sha256sum eapim-portal-static.zip > eapim-portal-static.zip.sha256
'''
- archiveArtifacts artifacts: 'build/libs/eapim-portal.war,build/libs/eapim-portal-boot.war,build/libs/eapim-portal.war.sha1,build/libs/eapim-portal.war.sha256,build/libs/eapim-portal.war.md5,build/libs/eapim-portal-boot.war.sha1,build/libs/eapim-portal-boot.war.sha256,build/libs/eapim-portal-boot.war.md5', fingerprint: true
+ archiveArtifacts artifacts: 'build/libs/eapim-portal.war,build/libs/eapim-portal-boot.war,build/libs/eapim-portal.war.sha1,build/libs/eapim-portal.war.sha256,build/libs/eapim-portal.war.md5,build/libs/eapim-portal-boot.war.sha1,build/libs/eapim-portal-boot.war.sha256,build/libs/eapim-portal-boot.war.md5,build/libs/eapim-portal-static.zip,build/libs/eapim-portal-static.zip.sha256', fingerprint: true
}
}
}
diff --git a/build.gradle b/build.gradle
index a021c9c..e367af4 100644
--- a/build.gradle
+++ b/build.gradle
@@ -315,6 +315,18 @@ war {
classpath = excludeLocalOnlyLibs(classpath)
}
+// 정적 리소스만 별도 zip (OHS 추가 배포용). war/bootWar 는 그대로 정적 리소스 포함 유지
+// (WAS 단독 접속 인원 지원). sass/ 는 컴파일 소스이며 실제 서빙 경로에 없어 제외
+// (PortalConfigWebDispatcherServlet#addResourceHandlers 기준).
+task staticResourcesZip(type: Zip) {
+ archiveFileName = "eapim-portal-static.zip"
+ destinationDirectory = file("$buildDir/libs")
+ from('src/main/resources/static') {
+ exclude 'sass/**'
+ }
+}
+assemble.dependsOn staticResourcesZip
+
task printSourceSets {
doLast {
sourceSets.each { srcSet ->
diff --git a/djb-docs/OHS-StaticResource-설정.md b/djb-docs/OHS-StaticResource-설정.md
new file mode 100644
index 0000000..9a2abcc
--- /dev/null
+++ b/djb-docs/OHS-StaticResource-설정.md
@@ -0,0 +1,64 @@
+## OHS Static Resource 설정 예시
+
+```apacheconf
+
+ ServerName weblogic-djb.rinjae.kr
+
+
+ SSLEngine on
+ SSLVerifyClient None
+ SSLProtocol TLSv1.2 TLSv1.3
+ SSLHonorCipherOrder on
+ SSLCipherSuite TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
+ SSLWallet "${ORACLE_INSTANCE}/config/fmwconfig/components/${COMPONENT_TYPE}/instances/${COMPONENT_NAME}/keystores/default"
+
+
+ SSLOptions +StdEnvVars
+
+
+
+ SSLOptions +StdEnvVars
+
+
+ BrowserMatch "MSIE [2-5]" \
+ nokeepalive ssl-unclean-shutdown \
+ downgrade-1.0 force-response-1.0
+
+
+ Header always set Strict-Transport-Security "max-age=63072000; preload; includeSubDomains"
+
+
+
+ Alias /css /ohs/static/eapim-portal/css
+ Alias /js /ohs/static/eapim-portal/js
+ Alias /img /ohs/static/eapim-portal/img
+ Alias /images /ohs/static/eapim-portal/images
+ Alias /webfonts /ohs/static/eapim-portal/webfonts
+ Alias /font /ohs/static/eapim-portal/font
+ Alias /html /ohs/static/eapim-portal/html
+ Alias /plugins /ohs/static/eapim-portal/plugins
+
+
+ Require all granted
+ Options -Indexes
+
+
+
+ Header set Cache-Control "no-cache"
+
+
+
+
+ DirectoryIndex disabled
+ SetHandler weblogic-handler
+ WebLogicHost 172.30.1.100
+ WebLogicPort 39130
+ DynamicServerList OFF
+ ConnectTimeoutSecs 10
+ ConnectRetrySecs 2
+ WLProxySSL ON
+
+
+
+
+```
\ No newline at end of file
diff --git a/src/main/java/com/eactive/apim/portal/config/PortalConfigWebDispatcherServlet.java b/src/main/java/com/eactive/apim/portal/config/PortalConfigWebDispatcherServlet.java
index 6a965f0..ae091ea 100644
--- a/src/main/java/com/eactive/apim/portal/config/PortalConfigWebDispatcherServlet.java
+++ b/src/main/java/com/eactive/apim/portal/config/PortalConfigWebDispatcherServlet.java
@@ -175,31 +175,44 @@ public class PortalConfigWebDispatcherServlet implements WebMvcConfigurer {
* {@code /css/main.css} 요청을 내용 해시가 포함된 {@code /css/main-.css} 로 매핑한다.
* 내용이 바뀌면 URL 이 바뀌므로 강제 새로고침 없이 브라우저 캐시가 무효화된다. Thymeleaf 의
* {@code @{/css/main.css}} 링크는 {@link #resourceUrlEncodingFilter()} 가 해시 URL 로 치환한다.
+ *
+ * 버전닝이 꺼져 있으면(prod 포함 — {@link #isResourceVersioningEnabled()} 참고) URL 이
+ * 고정이라 장기 {@code max-age} 캐시를 쓰면 배포 후에도 브라우저가 옛 내용을 계속 쓸 위험이 있다.
+ * 이 경우 {@code no-cache}(매 요청 {@code If-Modified-Since} 조건부 재검증, 변경 없으면 304)로
+ * 신선도를 보장한다 — OHS 가 정적 리소스를 직접 서빙해도 Apache 가 파일 mtime 기준으로 동일하게
+ * Last-Modified/조건부 GET 을 처리하므로 WAS 와 동작이 어긋나지 않는다
+ * (배경: djb-obsidian-docs/3000-테스트/15-Static리소스분리).
*/
private void addStaticResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) {
- // 캐싱 ON(prod) 이면 브라우저에 1일 캐시를, OFF(dev/local) 면 no-store 를 내려보낸다.
- // no-store 는 브라우저가 아예 저장하지 않으므로 강제 새로고침 없이 sass/JS 변경이 바로 보인다.
- CacheControl cacheControl = isResourceCachingEnabled()
- ? CacheControl.maxAge(1, TimeUnit.DAYS)
- : CacheControl.noStore();
+ boolean versioningOn = isResourceVersioningEnabled();
+ CacheControl cacheControl;
+ if (!isResourceCachingEnabled()) {
+ // no-store: 브라우저가 아예 저장하지 않으므로 강제 새로고침 없이 sass/JS 변경이 바로 보인다(dev/local).
+ cacheControl = CacheControl.noStore();
+ } else if (versioningOn) {
+ cacheControl = CacheControl.maxAge(1, TimeUnit.DAYS);
+ } else {
+ cacheControl = CacheControl.noCache();
+ }
ResourceChainRegistration chain = registry.addResourceHandler(pattern)
.addResourceLocations(locations)
.setCacheControl(cacheControl)
.resourceChain(isResourceCachingEnabled());
- if (isResourceVersioningEnabled()) {
+ if (versioningOn) {
chain.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
}
chain.addResolver(new PathResourceResolver());
}
/**
- * 정적자원 해시 버전닝 활성 여부. prod 프로파일은 토글과 무관하게 항상 ON,
- * 그 외 프로파일은 {@code app.resource-versioning.enabled} 값을 따른다.
+ * 정적자원 해시 버전닝 활성 여부. {@code app.resource-versioning.enabled} 값을 그대로 따른다.
+ *
+ * 과거 prod 프로파일은 이 토글과 무관하게 항상 ON 이었으나, OHS 가 정적 리소스를 직접
+ * 서빙하는 구조로 바뀌면서 OFF 로 전환했다 — 해시가 붙은 URL(main-<hash>.css)이 OHS
+ * 디스크의 무해시 원본과 어긋나 404 가 날 수 있어서다(prod.yml 에서 명시적으로 false 설정).
+ * 대신 캐시 신선도는 {@link #addStaticResourceHandler} 의 no-cache 조건부 재검증으로 보장한다.
*/
private boolean isResourceVersioningEnabled() {
- if (environment.acceptsProfiles(Profiles.of("prod"))) {
- return true;
- }
return resourceVersioningEnabled;
}
diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml
index 58703ce..fb510a3 100644
--- a/src/main/resources/application-prod.yml
+++ b/src/main/resources/application-prod.yml
@@ -9,9 +9,11 @@ spring:
cachecontrol:
max-age: 86400
public: true
- # 정적자원 콘텐츠 해시 버전닝은 PortalConfigWebDispatcherServlet 가 prod 프로파일에서
- # 항상 ON 으로 수행한다(app.resource-versioning.enabled 토글 무시).
- # 해시 URL + 장기 캐시(max-age 86400)로 배포 시 자동 캐시 무효화.
+ # 정적자원 콘텐츠 해시 버전닝: OHS 가 정적 리소스(css/js/img 등)를 직접 서빙하는 구조에서는
+ # 해시 URL(main-.css)이 OHS 디스크의 무해시 원본과 어긋나 404 위험이 있어 OFF 로 전환했다
+ # (아래 app.resource-versioning.enabled: false, PortalConfigWebDispatcherServlet 참고).
+ # 캐시 무효화는 대신 no-cache(매 요청 If-Modified-Since 조건부 재검증)로 보장한다.
+ # 배경: djb-obsidian-docs/3000-테스트/15-Static리소스분리
jpa:
properties:
hibernate:
@@ -28,7 +30,7 @@ spring:
app:
resource-versioning:
- enabled: true
+ enabled: false
resource-caching:
enabled: true