Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2bbd3824ed | |||
| 4ff3589557 | |||
| db2b34a355 | |||
| d79bcf8430 | |||
| e049420ca3 | |||
| 10bbd57c78 | |||
| 70c636d719 | |||
| 1b37fc5e1b | |||
| 9a730dc0b7 | |||
| 0ff2259f78 | |||
| 8231318f14 | |||
| c44b331a32 | |||
| 88f774e6b5 | |||
| a77f4d4ef0 | |||
| 73a50e5db4 | |||
| ebf3cfa98d |
@@ -5,6 +5,7 @@ gradle.properties
|
|||||||
|
|
||||||
src/main/generated
|
src/main/generated
|
||||||
WebContent/generated
|
WebContent/generated
|
||||||
|
src/main/resources/version.info
|
||||||
|
|
||||||
# Eclipse #
|
# Eclipse #
|
||||||
.metadata
|
.metadata
|
||||||
|
|||||||
Vendored
+15
@@ -73,6 +73,21 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SBOM(CycloneDX) -> xlsx. 산출물 eapim-online-sbom.xlsx 를 아티팩트로 보관.
|
||||||
|
// 실패해도 배포는 진행하도록 UNSTABLE 로만 표시한다.
|
||||||
|
stage('SBOM') {
|
||||||
|
steps {
|
||||||
|
catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
|
||||||
|
sh 'gradle sbomXlsx --no-daemon -Pprofile=weblogic'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
archiveArtifacts allowEmptyArchive: true, artifacts: 'build/reports/sbom/*.xlsx', fingerprint: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ plugins {
|
|||||||
id 'groovy'
|
id 'groovy'
|
||||||
id 'war'
|
id 'war'
|
||||||
id 'com.diffplug.eclipse.apt' version '3.41.1'
|
id 'com.diffplug.eclipse.apt' version '3.41.1'
|
||||||
|
id 'org.cyclonedx.bom' version '3.2.4'
|
||||||
}
|
}
|
||||||
|
|
||||||
group 'com.eactvie'
|
group 'com.eactvie'
|
||||||
@@ -172,6 +173,54 @@ task initDirs() {
|
|||||||
file(generatedJavaDir).mkdirs()
|
file(generatedJavaDir).mkdirs()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// version.info 를 classpath 리소스로 생성해 WAR의 WEB-INF/classes에 포함시킨다.
|
||||||
|
// elink-online-common 등 공유 라이브러리 모듈이 아니라, 실제 배포 아티팩트(eapim-online.war)를
|
||||||
|
// 만드는 이 루트 프로젝트에서 생성해야 "지금 배포된 게이트웨이가 정확히 어느 커밋인지"를
|
||||||
|
// eapim-online 소스 변경 여부와 무관하게 항상 최신으로 반영한다.
|
||||||
|
// ToolsController(/manage/tools/version, elink-online-common 모듈)는 이 파일을
|
||||||
|
// Class.getResourceAsStream("/version.info")로 읽는데, WAR는 WEB-INF/classes와
|
||||||
|
// WEB-INF/lib 전체가 하나의 클래스로더를 공유하므로 어느 모듈의 클래스에서 조회하든 찾을 수 있다.
|
||||||
|
//
|
||||||
|
// elink-online-common/elink-online-core/... 는 별도 저장소를 참조하는 git submodule이라
|
||||||
|
// 루트(eapim-online)의 describe/dirty 만으로는 "어느 서브모듈이 바뀌었는지"를 알 수 없다
|
||||||
|
// (서브모듈 포인터가 커밋되고 나면 루트는 다시 clean 해져서 -dirty 흔적도 사라진다).
|
||||||
|
// 그래서 서브모듈 각각에 대해서도 describe를 실행해 module.<이름>=<결과> 형식으로 함께 기록한다.
|
||||||
|
def describeGit(File dir) {
|
||||||
|
try {
|
||||||
|
def proc = "git describe --tags --always --dirty".execute(null, dir)
|
||||||
|
// proc.text는 JVM/OS 기본 charset(Windows 환경에선 CP949 등)으로 stdout을 디코딩한다.
|
||||||
|
// git 태그명은 UTF-8 바이트이므로 기본 charset이 UTF-8이 아니면 여기서 한글이 깨진다.
|
||||||
|
// stream을 명시적으로 UTF-8로 읽어서 이 문제를 피한다.
|
||||||
|
def output = proc.inputStream.getText("UTF-8")
|
||||||
|
proc.waitFor()
|
||||||
|
return (proc.exitValue() == 0) ? output.trim() : "unknown"
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("${dir} git describe 실행 불가, unknown 으로 대체: ${e.message}")
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task generateVersionInfo {
|
||||||
|
doLast {
|
||||||
|
def gitVersion = describeGit(projectDir)
|
||||||
|
|
||||||
|
def sb = new StringBuilder()
|
||||||
|
sb.append("version=${gitVersion}\n")
|
||||||
|
sb.append("buildTime=${new Date().format("yyyy-MM-dd HH:mm:ss")}\n")
|
||||||
|
subprojects.sort { it.name }.each { sub ->
|
||||||
|
if (file("${sub.projectDir}/.git").exists()) {
|
||||||
|
sb.append("module.${sub.name}=${describeGit(sub.projectDir)}\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def versionInfoFile = file("$projectDir/src/main/resources/version.info")
|
||||||
|
versionInfoFile.parentFile.mkdirs()
|
||||||
|
versionInfoFile.write(sb.toString(), "UTF-8")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processResources.dependsOn generateVersionInfo
|
||||||
|
|
||||||
eclipse {
|
eclipse {
|
||||||
wtp {
|
wtp {
|
||||||
component {
|
component {
|
||||||
@@ -201,3 +250,6 @@ eclipse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// CycloneDX SBOM -> xlsx 변환 (gradle sbomXlsx)
|
||||||
|
apply from: "$projectDir/gradle/sbom-xlsx.gradle"
|
||||||
|
|||||||
+1
-1
Submodule elink-online-common updated: a923ff3f9f...4813a2ef0e
+1
-1
Submodule elink-online-core updated: 2929fc753f...495241c5d0
+1
-1
Submodule elink-online-core-jpa updated: 9620845daf...138621f2d0
+1
-1
Submodule elink-online-transformer updated: a1d822c2d7...375f0e0f15
@@ -0,0 +1,326 @@
|
|||||||
|
/*
|
||||||
|
* CycloneDX SBOM(bom.json) -> Excel(xlsx) 변환 태스크.
|
||||||
|
*
|
||||||
|
* gradle sbomXlsx # cyclonedxBom 실행 후 변환
|
||||||
|
* gradle sbomXlsx -PsbomJson=path.json # 기존 bom.json 사용(cyclonedxBom 생략)
|
||||||
|
* gradle sbomXlsx -PsbomOut=out.xlsx # 출력 경로 지정
|
||||||
|
*
|
||||||
|
* buildscript 블록이 이 스크립트에만 적용되므로 POI 의존성이 메인 빌드
|
||||||
|
* classpath 나 WAR 산출물에는 포함되지 않는다.
|
||||||
|
*
|
||||||
|
* 시트: 요약 / WAR 기준
|
||||||
|
* 산출 기준은 war 태스크의 classpath(= runtimeClasspath) 이므로
|
||||||
|
* test·annotationProcessor·developmentOnly·compileOnly 의존은 모두 제외된다.
|
||||||
|
* bom.json 은 라이선스/해시/설명/직접-전이 판별을 위한 메타 소스로만 쓴다.
|
||||||
|
*/
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
url "https://nexus.eactive.synology.me:8090/repository/maven-public/"
|
||||||
|
allowInsecureProtocol = true
|
||||||
|
}
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath 'org.apache.poi:poi-ooxml:3.17'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
import groovy.json.JsonSlurper
|
||||||
|
import org.apache.poi.ss.usermodel.BorderStyle
|
||||||
|
import org.apache.poi.ss.usermodel.FillPatternType
|
||||||
|
import org.apache.poi.ss.usermodel.HorizontalAlignment
|
||||||
|
import org.apache.poi.ss.usermodel.IndexedColors
|
||||||
|
import org.apache.poi.ss.usermodel.VerticalAlignment
|
||||||
|
import org.apache.poi.ss.util.CellRangeAddress
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook
|
||||||
|
|
||||||
|
// 엑셀 셀 문자열 상한(32767)보다 여유를 둔 절단 길이
|
||||||
|
ext.SBOM_CELL_LIMIT = 32000
|
||||||
|
|
||||||
|
task sbomXlsx {
|
||||||
|
group = 'sbom'
|
||||||
|
description = 'CycloneDX bom.json 을 WAR 수록 기준 xlsx 로 변환한다'
|
||||||
|
|
||||||
|
// -PsbomJson 으로 기존 산출물을 지정하면 재생성하지 않는다
|
||||||
|
if (!project.hasProperty('sbomJson')) {
|
||||||
|
dependsOn 'cyclonedxBom'
|
||||||
|
}
|
||||||
|
|
||||||
|
doLast {
|
||||||
|
File src = resolveBomJson(project)
|
||||||
|
File out = project.hasProperty('sbomOut')
|
||||||
|
? project.file(project.property('sbomOut'))
|
||||||
|
: new File(project.buildDir, "reports/sbom/${sbomFileName(project)}")
|
||||||
|
out.parentFile.mkdirs()
|
||||||
|
|
||||||
|
def bom = new JsonSlurper().parse(src, 'UTF-8')
|
||||||
|
def deploy = collectDeployJars(project)
|
||||||
|
def warRows = joinWarRows(deploy.jars, indexComponents(bom))
|
||||||
|
|
||||||
|
def wb = new XSSFWorkbook()
|
||||||
|
def st = createStyles(wb)
|
||||||
|
writeSummarySheet(wb, st, bom, warRows, src, deploy.label)
|
||||||
|
writeWarSheet(wb, st, warRows)
|
||||||
|
|
||||||
|
out.withOutputStream { os -> wb.write(os) }
|
||||||
|
wb.close()
|
||||||
|
|
||||||
|
int unmatched = warRows.count { it.matched == 'N' }
|
||||||
|
logger.lifecycle("SBOM xlsx 생성: ${out.absolutePath} " +
|
||||||
|
"(배포 수록 ${warRows.size()}개, SBOM 미매칭 ${unmatched}개, 원본 ${src.name})")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 산출 파일명: 배포 패키지명 기준 (war 있으면 war 파일명, 없으면 project 이름[-버전]) */
|
||||||
|
String sbomFileName(Project p) {
|
||||||
|
def warTask = p.tasks.findByName('war')
|
||||||
|
if (warTask != null) {
|
||||||
|
String archive = warTask.archiveFileName.get()
|
||||||
|
return archive.replaceAll(/\.(war|jar|ear)$/, '') + '-sbom.xlsx'
|
||||||
|
}
|
||||||
|
String ver = (p.version == null || p.version.toString() in ['', 'unspecified']) ? '' : "-${p.version}"
|
||||||
|
return "${p.name}${ver}-sbom.xlsx"
|
||||||
|
}
|
||||||
|
|
||||||
|
/** bom.json 위치 결정: -PsbomJson > cyclonedxBom 산출 경로 후보 */
|
||||||
|
File resolveBomJson(Project p) {
|
||||||
|
if (p.hasProperty('sbomJson')) {
|
||||||
|
File f = p.file(p.property('sbomJson'))
|
||||||
|
if (!f.exists()) {
|
||||||
|
throw new GradleException("bom.json 없음: ${f.absolutePath}")
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
def candidates = [
|
||||||
|
new File(p.buildDir, 'reports/cyclonedx/bom.json'),
|
||||||
|
new File(p.buildDir, 'reports/bom.json'),
|
||||||
|
]
|
||||||
|
File found = candidates.find { it.exists() }
|
||||||
|
if (found == null) {
|
||||||
|
throw new GradleException(
|
||||||
|
"bom.json 을 찾지 못했다. 확인한 경로: " + candidates*.absolutePath.join(', ') +
|
||||||
|
"\n'gradle cyclonedxBom' 실행 후 재시도하거나 -PsbomJson=<경로> 로 지정한다.")
|
||||||
|
}
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 실제 배포물에 packaging 되는 jar 목록.
|
||||||
|
* war 프로젝트는 war 태스크 classpath(= runtimeClasspath), 그 외는 runtimeClasspath 를
|
||||||
|
* 기준으로 하므로 test/annotationProcessor/developmentOnly/compileOnly 는 자동으로 빠진다.
|
||||||
|
*
|
||||||
|
* @return [label: 기준 설명, jars: 행 목록]
|
||||||
|
*/
|
||||||
|
Map collectDeployJars(Project p) {
|
||||||
|
def cfg = p.configurations.findByName('runtimeClasspath')
|
||||||
|
if (cfg == null) {
|
||||||
|
p.logger.warn("[${p.name}] runtimeClasspath 가 없어 배포 기준 시트를 비운다")
|
||||||
|
return [label: '(없음)', jars: []]
|
||||||
|
}
|
||||||
|
|
||||||
|
def warTask = p.tasks.findByName('war')
|
||||||
|
def files
|
||||||
|
String label
|
||||||
|
if (warTask != null) {
|
||||||
|
files = warTask.classpath.files
|
||||||
|
label = 'WAR WEB-INF/lib (war 태스크 classpath)'
|
||||||
|
} else {
|
||||||
|
files = cfg.files
|
||||||
|
label = 'runtimeClasspath (war 태스크 없음)'
|
||||||
|
}
|
||||||
|
|
||||||
|
def coordByFile = [:]
|
||||||
|
cfg.resolvedConfiguration.resolvedArtifacts.each { a ->
|
||||||
|
def id = a.moduleVersion.id
|
||||||
|
coordByFile[a.file] = [group: id.group, name: id.name, version: id.version]
|
||||||
|
}
|
||||||
|
def jars = files.findAll { it.name.endsWith('.jar') }.collect { f ->
|
||||||
|
def c = coordByFile[f]
|
||||||
|
[
|
||||||
|
file : f.name,
|
||||||
|
group : c?.group ?: '',
|
||||||
|
name : c?.name ?: f.name.replaceAll(/\.jar$/, ''),
|
||||||
|
version: c?.version ?: '',
|
||||||
|
coord : c ? "${c.group}:${c.name}:${c.version}".toString() : '',
|
||||||
|
]
|
||||||
|
}.sort { it.file }
|
||||||
|
|
||||||
|
return [label: label, jars: jars]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** bom.json 컴포넌트를 'group:name:version' 키로 색인 (라이선스/해시/설명/직접-전이) */
|
||||||
|
Map indexComponents(bom) {
|
||||||
|
String rootRef = bom.metadata?.component?.'bom-ref'
|
||||||
|
Set directRefs = (bom.dependencies?.find { it.ref == rootRef }?.dependsOn ?: []) as Set
|
||||||
|
|
||||||
|
def index = [:]
|
||||||
|
bom.components?.each { c ->
|
||||||
|
def hashes = [:]
|
||||||
|
c.hashes?.each { h -> hashes[h.alg] = h.content }
|
||||||
|
|
||||||
|
def licenses = (c.licenses ?: []).collect { l ->
|
||||||
|
l.license?.id ?: l.license?.name ?: l.expression ?: ''
|
||||||
|
}.findAll { it }
|
||||||
|
|
||||||
|
index["${c.group ?: ''}:${c.name ?: ''}:${c.version ?: ''}".toString()] = [
|
||||||
|
direct : directRefs.contains(c.'bom-ref') ? '직접' : '전이',
|
||||||
|
licenses : licenses.join('; '),
|
||||||
|
licenseList: licenses.isEmpty() ? ['(미상)'] : licenses,
|
||||||
|
purl : c.purl ?: '',
|
||||||
|
sha256 : hashes['SHA-256'] ?: '',
|
||||||
|
sha1 : hashes['SHA-1'] ?: '',
|
||||||
|
description: c.description ?: '',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
|
/** WAR jar 목록에 SBOM 메타를 좌표로 결합 */
|
||||||
|
List joinWarRows(List warJars, Map index) {
|
||||||
|
def result = []
|
||||||
|
warJars.eachWithIndex { j, i ->
|
||||||
|
def m = j.coord ? index[j.coord] : null
|
||||||
|
result << [
|
||||||
|
no : i + 1,
|
||||||
|
file : j.file,
|
||||||
|
group : j.group,
|
||||||
|
name : j.name,
|
||||||
|
version : j.version,
|
||||||
|
direct : m?.direct ?: '',
|
||||||
|
licenses : m?.licenses ?: '',
|
||||||
|
licenseList: m?.licenseList ?: ['(미상)'],
|
||||||
|
purl : m?.purl ?: '',
|
||||||
|
sha256 : m?.sha256 ?: '',
|
||||||
|
sha1 : m?.sha1 ?: '',
|
||||||
|
matched : (m != null) ? 'Y' : 'N',
|
||||||
|
description: m?.description ?: '',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
Map createStyles(wb) {
|
||||||
|
def headFont = wb.createFont()
|
||||||
|
headFont.setBold(true)
|
||||||
|
headFont.setColor(IndexedColors.WHITE.getIndex())
|
||||||
|
|
||||||
|
def head = wb.createCellStyle()
|
||||||
|
head.setFont(headFont)
|
||||||
|
head.setFillForegroundColor(IndexedColors.DARK_BLUE.getIndex())
|
||||||
|
head.setFillPattern(FillPatternType.SOLID_FOREGROUND)
|
||||||
|
head.setAlignment(HorizontalAlignment.CENTER)
|
||||||
|
head.setVerticalAlignment(VerticalAlignment.CENTER)
|
||||||
|
head.setBorderBottom(BorderStyle.THIN)
|
||||||
|
|
||||||
|
def body = wb.createCellStyle()
|
||||||
|
body.setVerticalAlignment(VerticalAlignment.TOP)
|
||||||
|
|
||||||
|
def wrap = wb.createCellStyle()
|
||||||
|
wrap.setVerticalAlignment(VerticalAlignment.TOP)
|
||||||
|
wrap.setWrapText(true)
|
||||||
|
|
||||||
|
def labelFont = wb.createFont()
|
||||||
|
labelFont.setBold(true)
|
||||||
|
def label = wb.createCellStyle()
|
||||||
|
label.setFont(labelFont)
|
||||||
|
|
||||||
|
return [head: head, body: body, wrap: wrap, label: label]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 헤더 행 생성 + 폭 지정 + 틀고정 */
|
||||||
|
def writeHeader(sheet, style, List<String> headers, List<Integer> widths) {
|
||||||
|
def row = sheet.createRow(0)
|
||||||
|
row.setHeightInPoints(20f)
|
||||||
|
headers.eachWithIndex { h, i ->
|
||||||
|
def cell = row.createCell(i)
|
||||||
|
cell.setCellValue(h)
|
||||||
|
cell.setCellStyle(style)
|
||||||
|
sheet.setColumnWidth(i, widths[i] * 256)
|
||||||
|
}
|
||||||
|
sheet.createFreezePane(0, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
def cellOf(row, int idx, value, style) {
|
||||||
|
def cell = row.createCell(idx)
|
||||||
|
String s = (value == null) ? '' : value.toString()
|
||||||
|
if (s.length() > SBOM_CELL_LIMIT) {
|
||||||
|
s = s.substring(0, SBOM_CELL_LIMIT) + '…(생략)'
|
||||||
|
}
|
||||||
|
cell.setCellValue(s)
|
||||||
|
cell.setCellStyle(style)
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
def writeSummarySheet(wb, st, bom, List warRows, File src, String basisLabel) {
|
||||||
|
def sheet = wb.createSheet('요약')
|
||||||
|
def comp = bom.metadata?.component ?: [:]
|
||||||
|
def tool = bom.metadata?.tools?.components?.getAt(0)
|
||||||
|
Set licenseKinds = warRows.collectMany { it.licenseList } as Set
|
||||||
|
|
||||||
|
def items = [
|
||||||
|
['대상 프로젝트', "${comp.group ?: ''}:${comp.name ?: ''}:${comp.version ?: ''}"],
|
||||||
|
['산출 기준', "${basisLabel} — test/annotationProcessor/compileOnly 제외"],
|
||||||
|
['BOM 포맷', "${bom.bomFormat ?: ''} ${bom.specVersion ?: ''}"],
|
||||||
|
['serialNumber', bom.serialNumber ?: ''],
|
||||||
|
['생성 시각', bom.metadata?.timestamp ?: ''],
|
||||||
|
['생성 도구', tool ? "${tool.name} ${tool.version}" : ''],
|
||||||
|
['원본 파일', src.absolutePath],
|
||||||
|
['배포 수록 jar', warRows.size()],
|
||||||
|
[' └ 직접 의존', warRows.count { it.direct == '직접' }],
|
||||||
|
[' └ 전이 의존', warRows.count { it.direct == '전이' }],
|
||||||
|
[' └ SBOM 미매칭', warRows.count { it.matched == 'N' }],
|
||||||
|
['라이선스 종류', licenseKinds.size()],
|
||||||
|
['라이선스 미상', warRows.count { it.licenses.isEmpty() }],
|
||||||
|
]
|
||||||
|
|
||||||
|
writeHeader(sheet, st.head, ['항목', '값'], [30, 90])
|
||||||
|
items.eachWithIndex { item, i ->
|
||||||
|
def row = sheet.createRow(i + 1)
|
||||||
|
cellOf(row, 0, item[0], st.label)
|
||||||
|
cellOf(row, 1, item[1], st.body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 라이선스 분포 (요약 하단)
|
||||||
|
def byLicense = [:].withDefault { 0 }
|
||||||
|
warRows.each { r -> r.licenseList.each { lic -> byLicense[lic] = byLicense[lic] + 1 } }
|
||||||
|
def sorted = byLicense.entrySet().sort { a, b -> (b.value <=> a.value) ?: (a.key <=> b.key) }
|
||||||
|
|
||||||
|
int base = items.size() + 2
|
||||||
|
def hdr = sheet.createRow(base)
|
||||||
|
cellOf(hdr, 0, '라이선스', st.head)
|
||||||
|
cellOf(hdr, 1, 'jar 수', st.head)
|
||||||
|
sorted.eachWithIndex { e, i ->
|
||||||
|
def row = sheet.createRow(base + 1 + i)
|
||||||
|
cellOf(row, 0, e.key, st.body)
|
||||||
|
cellOf(row, 1, e.value, st.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 실제 배포물(WAR WEB-INF/lib) 기준 시트 */
|
||||||
|
def writeWarSheet(wb, st, List warRows) {
|
||||||
|
def sheet = wb.createSheet('WAR 기준')
|
||||||
|
def headers = ['No', 'jar 파일명', 'Group', 'Name', 'Version', '구분',
|
||||||
|
'License', 'purl', 'SHA-256', 'SHA-1', 'SBOM매칭', 'Description']
|
||||||
|
def widths = [6, 46, 32, 34, 16, 7, 30, 60, 40, 30, 10, 60]
|
||||||
|
writeHeader(sheet, st.head, headers, widths)
|
||||||
|
|
||||||
|
warRows.eachWithIndex { r, i ->
|
||||||
|
def row = sheet.createRow(i + 1)
|
||||||
|
cellOf(row, 0, r.no, st.body)
|
||||||
|
cellOf(row, 1, r.file, st.body)
|
||||||
|
cellOf(row, 2, r.group, st.body)
|
||||||
|
cellOf(row, 3, r.name, st.body)
|
||||||
|
cellOf(row, 4, r.version, st.body)
|
||||||
|
cellOf(row, 5, r.direct, st.body)
|
||||||
|
cellOf(row, 6, r.licenses, st.body)
|
||||||
|
cellOf(row, 7, r.purl, st.body)
|
||||||
|
cellOf(row, 8, r.sha256, st.body)
|
||||||
|
cellOf(row, 9, r.sha1, st.body)
|
||||||
|
cellOf(row, 10, r.matched, st.body)
|
||||||
|
cellOf(row, 11, r.description, st.wrap)
|
||||||
|
}
|
||||||
|
if (!warRows.isEmpty()) {
|
||||||
|
sheet.setAutoFilter(new CellRangeAddress(0, warRows.size(), 0, headers.size() - 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,6 +51,7 @@ public class DJBApiAdapterController implements HttpAdapterServiceKey {
|
|||||||
@Autowired
|
@Autowired
|
||||||
DJBApiAdapterService service;
|
DJBApiAdapterService service;
|
||||||
|
|
||||||
|
EAIServerManager eaiServerManager;
|
||||||
/**
|
/**
|
||||||
* DJErp OAuth 조기 적용을 위한 Controller
|
* DJErp OAuth 조기 적용을 위한 Controller
|
||||||
*
|
*
|
||||||
@@ -67,7 +68,7 @@ public class DJBApiAdapterController implements HttpAdapterServiceKey {
|
|||||||
"/tsb/{path:^(?!oauth).*$}", "/tsb/{path:^(?!oauth).*$}/**",
|
"/tsb/{path:^(?!oauth).*$}", "/tsb/{path:^(?!oauth).*$}/**",
|
||||||
"/shb/{path:^(?!oauth).*$}", "/shb/{path:^(?!oauth).*$}/**",
|
"/shb/{path:^(?!oauth).*$}", "/shb/{path:^(?!oauth).*$}/**",
|
||||||
"/tst/{path:^(?!oauth).*$}", "/tst/{path:^(?!oauth).*$}/**",
|
"/tst/{path:^(?!oauth).*$}", "/tst/{path:^(?!oauth).*$}/**",
|
||||||
"/**/{path:^(?!oauth).*$}", "/**/{path:^(?!oauth).*$}/**",
|
"/**/{path:^(?!oauth)(?!favicon\\.ico$).*$}", "/**/{path:^(?!oauth)(?!favicon\\.ico$).*$}/**",
|
||||||
})
|
})
|
||||||
public ResponseEntity<String> callApi(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
|
public ResponseEntity<String> callApi(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
@@ -83,17 +84,31 @@ public class DJBApiAdapterController implements HttpAdapterServiceKey {
|
|||||||
logger.debug("ApiAdapterController] service request uri : " + servletRequest.getRequestURI());
|
logger.debug("ApiAdapterController] service request uri : " + servletRequest.getRequestURI());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (adptUri == null) {
|
|
||||||
logError(servletRequest);
|
|
||||||
String errorMsg = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_SERVICE_NOT_FOUND,
|
|
||||||
"can not find Adapter Uri");
|
|
||||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).contentType(MediaType.APPLICATION_JSON).body(errorMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Received time , jwhong
|
// Received time , jwhong
|
||||||
long receivedTimeMillis = System.currentTimeMillis(); // 밀리세컨 단위로 보내야함
|
long receivedTimeMillis = System.currentTimeMillis(); // 밀리세컨 단위로 보내야함
|
||||||
String receivedTimeStr = String.valueOf(receivedTimeMillis);
|
String receivedTimeStr = String.valueOf(receivedTimeMillis);
|
||||||
|
|
||||||
|
ResponseEntity<String> responseEntity = null;
|
||||||
|
Properties transactionProp = new Properties();
|
||||||
|
|
||||||
|
if(eaiServerManager == null)
|
||||||
|
eaiServerManager = EAIServerManager.getInstance();
|
||||||
|
|
||||||
|
String instid = eaiServerManager.getGroupInstId();
|
||||||
|
String uuid = instid+UUIDGenerator.getUUID().toString().replaceAll("-", "");
|
||||||
|
|
||||||
|
transactionProp.setProperty(TransactionContextKeys.TRANSACTION_UUID, uuid);
|
||||||
|
transactionProp.put(INBOUND_REQUESTED_TIME, receivedTimeStr);
|
||||||
|
|
||||||
|
if (adptUri == null) {
|
||||||
|
//logError(servletRequest);
|
||||||
|
String errorMsg = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_SERVICE_NOT_FOUND,
|
||||||
|
"can not find Adapter Uri");
|
||||||
|
logError(servletRequest, "HTTP_IN_NO_URI", MessageUtil.ERROR_CODE_SERVICE_NOT_FOUND,
|
||||||
|
errorMsg, transactionProp, null);
|
||||||
|
return ResponseEntity.status(HttpStatus.NOT_FOUND).contentType(MediaType.APPLICATION_JSON).body(errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
String adapterGroupName = adptUri.getAdptGrpName();
|
String adapterGroupName = adptUri.getAdptGrpName();
|
||||||
String adapterName = adptUri.getAdptName();
|
String adapterName = adptUri.getAdptName();
|
||||||
AdapterGroupVO adapterGroupVO = AdapterManager.getInstance().getAdapterGroupVO(adapterGroupName);
|
AdapterGroupVO adapterGroupVO = AdapterManager.getInstance().getAdapterGroupVO(adapterGroupName);
|
||||||
@@ -101,6 +116,8 @@ public class DJBApiAdapterController implements HttpAdapterServiceKey {
|
|||||||
if (adapterVO == null) {
|
if (adapterVO == null) {
|
||||||
String errorMsg = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_AP_ERROR,
|
String errorMsg = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_AP_ERROR,
|
||||||
"Adapter not found error");
|
"Adapter not found error");
|
||||||
|
logError(servletRequest, adapterGroupName, MessageUtil.ERROR_CODE_AP_ERROR,
|
||||||
|
errorMsg, transactionProp, null);
|
||||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(MediaType.APPLICATION_JSON)
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(MediaType.APPLICATION_JSON)
|
||||||
.body(errorMsg);
|
.body(errorMsg);
|
||||||
}
|
}
|
||||||
@@ -112,16 +129,10 @@ public class DJBApiAdapterController implements HttpAdapterServiceKey {
|
|||||||
MediaType mediaType = MediaType.valueOf("application/json;charset=" + encode);
|
MediaType mediaType = MediaType.valueOf("application/json;charset=" + encode);
|
||||||
String errorResponseFormat = httpProp.getProperty(ERROR_RESPONSE_FORMAT);
|
String errorResponseFormat = httpProp.getProperty(ERROR_RESPONSE_FORMAT);
|
||||||
|
|
||||||
ResponseEntity<String> responseEntity = null;
|
|
||||||
Properties transactionProp = new Properties();
|
|
||||||
String uuid = UUIDGenerator.getUUID().toString().replaceAll("-", "");
|
|
||||||
transactionProp.setProperty(TransactionContextKeys.TRANSACTION_UUID, uuid);
|
|
||||||
|
|
||||||
TxSiftContext.begin(uuid);
|
TxSiftContext.begin(uuid);
|
||||||
|
|
||||||
// jwhong, put api received time, eaiSvcCode
|
// jwhong, put api received time, eaiSvcCode
|
||||||
transactionProp.put(INBOUND_REQUESTED_TIME, receivedTimeStr);
|
|
||||||
|
|
||||||
String responseData = "";
|
String responseData = "";
|
||||||
try {
|
try {
|
||||||
responseData = service.callApi(servletRequest, servletResponse, httpProp, adapterGroupVO, adapterVO,
|
responseData = service.callApi(servletRequest, servletResponse, httpProp, adapterGroupVO, adapterVO,
|
||||||
@@ -150,14 +161,14 @@ public class DJBApiAdapterController implements HttpAdapterServiceKey {
|
|||||||
responseEntity = ResponseEntity.status(e1.getStatus()).contentType(mediaType).body(responseErrorMsg);
|
responseEntity = ResponseEntity.status(e1.getStatus()).contentType(mediaType).body(responseErrorMsg);
|
||||||
} else if (e instanceof JwtAuthException) {
|
} else if (e instanceof JwtAuthException) {
|
||||||
responseErrorMsg = genErrorResponseMessage(adapterGroupName, adapterName,
|
responseErrorMsg = genErrorResponseMessage(adapterGroupName, adapterName,
|
||||||
transactionProp, MessageUtil.ERROR_CODE_AP_ERROR, e, adptMsgType, encode, errorResponseFormat);
|
transactionProp, MessageUtil.ERROR_CODE_AUTH_FAIL, e, adptMsgType, encode, errorResponseFormat);
|
||||||
JwtAuthException e1 = (JwtAuthException) e;
|
JwtAuthException e1 = (JwtAuthException) e;
|
||||||
this.logError(servletRequest, adapterGroupName, e1.getCode(), responseErrorMsg, transactionProp, e);
|
this.logError(servletRequest, adapterGroupName, e1.getCode(), responseErrorMsg, transactionProp, e);
|
||||||
logger.error("ApiAdapterController] " + adapterGroupName + "-" + adapterName + ">>" + e.getMessage(), e);
|
logger.error("ApiAdapterController] " + adapterGroupName + "-" + adapterName + ">>" + e.getMessage(), e);
|
||||||
responseEntity = ResponseEntity.status(HttpStatus.UNAUTHORIZED).contentType(mediaType).body(responseErrorMsg);
|
responseEntity = ResponseEntity.status(HttpStatus.UNAUTHORIZED).contentType(mediaType).body(responseErrorMsg);
|
||||||
} else if (e instanceof HttpStatusException) {
|
} else if (e instanceof HttpStatusException) {
|
||||||
responseErrorMsg = genErrorResponseMessage(adapterGroupName, adapterName,
|
responseErrorMsg = genErrorResponseMessage(adapterGroupName, adapterName,
|
||||||
transactionProp, MessageUtil.ERROR_CODE_AUTH_FAIL, e, adptMsgType, encode, errorResponseFormat);
|
transactionProp, MessageUtil.ERROR_CODE_AP_ERROR, e, adptMsgType, encode, errorResponseFormat);
|
||||||
logger.error("ApiAdapterController] " + adapterGroupName + "-" + adapterName + ">>" + e.getMessage());
|
logger.error("ApiAdapterController] " + adapterGroupName + "-" + adapterName + ">>" + e.getMessage());
|
||||||
HttpStatusException e1 = (HttpStatusException) e;
|
HttpStatusException e1 = (HttpStatusException) e;
|
||||||
responseEntity = ResponseEntity.status(e1.getStatus()).contentType(mediaType).body(responseErrorMsg);
|
responseEntity = ResponseEntity.status(e1.getStatus()).contentType(mediaType).body(responseErrorMsg);
|
||||||
|
|||||||
@@ -154,6 +154,8 @@ public class DJBApiAdapterService extends HttpAdapterServiceSupport {
|
|||||||
transactionProp.put(HEADER_GROUP, headerGroupName);
|
transactionProp.put(HEADER_GROUP, headerGroupName);
|
||||||
transactionProp.put(HEADER_KEYS, relayRequestHeaderKeys);
|
transactionProp.put(HEADER_KEYS, relayRequestHeaderKeys);
|
||||||
|
|
||||||
|
transactionProp.put(ADAPTER_TOKEN_HEADER_NAME, httpProp.getProperty(ADAPTER_TOKEN_HEADER_NAME, "")); // OAuth 인증 토큰 헤더 이름
|
||||||
|
transactionProp.put(ADAPTER_APIKEY_HEADER_NAME, httpProp.getProperty(ADAPTER_APIKEY_HEADER_NAME, "")); // API-KEY 인증 헤더 이름
|
||||||
// SEED 컬럼암호하 시 Key로 사용함
|
// SEED 컬럼암호하 시 Key로 사용함
|
||||||
String seedkey = inboundHeaderProp.getOrDefault("x-obp-partnercode", "").toString();
|
String seedkey = inboundHeaderProp.getOrDefault("x-obp-partnercode", "").toString();
|
||||||
ElinkTransactionContext.setSeedKey(seedkey);
|
ElinkTransactionContext.setSeedKey(seedkey);
|
||||||
|
|||||||
+2
-11
@@ -6,7 +6,6 @@ import java.util.Properties;
|
|||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.json.simple.JSONObject;
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
|
||||||
import com.eactive.eai.adapter.http.dynamic.filter.FilterException;
|
import com.eactive.eai.adapter.http.dynamic.filter.FilterException;
|
||||||
@@ -16,6 +15,7 @@ import com.eactive.eai.common.security.ARIACryptoModuleExtension;
|
|||||||
import com.eactive.eai.common.security.CryptoModuleExtension;
|
import com.eactive.eai.common.security.CryptoModuleExtension;
|
||||||
import com.eactive.eai.common.util.Logger;
|
import com.eactive.eai.common.util.Logger;
|
||||||
import com.eactive.eai.util.HexaConverter;
|
import com.eactive.eai.util.HexaConverter;
|
||||||
|
import com.eactive.eai.util.JsonPathUtil;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
@@ -30,16 +30,7 @@ public class DecrytTestFilter implements HttpAdapterFilter {
|
|||||||
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
|
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
|
||||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||||
try {
|
try {
|
||||||
ObjectNode rootNode = null;
|
ObjectNode rootNode = (ObjectNode)JsonPathUtil.toTree(message);
|
||||||
String jsonStr = null;
|
|
||||||
if (message instanceof String) {
|
|
||||||
jsonStr = (String) message;
|
|
||||||
} else if (message instanceof JSONObject) {
|
|
||||||
jsonStr = ((JSONObject) message).toJSONString();
|
|
||||||
} else {
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
rootNode = (ObjectNode) OBJECT_MAPPER.readTree(jsonStr);
|
|
||||||
|
|
||||||
JsonNode alg = rootNode.get("alg");
|
JsonNode alg = rootNode.get("alg");
|
||||||
JsonNode mode = rootNode.get("mode");
|
JsonNode mode = rootNode.get("mode");
|
||||||
|
|||||||
+2
-11
@@ -8,7 +8,6 @@ import javax.crypto.SecretKey;
|
|||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.json.simple.JSONObject;
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
|
||||||
import com.eactive.eai.adapter.http.dynamic.filter.FilterException;
|
import com.eactive.eai.adapter.http.dynamic.filter.FilterException;
|
||||||
@@ -19,6 +18,7 @@ import com.eactive.eai.common.security.ARIACryptoModuleExtension;
|
|||||||
import com.eactive.eai.common.security.CryptoModuleExtension;
|
import com.eactive.eai.common.security.CryptoModuleExtension;
|
||||||
import com.eactive.eai.common.util.Logger;
|
import com.eactive.eai.common.util.Logger;
|
||||||
import com.eactive.eai.util.HexaConverter;
|
import com.eactive.eai.util.HexaConverter;
|
||||||
|
import com.eactive.eai.util.JsonPathUtil;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
@@ -33,16 +33,7 @@ public class EncrytTestFilter implements HttpAdapterFilter {
|
|||||||
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
|
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
|
||||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||||
try {
|
try {
|
||||||
ObjectNode rootNode = null;
|
ObjectNode rootNode = (ObjectNode)JsonPathUtil.toTree(message);
|
||||||
String jsonStr = null;
|
|
||||||
if (message instanceof String) {
|
|
||||||
jsonStr = (String) message;
|
|
||||||
} else if (message instanceof JSONObject) {
|
|
||||||
jsonStr = ((JSONObject) message).toJSONString();
|
|
||||||
} else {
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
rootNode = (ObjectNode) OBJECT_MAPPER.readTree(jsonStr);
|
|
||||||
|
|
||||||
// 1. HSM에서 키 가져오기
|
// 1. HSM에서 키 가져오기
|
||||||
byte[] hsmKeyBytes = null;
|
byte[] hsmKeyBytes = null;
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import java.util.Properties;
|
|||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.json.simple.JSONObject;
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
|
||||||
import com.eactive.eai.adapter.http.dynamic.filter.FilterException;
|
import com.eactive.eai.adapter.http.dynamic.filter.FilterException;
|
||||||
@@ -18,6 +17,7 @@ import com.eactive.eai.common.util.Logger;
|
|||||||
import com.eactive.eai.custom.common.security.keyderiv.HsmContextSha256KeyDerivationStrategy;
|
import com.eactive.eai.custom.common.security.keyderiv.HsmContextSha256KeyDerivationStrategy;
|
||||||
import com.eactive.eai.custom.common.security.keyderiv.HsmKeyAndIvSliceDerivationStrategy;
|
import com.eactive.eai.custom.common.security.keyderiv.HsmKeyAndIvSliceDerivationStrategy;
|
||||||
import com.eactive.eai.util.HexaConverter;
|
import com.eactive.eai.util.HexaConverter;
|
||||||
|
import com.eactive.eai.util.JsonPathUtil;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
@@ -32,16 +32,7 @@ public class GenKeyFilter implements HttpAdapterFilter {
|
|||||||
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
|
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
|
||||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||||
try {
|
try {
|
||||||
ObjectNode rootNode = null;
|
ObjectNode rootNode = (ObjectNode)JsonPathUtil.toTree(message);
|
||||||
String jsonStr = null;
|
|
||||||
if (message instanceof String) {
|
|
||||||
jsonStr = (String) message;
|
|
||||||
} else if (message instanceof JSONObject) {
|
|
||||||
jsonStr = ((JSONObject) message).toJSONString();
|
|
||||||
} else {
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
rootNode = (ObjectNode) OBJECT_MAPPER.readTree(jsonStr);
|
|
||||||
|
|
||||||
JsonNode hsmKey = rootNode.get("hsmKey");
|
JsonNode hsmKey = rootNode.get("hsmKey");
|
||||||
JsonNode hsmIv = rootNode.get("hsmIv");
|
JsonNode hsmIv = rootNode.get("hsmIv");
|
||||||
|
|||||||
+108
-80
@@ -10,6 +10,7 @@ import java.security.UnrecoverableKeyException;
|
|||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
|
|
||||||
@@ -62,6 +63,13 @@ public class DJBErpNsmapiAccessTokenService implements HttpClientAccessTokenServ
|
|||||||
|
|
||||||
private static boolean testMode = TestModeChecker.isTestMode();
|
private static boolean testMode = TestModeChecker.isTestMode();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mTLS 여부/clientId 조합별로 CloseableHttpClient(및 내부 PoolingHttpClientConnectionManager)를
|
||||||
|
* 1회만 생성해 재사용한다. 이 서비스 인스턴스는 HttpClientAccessTokenServiceFactoryByDB에
|
||||||
|
* className 기준으로 캐시되어 재사용되므로, 이 필드도 인스턴스 생명주기 동안 안전하게 재사용된다.
|
||||||
|
*/
|
||||||
|
private final ConcurrentHashMap<String, CloseableHttpClient> httpClientCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1. 기능 : 법인검증 토큰 발급에 사용 2. 처리 개요 : - 속성 정보를 설정 하고 토큰 발급 URL 호출 한다. 3. 주의사항
|
* 1. 기능 : 법인검증 토큰 발급에 사용 2. 처리 개요 : - 속성 정보를 설정 하고 토큰 발급 URL 호출 한다. 3. 주의사항
|
||||||
* - JSON 방식
|
* - JSON 방식
|
||||||
@@ -121,80 +129,11 @@ public class DJBErpNsmapiAccessTokenService implements HttpClientAccessTokenServ
|
|||||||
logger.info("adapterGroupName. : {}, useMtls. : {}, clientId : {}", name, useMtls, clientId);
|
logger.info("adapterGroupName. : {}, useMtls. : {}, clientId : {}", name, useMtls, clientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
int maxTotalConnections = HttpClientAdapterServiceKey.DEFAULT_MAX_TOTAL_CONNECTIONS;
|
// mTLS 여부(및 clientId)별로 CloseableHttpClient를 재사용한다. 매 호출마다 새로 만들면
|
||||||
int maxHostConnections = HttpClientAdapterServiceKey.DEFAULT_MAX_CONNECTION_PER_HOST;
|
// PoolingHttpClientConnectionManager를 쓰는 의미가 없어지고 SSL 핸드셰이크 비용만 반복된다.
|
||||||
|
String httpClientCacheKey = useMtls ? "mtls:" + clientId : "default";
|
||||||
HttpOutTlsInfoVO mtlsInfo = null;
|
CloseableHttpClient httpClient = httpClientCache.computeIfAbsent(httpClientCacheKey,
|
||||||
SSLContext sslContext = null;
|
key -> buildHttpClient(useMtls, clientId, name));
|
||||||
PoolingHttpClientConnectionManagerBuilder cmBuilder = PoolingHttpClientConnectionManagerBuilder.create();
|
|
||||||
PoolingHttpClientConnectionManager connectionManager = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (useMtls) {
|
|
||||||
HttpOutTlsInfoManager tlsManager = HttpOutTlsInfoManager.getInstance();
|
|
||||||
if (StringUtils.isNotEmpty(clientId)) {
|
|
||||||
mtlsInfo = tlsManager.getHttpOutTlsInfo(clientId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (useMtls && mtlsInfo != null) {
|
|
||||||
String storeType = mtlsInfo.getStoreType();
|
|
||||||
String keyStoreInfo = mtlsInfo.getKeystoreInfo();
|
|
||||||
String keyStorePassword = mtlsInfo.getKeystorePassword();
|
|
||||||
String trustStoreInfo = mtlsInfo.getTruststoreInfo();
|
|
||||||
String trustStorePassword = mtlsInfo.getTruststorePassword();
|
|
||||||
|
|
||||||
String[] tlsVersions = null;
|
|
||||||
String[] cipherSuites = null;
|
|
||||||
|
|
||||||
if (StringUtils.isAnyEmpty(keyStoreInfo, keyStorePassword)) {
|
|
||||||
throw new Exception("mTLS keyStore config error");
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean skipTrust = false;
|
|
||||||
if (StringUtils.isAnyEmpty(trustStoreInfo, trustStorePassword)) {
|
|
||||||
if (logger.isWarn())
|
|
||||||
logger.warn("Skip trustStore validation adapterGroupName : " + name);
|
|
||||||
skipTrust = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
sslContext = HttpClient5SSLContextFactory.createMTLSContextFromContent(storeType, keyStoreInfo,
|
|
||||||
keyStorePassword, trustStoreInfo, trustStorePassword, skipTrust, tlsVersions, cipherSuites);
|
|
||||||
|
|
||||||
SSLConnectionSocketFactory sslSocketFactory = null;
|
|
||||||
if (testMode) {
|
|
||||||
sslSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
|
|
||||||
} else {
|
|
||||||
sslSocketFactory = new SSLConnectionSocketFactory(sslContext);
|
|
||||||
}
|
|
||||||
cmBuilder.setSSLSocketFactory(sslSocketFactory);
|
|
||||||
} else {
|
|
||||||
sslContext = SSLContextBuilder.create().loadTrustMaterial(new TrustAllStrategy()).build();
|
|
||||||
SSLConnectionSocketFactory sslSocketFactory = null;
|
|
||||||
if(testMode) {
|
|
||||||
// Hostname verifier 비활성화 (테스트용)
|
|
||||||
sslSocketFactory = new SSLConnectionSocketFactory(
|
|
||||||
sslContext
|
|
||||||
, NoopHostnameVerifier.INSTANCE
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
sslSocketFactory = new SSLConnectionSocketFactory(
|
|
||||||
sslContext
|
|
||||||
);
|
|
||||||
}
|
|
||||||
cmBuilder.setSSLSocketFactory(sslSocketFactory);
|
|
||||||
}
|
|
||||||
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException | CertificateException
|
|
||||||
| IOException | UnrecoverableKeyException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
connectionManager = cmBuilder.build();
|
|
||||||
connectionManager.setMaxTotal(maxTotalConnections);
|
|
||||||
connectionManager.setDefaultMaxPerRoute(maxHostConnections);
|
|
||||||
|
|
||||||
//json body setting
|
//json body setting
|
||||||
// Map<String,Object> jsonMap = new HashMap<>();
|
// Map<String,Object> jsonMap = new HashMap<>();
|
||||||
@@ -203,7 +142,7 @@ public class DJBErpNsmapiAccessTokenService implements HttpClientAccessTokenServ
|
|||||||
// ObjectMapper objMapper = new ObjectMapper();
|
// ObjectMapper objMapper = new ObjectMapper();
|
||||||
// String authJsonBody = objMapper.writeValueAsString(jsonMap);
|
// String authJsonBody = objMapper.writeValueAsString(jsonMap);
|
||||||
|
|
||||||
try (CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(connectionManager).build();) {
|
{
|
||||||
HttpPost httpPost = new HttpPost(uri);
|
HttpPost httpPost = new HttpPost(uri);
|
||||||
// httpPost.setEntity(new StringEntity(authJsonBody));
|
// httpPost.setEntity(new StringEntity(authJsonBody));
|
||||||
httpPost.setHeader("Content-Type", contentType+" charset=" + encode);
|
httpPost.setHeader("Content-Type", contentType+" charset=" + encode);
|
||||||
@@ -264,11 +203,14 @@ public class DJBErpNsmapiAccessTokenService implements HttpClientAccessTokenServ
|
|||||||
|
|
||||||
if (responseJSON.has("data")) {
|
if (responseJSON.has("data")) {
|
||||||
JsonNode data = responseJSON.path("data");
|
JsonNode data = responseJSON.path("data");
|
||||||
if(data.has("token")) {
|
if(data.has("access_token")) {
|
||||||
String token = data.path("token").asText();
|
String token = data.path("access_token").asText();
|
||||||
|
long intervalSec = oAuthCredentialVo.getIntervalSec() > 0
|
||||||
|
? oAuthCredentialVo.getIntervalSec()
|
||||||
|
: 24 * 60 * 60;
|
||||||
accessToken = new OAuth2AccessTokenVO();
|
accessToken = new OAuth2AccessTokenVO();
|
||||||
accessToken.setAccessToken(token);
|
accessToken.setAccessToken(token);
|
||||||
accessToken.setExpiration(new Date(currentTime + 30_000L));
|
accessToken.setExpiration(new Date(currentTime + intervalSec * 1000L));
|
||||||
logger.debug("oauthToken =" + accessToken.toString());
|
logger.debug("oauthToken =" + accessToken.toString());
|
||||||
return accessToken;
|
return accessToken;
|
||||||
} else {
|
} else {
|
||||||
@@ -277,8 +219,6 @@ public class DJBErpNsmapiAccessTokenService implements HttpClientAccessTokenServ
|
|||||||
} else {
|
} else {
|
||||||
throw new Exception("oauth token return null");
|
throw new Exception("oauth token return null");
|
||||||
}
|
}
|
||||||
// accessToken.setExpiration(new Date(currentTime + oAuthCredentialVo.getIntervalSec() * 1000L));
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw new Exception("oauth token return null");
|
throw new Exception("oauth token return null");
|
||||||
}
|
}
|
||||||
@@ -289,6 +229,94 @@ public class DJBErpNsmapiAccessTokenService implements HttpClientAccessTokenServ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mTLS 여부/clientId 조합에 맞는 SSLContext로 PoolingHttpClientConnectionManager와
|
||||||
|
* CloseableHttpClient를 생성한다. httpClientCache에 의해 조합당 1회만 호출되며, 반환된
|
||||||
|
* CloseableHttpClient는 재사용을 위해 닫지 않는다(닫으면 커넥션 풀이 함께 종료된다).
|
||||||
|
*/
|
||||||
|
private CloseableHttpClient buildHttpClient(boolean useMtls, String clientId, String adapterGroupName) {
|
||||||
|
int maxTotalConnections = HttpClientAdapterServiceKey.DEFAULT_MAX_TOTAL_CONNECTIONS;
|
||||||
|
int maxHostConnections = HttpClientAdapterServiceKey.DEFAULT_MAX_CONNECTION_PER_HOST;
|
||||||
|
|
||||||
|
HttpOutTlsInfoVO mtlsInfo = null;
|
||||||
|
SSLContext sslContext = null;
|
||||||
|
PoolingHttpClientConnectionManagerBuilder cmBuilder = PoolingHttpClientConnectionManagerBuilder.create();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (useMtls) {
|
||||||
|
HttpOutTlsInfoManager tlsManager = HttpOutTlsInfoManager.getInstance();
|
||||||
|
if (StringUtils.isNotEmpty(clientId)) {
|
||||||
|
mtlsInfo = tlsManager.getHttpOutTlsInfo(clientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useMtls && mtlsInfo != null) {
|
||||||
|
String storeType = mtlsInfo.getStoreType();
|
||||||
|
String keyStoreInfo = mtlsInfo.getKeystoreInfo();
|
||||||
|
String keyStorePassword = mtlsInfo.getKeystorePassword();
|
||||||
|
String trustStoreInfo = mtlsInfo.getTruststoreInfo();
|
||||||
|
String trustStorePassword = mtlsInfo.getTruststorePassword();
|
||||||
|
|
||||||
|
String[] tlsVersions = null;
|
||||||
|
String[] cipherSuites = null;
|
||||||
|
|
||||||
|
if (StringUtils.isAnyEmpty(keyStoreInfo, keyStorePassword)) {
|
||||||
|
throw new Exception("mTLS keyStore config error");
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean skipTrust = false;
|
||||||
|
if (StringUtils.isAnyEmpty(trustStoreInfo, trustStorePassword)) {
|
||||||
|
if (logger.isWarn())
|
||||||
|
logger.warn("Skip trustStore validation adapterGroupName : " + adapterGroupName);
|
||||||
|
skipTrust = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
sslContext = HttpClient5SSLContextFactory.createMTLSContextFromContent(storeType, keyStoreInfo,
|
||||||
|
keyStorePassword, trustStoreInfo, trustStorePassword, skipTrust, tlsVersions, cipherSuites);
|
||||||
|
|
||||||
|
SSLConnectionSocketFactory sslSocketFactory = null;
|
||||||
|
if (testMode) {
|
||||||
|
sslSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
|
||||||
|
} else {
|
||||||
|
sslSocketFactory = new SSLConnectionSocketFactory(sslContext);
|
||||||
|
}
|
||||||
|
cmBuilder.setSSLSocketFactory(sslSocketFactory);
|
||||||
|
} else {
|
||||||
|
sslContext = SSLContextBuilder.create().loadTrustMaterial(new TrustAllStrategy()).build();
|
||||||
|
SSLConnectionSocketFactory sslSocketFactory = null;
|
||||||
|
if(testMode) {
|
||||||
|
// Hostname verifier 비활성화 (테스트용)
|
||||||
|
sslSocketFactory = new SSLConnectionSocketFactory(
|
||||||
|
sslContext
|
||||||
|
, NoopHostnameVerifier.INSTANCE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sslSocketFactory = new SSLConnectionSocketFactory(
|
||||||
|
sslContext
|
||||||
|
);
|
||||||
|
}
|
||||||
|
cmBuilder.setSSLSocketFactory(sslSocketFactory);
|
||||||
|
}
|
||||||
|
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException | CertificateException
|
||||||
|
| IOException | UnrecoverableKeyException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
PoolingHttpClientConnectionManager connectionManager = cmBuilder.build();
|
||||||
|
connectionManager.setMaxTotal(maxTotalConnections);
|
||||||
|
connectionManager.setDefaultMaxPerRoute(maxHostConnections);
|
||||||
|
|
||||||
|
if (logger.isInfo()) {
|
||||||
|
logger.info("DJBErpNsmapiAccessTokenService] HttpClient(재사용) 생성. adapterGroupName={}, useMtls={}, clientId={}",
|
||||||
|
adapterGroupName, useMtls, clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return HttpClients.custom().setConnectionManager(connectionManager).build();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* URL에 경로를 추가합니다. 중복되는 슬래시를 방지합니다.
|
* URL에 경로를 추가합니다. 중복되는 슬래시를 방지합니다.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public final class GUIDGeneratorDJB {
|
|||||||
|
|
||||||
static {
|
static {
|
||||||
// 노드번호(2): 서버명 마지막 2자리, 없으면 "00"
|
// 노드번호(2): 서버명 마지막 2자리, 없으면 "00"
|
||||||
String serverName = System.getProperty("server.key", "");
|
String serverName = System.getProperty(com.eactive.eai.common.server.Keys.SERVER_KEY, "");
|
||||||
if (serverName.length() >= 2) {
|
if (serverName.length() >= 2) {
|
||||||
NODE_NO = serverName.substring(serverName.length() - 2);
|
NODE_NO = serverName.substring(serverName.length() - 2);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user