This commit is contained in:
2025-12-18 23:03:26 +09:00
commit ce402ef828
95 changed files with 4704 additions and 0 deletions

38
.gitignore vendored Normal file
View File

@@ -0,0 +1,38 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
/logs/

View File

@@ -0,0 +1,6 @@
### GET request to example server
GET http://localhost:8010/api/itg/appr?page=1&row=10
sabun: psn14020
###

View File

@@ -0,0 +1,6 @@
### GET request to example server
GET http://localhost:8010/api/itg/appr/req?page=1&row=10
sabun: 17131303
###

View File

@@ -0,0 +1,16 @@
PUT http://localhost:8010//api/appr
sabun: 17131304
Content-Type: application/json
{
"apprNo": "APPR-0000000040",
"apprOrd": 4,
"sabun": "17131304",
"apprStatCd": "0200",
"reason": ""
}
###
#APPR-20250519017,20,17131303,김진형,0000,결재요청
#APPR-20250519017,30,17131304,손원장,0000,결재요청

View File

@@ -0,0 +1,12 @@
PUT http://localhost:8010//api/business/password
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJiaXpObyI6Ijk5OS05OS05OTk5OCIsImlwIjoiMTI3LjAuMC4xIiwic3ViIjoiOTk5LTk5LTk5OTk4IiwiaWF0IjoxNzQ2MDEyNjcyLCJleHAiOjE3NDYwMTI5NzJ9.RaT9wa3_8oPeL6nWv1_uM6QlY1mcHyCN2tC6sP_N03w
Content-Type: application/json
{
"bizNo": "999-99-99998",
"oldPwd": "kospo2025!",
"pwd": "kospo2024!",
"rePwd": "kospo2024!"
}
###

View File

@@ -0,0 +1,13 @@
POST http://localhost:8010//api/business
Content-Type: application/json
{
"bizNo": "999-99-99998",
"pwd": "kospo2025!",
"rePwd": "kospo2025!",
"compNm": "테스트2",
"repNm": "테스트2",
"email": "bangae1@gmail.com"
}
###

View File

@@ -0,0 +1,12 @@
PUT http://localhost:8010//api/business
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJiaXpObyI6Ijk5OS05OS05OTk5OCIsImlwIjoiMTI3LjAuMC4xIiwic3ViIjoiOTk5LTk5LTk5OTk4IiwiaWF0IjoxNzQ2MDEyNjcyLCJleHAiOjE3NDYwMTI5NzJ9.RaT9wa3_8oPeL6nWv1_uM6QlY1mcHyCN2tC6sP_N03w
Content-Type: application/json
{
"bizNo": "999-99-99998",
"compNm": "테스트3",
"repNm": "테스트3",
"email": "bangae3@gmail.com"
}
###

View File

@@ -0,0 +1,5 @@
DELETE http://localhost:8010//api/business/999-99-99998
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJiaXpObyI6Ijk5OS05OS05OTk5OCIsImlwIjoiMTI3LjAuMC4xIiwic3ViIjoiOTk5LTk5LTk5OTk4IiwiaWF0IjoxNzQ2MDEyNjcyLCJleHAiOjE3NDYwMTI5NzJ9.RaT9wa3_8oPeL6nWv1_uM6QlY1mcHyCN2tC6sP_N03w
Content-Type: application/json
###

View File

@@ -0,0 +1,4 @@
### GET request to example server
GET http://localhost:8010/api/cont/CONT-0000000005
###

View File

@@ -0,0 +1,5 @@
### GET request to example server
GET http://localhost:8010/api/cont/page
?cateStatCd=&signSdt=&signEdt=&page=1&row=10
###

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,4 @@
DELETE http://localhost:8010/api/bid/BID-0000000009/1
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJiaXpObyI6Ijk5OS05OS05OTk5OSIsImlwIjoiMTI3LjAuMC4xIiwic3ViIjoiOTk5LTk5LTk5OTk5IiwiaWF0IjoxNzQ1NTkwNjQ3LCJleHAiOjE3NDU1OTA5NDd9.a9nwoQTDV702VbU0HnP1jS8SGhFi_3UjkjdBcjoDzCE
###

View File

@@ -0,0 +1,24 @@
PUT http://localhost:8010/api/estimate
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJiaXpObyI6Ijk5OS05OS05OTk5OSIsImlwIjoiMTI3LjAuMC4xIiwic3ViIjoiOTk5LTk5LTk5OTk5IiwiaWF0IjoxNzQ3MDQ4NDI5LCJleHAiOjE3NDcwNTAyMjl9.6SzI3eiNVmXOz1s1p8ab26fT80rbSJBfoDlKLQ4z5Sg
{
"estimates": [
{
"estNo": "EST-0000000003",
"mngNm": "조진우",
"unitPrc": 2,
"amt": 40000
},
{
"estNo": "EST-0000000004",
"mngNm": "조진우",
"unitPrc": 12,
"amt": 60000
}
],
"pbAtts": []
}
###

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,6 @@
### GET request to example server
GET http://localhost:8010/api/estimate
?prcsNo=PRCS-0000000006
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJiaXpObyI6Ijk5OS05OS05OTk5OSIsImlwIjoiMTI3LjAuMC4xIiwic3ViIjoiOTk5LTk5LTk5OTk5IiwiaWF0IjoxNzQ3MDQ4NDI5LCJleHAiOjE3NDcwNTAyMjl9.6SzI3eiNVmXOz1s1p8ab26fT80rbSJBfoDlKLQ4z5Sg
###

View File

@@ -0,0 +1,10 @@
### GET request to example server
POST http://localhost:8010/api/login
Content-Type: application/json
{
"bizNo": "111-11-11111",
"pwd": "kospo2025!"
}
###

View File

@@ -0,0 +1,6 @@
### GET request to example server
GET http://localhost:8010/api/prcs/external/page
?cateCd=&stCd=&regNm=&regSdt=&regEdt=&page=1&row=10
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJiaXpObyI6Ijk5OS05OS05OTk5OSIsImlwIjoiMTI3LjAuMC4xIiwic3ViIjoiOTk5LTk5LTk5OTk5IiwiaWF0IjoxNzQ2MDEzMjAwLCJleHAiOjE3NDYwMTM1MDB9.2mSDvhpXtvkYHeRr_d1tiKvnSU0OGehBq0ce1AOvXnE
###

View File

@@ -0,0 +1,4 @@
### GET request to example server
GET http://localhost:8010/api/prcs/PRCS-20250523052
###

View File

@@ -0,0 +1,54 @@
PUT http://localhost:8010//api/prcs
Content-Type: application/json
{
"prcsNo": "PRCS-20250530003",
"cateCd": "0200",
"title": "가격조사 결재 테스트",
"content": "시나리오테스트입니다.\n시나리오테스트입니다.\n시나리오테스트입니다.\n시나리오테스트입니다.\n시나리오테스트입니다.\n시나리오테스트입니다.\n시나리오테스트입니다.\n시나리오테스트입니다.\n시나리오테스트입니다.\n시나리오테스트입니다.\n시나리오테스트입니다.\n시나리오테스트입니다.\n",
"regSdat": "2025-04-01",
"regEdat": "2025-07-30",
"prvYn": false,
"prvRsn": "",
"prvPwd": "",
"aiYn": false,
"prcsAtts": [
],
"dtlSpecs": [
],
"apprReqs": [
{
"gubunCd": "",
"apprNo": "APPR-0000000066",
"sabun": "psn14020",
"name": "조진우",
"attendCd": ""
},
{
"gubunCd": "0100",
"apprNo": "APPR-0000000066",
"sabun": "17131303",
"name": "김진형",
"attendCd": ""
},
{
"gubunCd": "0200",
"apprNo": "APPR-0000000066",
"sabun": "17131304",
"name": "손원장",
"attendCd": "01"
}
],
"prcsBizs": [
{
"bizNo": "999-99-99999",
"email": "bangae2@gmail.com"
},
{
"bizNo": "111-11-11111",
"email": "bangae1@gmail.com"
}
]
}
###

View File

@@ -0,0 +1,46 @@
PUT https://svcm.hmsn.ink//api/prcs
Content-Type: application/json
{
"prcsNo": "PRCS-20250521023",
"cateCd": "0000",
"title": "4층 파티션 구매123",
"content": "싼거 찾아요123",
"regSdat": "2025-05-01",
"regEdat": "2025-05-03",
"prvYn": true,
"prvRsn": "",
"prvPwd": "",
"aiYn": true,
"prcsBizs": [
{
"bizNo": "999-99-99999",
"email": "aa@gmail.com"
},
{
"bizNo": "111-11-11111",
"email": "bb@gmail.com"
}
],
"prcsAtts": [],
"apprReqs": [
{
"gubunCd": "0000",
"sabun": "psn14020",
"name": "조진우",
"apprNo": "APPR-0000000023",
"apprOrd": 1,
"attendCd": null
},
{
"gubunCd": "0100",
"sabun": "15000007",
"name": "조용식",
"apprNo": "APPR-0000000023",
"apprOrd": 2,
"attendCd": null
}
]
}
###

View File

@@ -0,0 +1,4 @@
PUT http://localhost:8010/api/prcs/survey/PRCS-20250526039
Content-Type: application/json
###

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,5 @@
### GET request to example server
GET http://localhost:8010/api/prcs/page
?cateCd=&stCd=&regNm=&regSdt=&regEdt=&page=1&row=10
###

View File

@@ -0,0 +1,3 @@
PUT http://localhost:8010/api/prcs/ret/PRCS-20250522037
Content-Type: application/json
###

View File

@@ -0,0 +1,10 @@
PUT http://localhost:8010/api/sap/appr
sabun: 17131303
Content-Type: application/json
{
"wkfid": "FI2025900017",
"wkfsq": 2,
"bname": "17131303",
"wkfst": "A"
}

View File

@@ -0,0 +1,33 @@
POST http://localhost:8010/api/sap/appr
sabun: 15000062
Content-Type: application/json
{
"wkfid": "FI2025900017",
"apprs": [
{
"label": "협조",
"value": [
{
"lineclsf": "Q",
"bname": "15000062",
"abscd": "",
"grpid": 2
},
{
"lineclsf": "I",
"bname": "15000057",
"abscd": "",
"grpid": 2
},
{
"lineclsf": "A",
"bname": "15000056",
"abscd": "",
"grpid": 2
}
]
}
]
}

View File

@@ -0,0 +1,47 @@
PUT http://localhost:8010/api/slip
Content-Type: application/json
{
"contNo": "CONT-20250527037",
"zwf0011t": {
"belnr": "0604103229",
"wkfid": "FI2025900014",
"apprs": [
{
"label": "결재",
"value": [
{
"lineclsf": "Q",
"bname": "psn14020",
"abscd": ""
},
{
"lineclsf": "I",
"bname": "17131303",
"abscd": ""
},
{
"lineclsf": "I",
"bname": "15000004",
"abscd": "A"
},
{
"lineclsf": "E",
"bname": "15000037",
"abscd": ""
},
{
"lineclsf": "E",
"bname": "15000062",
"abscd": ""
},
{
"lineclsf": "A",
"bname": "15000005",
"abscd": ""
}
]
}
]
}
}

View File

@@ -0,0 +1,71 @@
POST http://localhost:8010/api/slip
Content-Type: application/json
{
"contNo": "CONT-20250527037",
"bldat": "20250501",
"budat": "20250502",
"waers": "KRW",
"bktxt": "전표 생성 테스트1",
"lifnr": "999-99-99999",
"wrbtr": "1203",
"mwskz": "V4",
"gsber": "1000",
"bupla": "1000",
"zterm": "PF00",
"banks": "KR",
"bankl": "012",
"bankn": "3510876657453",
"hkont": "5366010",
"wrbtrS": "1102",
"kostl": "12330",
"projk": "",
"trtGubun": "11",
"txBillSeq": "202503231",
"slipAtts": [
{
"logiFnm": "sapApprTest.pdf",
"data": "4paR4paS4paT4paI4pa6IFdpbmRvd3MgWC1MaXRlIOKXhOKWiOKWk+KWkuKWkQ0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0K4oCiIEN1c3RvbSBXaW5kb3dzIEJ1aWxkcyBkZXNpZ25lZCB0byBCcmVhdGhlIE5ldyBMaWZlIGludG8geW91ciBQQyENCuKAoiBTbWFsbGVyLCBMaWdodGVyLCBGYXN0ZXIgYW5kIE1vcmUgUmVzcG9uc2l2ZS4NCuKAoiBFbmhhbmNlZCBQcml2YWN5LCBBY2Nlc3NpYmlsaXR5IGFuZCBDb250cm9sLg0K4oCiIDEwMCUgQ2xlYW4uIDEwMCUgU2FmZS4gMTAwJSBPcHRpbWl6ZWQuDQrigKIgQXJlIFlvdSBSZWFkeSBUbyBUYWtlIFlvdXIgV2luZG93cyBleHBlcmllbmNlIHRvIHRoZSBOZXh0IExldmVsIT8NCg0KICDimJEgQ09NRSBWSVNJVCBVUyEg4piRDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCuKeoe+4jyBodHRwczovL3dpbmRvd3N4bGl0ZS5jb20NCuKeoe+4jyBodHRwczovL3d3dy55b3V0dWJlLmNvbS9AV2luZG93c1gtTGl0ZQ0K4p6h77iPIGh0dHBzOi8va28tZmkuY29tL3dpbmRvd3N4bGl0ZQ0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQrinaTvuI8gWW91IGNhbiBIZWxwIFN1cHBvcnQgVGhpcyBQcm9qZWN0IGJ5IERvbmF0aW5nIQ0K4p6h77iPIGh0dHBzOi8va28tZmkuY29tL3dpbmRvd3N4bGl0ZQ0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQrinaTvuI8gWW91IENhbiBIZWxwIFN1cHBvcnQgVGhpcyBQcm9qZWN0IGJ5IERpc2FibGluZyBBZCBCbG9ja2Vycw0KYW5kIGNsaWNraW5nIGFkcyB0aGF0IGludGVyZXN0IHlvdSB3aGlsZSB2aXNpdGluZyBvdXIgV2Vic2l0ZSBhbmQgb3VyIFlvdVR1YmUgQ2hhbm5lbCENCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0K4p6h77iPIFRoYW5rIHlvdSBmb3IgeW91ciBzdXBwb3J0LCBhbmQgZm9yIGJlaW5nIGEgcGFydCBvZiBvdXIgY29tbXVuaXR5IQ0K4p6h77iPIFlvdXJzIFRydWx5IC0gRkJDb25hbiAmIFRoZSBXaW5kb3dzIFgtTGl0ZSBUZWFtDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg=="
}
],
"zwf0011t": {
"wkftx": "결재 테스트 11",
"apprs": [
{
"label": "결재",
"value": [
{
"lineclsf": "Q",
"bname": "psn14020",
"abscd": ""
},
{
"lineclsf": "I",
"bname": "17131303",
"abscd": ""
},
{
"lineclsf": "I",
"bname": "15000004",
"abscd": "A"
},
{
"lineclsf": "E",
"bname": "15000037",
"abscd": ""
},
{
"lineclsf": "E",
"bname": "15000062",
"abscd": ""
},
{
"lineclsf": "A",
"bname": "15000005",
"abscd": ""
}
]
}
]
}
}

94
build.gradle Normal file
View File

@@ -0,0 +1,94 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.3.2'
id 'io.spring.dependency-management' version '1.1.6'
}
group = 'com.kospo'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
ext['queryDslVersion'] = '5.0.0'
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
implementation 'org.springframework.boot:spring-boot-starter-web-services'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-reactor-netty'
implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'org.springframework.retry:spring-retry:2.0.8'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'
implementation 'org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16'
implementation 'com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.5'
implementation 'com.github.ulisesbocchio:jasypt-maven-plugin:3.0.5'
implementation 'org.jetbrains:annotations:24.0.0'
implementation "com.querydsl:querydsl-jpa:${queryDslVersion}:jakarta"
implementation 'org.springframework.boot:spring-boot-starter-actuator'
// annotationProcessor "com.querydsl:querydsl-apt:${queryDslVersion}"
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
implementation 'com.google.code.gson:gson:2.9.0'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.3'
implementation 'com.fasterxml.jackson.core:jackson-core:2.15.3'
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.15.3'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.3'
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
implementation 'org.apache.poi:poi:5.4.1'
implementation 'org.apache.poi:poi-ooxml:5.4.1'
implementation 'javax.xml.bind:jaxb-api:2.3.1'
implementation 'org.modelmapper:modelmapper:3.2.0'
implementation 'org.apache.commons:commons-lang3:3.13.0'
implementation 'commons-codec:commons-codec:1.16.0'
implementation 'org.apache.commons:commons-dbcp2:2.10.0'
implementation 'org.apache.commons:commons-pool2:2.12.0'
implementation 'commons-logging:commons-logging:1.2'
implementation 'commons-io:commons-io:2.19.0'
implementation 'org.apache.commons:commons-email:1.5'
implementation 'org.postgresql:postgresql:42.7.3'
// https://mvnrepository.com/artifact/com.oracle.database.jdbc/ojdbc10
implementation 'com.oracle.database.jdbc:ojdbc10:19.27.0.0'
// https://mvnrepository.com/artifact/com.oracle.database.jdbc/ojdbc8
implementation 'com.oracle.database.jdbc:ojdbc8:23.8.0.25.04'
// sockjs
runtimeOnly group: 'io.netty', name: 'netty-resolver-dns-native-macos', version: '4.1.112.Final'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.named('test') {
useJUnitPlatform()
}

31
fileToByte.html Normal file

File diff suppressed because one or more lines are too long

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

249
gradlew vendored Normal file
View File

@@ -0,0 +1,249 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

92
gradlew.bat vendored Normal file
View File

@@ -0,0 +1,92 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

File diff suppressed because one or more lines are too long

1
readme.md Normal file
View File

@@ -0,0 +1 @@
drm api

1
settings.gradle Normal file
View File

@@ -0,0 +1 @@
rootProject.name = 'svcm'

View File

@@ -0,0 +1,21 @@
package com.kospo.drm;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.servers.Server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@OpenAPIDefinition(servers = {
@Server(url = "http://localhost:8010", description = "로컬 api"),
@Server(url = "http://hmsn.ink:8010", description = "개발 api"),
@Server(url = "https://svcm.hmsn.ink", description = "외부 api"),
})
@EnableScheduling
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

View File

@@ -0,0 +1,19 @@
package com.kospo.drm.config;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class CorsPreflightHandler {
@RequestMapping(method = RequestMethod.OPTIONS, path="/api/**")
public ResponseEntity options() {
return ResponseEntity.ok()
.header(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*")
.header(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, PUT, DELETE, OPTIONS")
.header(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "*")
.build();
}
}

View File

@@ -0,0 +1,21 @@
package com.kospo.drm.config.exception;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
@Schema(name="errorResponse", description = "오류")
public class CustomErrorResponse {
@Schema(name="code", description = "오류 코드", example = "30000")
private String code;
@Schema(name="body", description = "오류 메시지", example = "오류 발생")
private String body;
@Schema(name="errTime", description = "오류 시간", example = "1761000247")
private long errTime;
}

View File

@@ -0,0 +1,132 @@
package com.kospo.drm.config.exception;
import com.kospo.drm.exception.CustomException;
import com.kospo.drm.exception.CustomMessageException;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import java.util.Date;
@Slf4j
@ControllerAdvice
@Controller
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
public static final String ANSI_RESET = "\u001B[0m";
public static final String ANSI_YELLOW_COLOR = "\u001B[33m";
@ExceptionHandler(CustomException.class)
@ResponseBody
public ResponseEntity customException(CustomException e, HandlerMethod method, HttpServletRequest request, WebRequest webRequest) {
Date errorDate = new Date();
String exceptionName = e.getClass().getSimpleName();
String controllerName = method.getMethod().getDeclaringClass().getSimpleName();
String methodName = method.getMethod().getName();
int lineNumber = e.getStackTrace()[0].getLineNumber();
String detail = e.getMessage();
String requestURI = request.getRequestURI();
log.error(ANSI_YELLOW_COLOR + "[EVENT_TIME: {} | CONTROLLER_NAME : {} | METHOD_NAME : {} | EXCEPTION_LINE : {} | EXCEPTION_NAME : {} | DETAIL : {} | REQUEST_URI : {}]" + ANSI_RESET
, errorDate
, controllerName
, methodName
, lineNumber
, exceptionName
, detail
, requestURI);
HttpHeaders resHeaders = new HttpHeaders();
resHeaders.add("Content-Type", "application/json;charset=UTF-8");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).headers(resHeaders).body(
CustomErrorResponse.builder()
.code(e.getErrorCode().getCode())
.body(e.getErrorCode().getBody())
.errTime(System.currentTimeMillis()).build()
);
}
@ExceptionHandler(CustomMessageException.class)
@ResponseBody
public ResponseEntity customMessageException(CustomMessageException e, HandlerMethod method, HttpServletRequest request, WebRequest webRequest) {
Date errorDate = new Date();
String exceptionName = e.getClass().getSimpleName();
String controllerName = method.getMethod().getDeclaringClass().getSimpleName();
String methodName = method.getMethod().getName();
int lineNumber = e.getStackTrace()[0].getLineNumber();
String detail = e.getMessage();
String requestURI = request.getRequestURI();
log.error(ANSI_YELLOW_COLOR + "[EVENT_TIME: {} | CONTROLLER_NAME : {} | METHOD_NAME : {} | EXCEPTION_LINE : {} | EXCEPTION_NAME : {} | DETAIL : {} | REQUEST_URI : {}]" + ANSI_RESET
, errorDate
, controllerName
, methodName
, lineNumber
, exceptionName
, detail
, requestURI);
HttpHeaders resHeaders = new HttpHeaders();
resHeaders.add("Content-Type", "application/json;charset=UTF-8");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).headers(resHeaders).body(
CustomErrorResponse.builder()
.code(e.getCusMessage().getCode())
.body(e.getCusMessage().getMessage())
.errTime(System.currentTimeMillis()).build()
);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<?> handleException(Exception e, HandlerMethod method, HttpServletRequest request, WebRequest webRequest) {
// Date errorDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Calendar.getInstance().getTime());
Date errorDate = new Date();
String exceptionName = e.getClass().getSimpleName();
String controllerName = method.getMethod().getDeclaringClass().getSimpleName();
String methodName = method.getMethod().getName();
int lineNumber = e.getStackTrace()[0].getLineNumber();
String detail = e.getMessage();
String requestURI = request.getRequestURI();
log.error(ANSI_YELLOW_COLOR + "[EVENT_TIME: {} | CONTROLLER_NAME : {} | METHOD_NAME : {} | EXCEPTION_LINE : {} | EXCEPTION_NAME : {} | DETAIL : {} | REQUEST_URI : {}]" + ANSI_RESET
, errorDate
, controllerName
, methodName
, lineNumber
, exceptionName
, detail
, requestURI);
HttpHeaders resHeaders = new HttpHeaders();
resHeaders.add("Content-Type", "application/json;charset=UTF-8");
return ResponseEntity.status(500).headers(resHeaders).body(
CustomErrorResponse.builder()
.code(ErrorCode.INTERNAL_SERVER_ERROR.getCode())
.body(ErrorCode.INTERNAL_SERVER_ERROR.getBody())
.errTime(System.currentTimeMillis()).build()
);
}
}

View File

@@ -0,0 +1,65 @@
package com.kospo.drm.config.exception;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum ErrorCode {
/*트랜잭션 관련*/
FIND_ERR("20000", "조회에 실패했습니다."),
SAVE_ERR("20001", "저장에 실패했습니다."),
UPD_ERR("20002", "수정에 실패했습니다."),
DEL_ERR("20003", "삭제에 실패했습니다."),
APPR_ERR("20004", "결재가 완료 되지 않았습니다.."),
APPR_LOCK("20005", "결재 진행중 이거나 완료 상태로 수정이 불가합니다."),
APPR_SVY_ERR("20006", "조사완료 되어 등록이 불가합니다."),
/*토큰 관련*/
EXPIRE_TOKEN("30000", "토큰이 만료 되었습니다."),
MALFORMED_JWT("30001", "JWT 문자열은 정확히 2개의 마침표 문자를 포함해야 합니다."),
/*bad request*/
BIND_ERROR("40001", "파라미터를 다시 확인해주세요."),
/*존재 하지 않음*/
NO_RESULT("44001", "존재하지 않는 데이터 입니다."),
NO_ACCOUNT("44011", "아이디 또는 비밀번호를 다시 확인해주세요."),
NO_MATCH_ACCOUNT("44012", "일치하는 사용자가 없습니다."),
NO_TOKEN("44021", "헤더에 토큰이 없습니다."),
NO_MATCH_TOKEN("44022", "일치 하지 않는 토큰입니다."),
NO_SAME_IP("44030", "등록된 아이피와 일치 하지 않습니다. 다시 로그인 해주세요"),
NO_SAME_DATA("44031", "사용자가 소유한 데이터가 아닙니다."),
No_SVY_NOT_EXECUTE("44032", "사전조사가 실행 되지 않았거나 등록 기간중 입니다."),
SAME_DATA("44020", "이미 등록된 데이터가 있습니다."),
/*권한 관련*/
AUTHENTICATION("43000", "사용자가 정보가 일치하지 않습니다."),
AUTHENTICATION_NOT_SUPPORT("43001", "허용되지 않은 사용자입니다."),
NO_REGISTER("43002", "사용자가 존재하지 않습니다."),
SLEEP_ACCOUNT("43003", "휴면 계정입니다. 개인정보에 약관에 동의해주세요."),
//500 INTERNAL SERVER ERROR
INTERNAL_SERVER_ERROR("50000", "서버 에러입니다. 유지보수관리자에게 연락주세요!"),
/*로그인 관련*/
LOGIN_ERR("60000", "사업자번호 또는 비밀번호가 일치 하지 않습니다."),
NOT_REGISTER("60001", "가입된 사업자가 아닙니다."),
ALREADY_REGISTER("60001", "이미 가입된 사업자 입니다."),
OLD_PWD_NOT_MATCH("60002", "현재 비밀번호가 일치 하지 않습니다."),
PWD_NOT_MATCH("60003", "비밀번호가 일치 하지 않습니다."),
EMAIL_VALIDATE_ERR("60004", "정상적인 이메일이 아닙니다."),
/*결재관련*/
PASS_APPR_TWO_PERSON("70001", "전결이 두명이상 될수 없습니다."),
PASS_APPR_MANIPULATE_DATA("70002", "변경 대상자 정보가 변조 되었습니다."),
/*파일 관련*/
FILE_NOT_FOUND_ERROR("80000", "파일이 존재 하지 않습니다."),
FILE_EXT_UPLOAD_ERROR("80001", "허용되지 않은 확장자 입니다."),
FILE_UPLOAD_ERROR("80002", "파일이 정상적이지 않습니다. 확인해주세요"),
FILE_DELETE_ERROR("80003", "파일 삭제중 오류가 발생했습니다. 확인해주세요"),
FILE_DOWNLOAD_ERROR("80004", "파일 다운로드에 실패 했습니다."),
FILE_REQUIRED("80005", "첨부파일은 필수 항목입니다."),
NO_SIGNAL("90000", "응답 코드가 없습니다.");
private final String code;
private final String body;
}

View File

@@ -0,0 +1,102 @@
package com.kospo.drm.config.filter;
import com.kospo.drm.config.exception.ErrorCode;
import com.kospo.drm.config.jwt.JwtUtil;
import com.kospo.drm.exception.*;
import com.kospo.drm.model.Token;
import com.kospo.drm.model.TokenId;
import com.kospo.drm.repository.TokenRepository;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.io.DecodingException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class CustomBasicAuthenticationFilter extends BasicAuthenticationFilter {
private TokenRepository tokenRepository;
private JwtUtil jwtUtil;
public CustomBasicAuthenticationFilter(AuthenticationManager authenticationManager, TokenRepository tokenRepository, JwtUtil jwtUtil) {
super(authenticationManager);
this.jwtUtil = jwtUtil;
this.tokenRepository = tokenRepository;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
final String requestTokenHeader = request.getHeader("Authorization");
boolean isAnonymous = false;
String jwtToken = null;
try {
jwtToken = requestTokenHeader.substring(7);
} catch (NullPointerException e) {
throw new CustomNoMatchTokenException(e.getMessage());
}
String domain = "";
String ip = "";
try {
domain = jwtUtil.extractDomain(jwtToken);
ip = jwtUtil.extractIp(jwtToken);
} catch(ExpiredJwtException e) {
System.out.println(e.getMessage());
throw new CustomExpireTokenException(e.getMessage());
} catch (DecodingException e) {
System.out.println(e.getMessage());
throw new CustomNoMatchTokenException(e.getMessage());
} catch(MalformedJwtException e) {
System.out.println(e.getMessage());
throw new CustomMalformedJwtException(e.getMessage());
}
Optional<Token> optionalToken = tokenRepository.findById(TokenId.builder()
.domain(domain)
.ip(ip)
.build());
if(optionalToken.isPresent()) {
if(jwtUtil.validateToken(jwtToken, optionalToken.get())) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(optionalToken.get(), null, null);
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(usernamePasswordAuthenticationToken);
SecurityContextHolder.setContext(securityContext);
SecurityContextHolder.getContext().getAuthentication();
chain.doFilter(request, response);
SecurityContextHolder.clearContext();
}
} else {
throw new CustomException(ErrorCode.AUTHENTICATION);
}
}
public CustomBasicAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
public CustomBasicAuthenticationFilter(AuthenticationManager authenticationManager, AuthenticationEntryPoint authenticationEntryPoint) {
super(authenticationManager, authenticationEntryPoint);
}
}

View File

@@ -0,0 +1,18 @@
package com.kospo.drm.config.filter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply;
public class LoggingFilter extends Filter<ILoggingEvent> {
@Override
public FilterReply decide(ILoggingEvent iLoggingEvent) {
if(iLoggingEvent.getMessage().contains("locale")) {
return FilterReply.DENY;
} else {
return FilterReply.ACCEPT;
}
}
}

View File

@@ -0,0 +1,104 @@
package com.kospo.drm.config.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kospo.drm.config.exception.CustomErrorResponse;
import com.kospo.drm.config.exception.ErrorCode;
import com.kospo.drm.exception.*;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
public class SecurityExceptionFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException {
ObjectMapper om = new ObjectMapper();
try {
filterChain.doFilter(request, response);
} catch (CustomExpireTokenException e) {
response.setStatus(500);
response.setContentType("application/json;charset=utf-8");
response.getWriter().println(
om.writeValueAsString(CustomErrorResponse.builder()
.code(ErrorCode.EXPIRE_TOKEN.getCode())
.body(ErrorCode.EXPIRE_TOKEN.getBody())
.errTime(System.currentTimeMillis()).build())
);
} catch (CustomNoAccountException e) {
response.setStatus(500);
response.setContentType("application/json;charset=utf-8");
response.getWriter().println(
om.writeValueAsString(CustomErrorResponse.builder()
.code(ErrorCode.NO_ACCOUNT.getCode())
.body(ErrorCode.NO_ACCOUNT.getBody())
.errTime(System.currentTimeMillis()).build())
);
} catch (CustomNoMatchTokenException e) {
response.setStatus(500);
response.setContentType("application/json;charset=utf-8");
response.getWriter().println(
om.writeValueAsString(CustomErrorResponse.builder()
.code(ErrorCode.NO_MATCH_TOKEN.getCode())
.body(ErrorCode.NO_MATCH_TOKEN.getBody())
.errTime(System.currentTimeMillis()).build())
);
} catch (CustomNoResultException e) {
response.setStatus(500);
response.setContentType("application/json;charset=utf-8");
response.getWriter().println(
om.writeValueAsString(CustomErrorResponse.builder()
.code(ErrorCode.NO_RESULT.getCode())
.body(ErrorCode.NO_RESULT.getBody())
.errTime(System.currentTimeMillis()).build())
);
} catch (CustomNoTokenException e) {
response.setStatus(500);
response.setContentType("application/json;charset=utf-8");
response.getWriter().println(
om.writeValueAsString(CustomErrorResponse.builder()
.code(ErrorCode.NO_TOKEN.getCode())
.body(ErrorCode.NO_TOKEN.getBody())
.errTime(System.currentTimeMillis()).build())
);
} catch (ServletException e) {
response.setStatus(500);
response.setContentType("application/json;charset=utf-8");
response.getWriter().println(
om.writeValueAsString(CustomErrorResponse.builder()
.code(ErrorCode.INTERNAL_SERVER_ERROR.getCode())
.body(ErrorCode.INTERNAL_SERVER_ERROR.getBody())
.errTime(System.currentTimeMillis()).build())
);
} catch (IOException e) {
response.setStatus(500);
response.setContentType("application/json;charset=utf-8");
response.getWriter().println(
om.writeValueAsString(CustomErrorResponse.builder()
.code(ErrorCode.INTERNAL_SERVER_ERROR.getCode())
.body(ErrorCode.INTERNAL_SERVER_ERROR.getBody())
.errTime(System.currentTimeMillis()).build())
);
} catch (CustomMalformedJwtException e) {
response.setStatus(500);
response.setContentType("application/json;charset=utf-8");
response.getWriter().println(
om.writeValueAsString(CustomErrorResponse.builder()
.code(ErrorCode.MALFORMED_JWT.getCode())
.body(ErrorCode.MALFORMED_JWT.getBody())
.errTime(System.currentTimeMillis()).build())
);
} catch (CustomNoSameIpException e) {
response.setStatus(500);
response.setContentType("application/json;charset=utf-8");
response.getWriter().println(
om.writeValueAsString(CustomErrorResponse.builder()
.code(ErrorCode.NO_SAME_IP.getCode())
.body(ErrorCode.NO_SAME_IP.getBody())
.errTime(System.currentTimeMillis()).build())
);
}
}
}

View File

@@ -0,0 +1,33 @@
package com.kospo.drm.config.jasypt;
import com.ulisesbocchio.jasyptspringboot.annotation.EnableEncryptableProperties;
import org.jasypt.encryption.StringEncryptor;
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableEncryptableProperties
public class JasyptConfigAES {
@Value("${jasypt.encryptor.key}")
private String key;
@Bean("jasyptEncryptorAES")
public StringEncryptor stringEncryptor() {
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
config.setPassword(key); // 암호화키
config.setAlgorithm("PBEWITHHMACSHA512ANDAES_256"); // 알고리즘
config.setKeyObtentionIterations("1000"); // 반복할 해싱 회수
config.setPoolSize("1"); // 인스턴스 pool
config.setProviderName("SunJCE");
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator"); // salt 생성 클래스
config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator");
config.setStringOutputType("base64"); //인코딩 방식
encryptor.setConfig(config);
return encryptor;
}
}

View File

@@ -0,0 +1,54 @@
package com.kospo.drm.config.jwt;
import com.kospo.drm.config.exception.ErrorCode;
import com.kospo.drm.exception.CustomExpireTokenException;
import com.kospo.drm.exception.CustomMalformedJwtException;
import com.kospo.drm.exception.CustomNoMatchTokenException;
import com.kospo.drm.repository.TokenRepository;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.io.DecodingException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@RequiredArgsConstructor
public class JwtRequestFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final TokenRepository tokenRepository;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
boolean isAnonymous = false;
String domain = null;
String jwtToken = null;
if(requestTokenHeader != null && requestTokenHeader.startsWith("Bearer")) {
jwtToken = requestTokenHeader.substring(7);
try {
domain = jwtUtil.extractDomain(jwtToken);
} catch(ExpiredJwtException e) {
System.out.println(e.getMessage());
throw new CustomExpireTokenException(e.getMessage());
} catch (DecodingException e) {
System.out.println(e.getMessage());
throw new CustomNoMatchTokenException(e.getMessage());
} catch(MalformedJwtException e) {
System.out.println(e.getMessage());
throw new CustomMalformedJwtException(e.getMessage());
}
} else {
System.out.println("noToken");
isAnonymous = true;
}
// String ip = jwtUtil.extractIp(jwtToken);
// if(!CommonUtils.getClientIp(request).equals(ip)) {
// throw new CustomNoSameIpException(ErrorCode.NO_SAME_IP.getBody());
// }
filterChain.doFilter(request, response);
}
}

View File

@@ -0,0 +1,172 @@
package com.kospo.drm.config.jwt;
import com.kospo.drm.config.exception.ErrorCode;
import com.kospo.drm.config.utils.CommonUtils;
import com.kospo.drm.dto.res.TokenResponse;
import com.kospo.drm.exception.CustomException;
import com.kospo.drm.model.Token;
import io.jsonwebtoken.*;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtUtil {
@Value("${jwt.key}")
private String SECRET_KEY;
/* 토큰 사용자이름을 추출한다. */
public String extractDomain(String token) {
return extractClaim(token, Claims::getSubject);
}
/* 토큰 IP을 추출한다. */
public String extractIp(String token) {
return extractClaim(token, claims -> (String)claims.get("ip"));
}
/* 토큰 만료시간을 추출한다. */
public Date extractExpirationDate(String token) {
return extractClaim(token, Claims::getExpiration);
}
/* 토큰 정보를 추출한다. */
private <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
/* 토큰 모든 정보를 추출한다. */
private Claims extractAllClaims(String token) {
return Jwts.parserBuilder().setSigningKey(SECRET_KEY).build().parseClaimsJws(token).getBody();
}
/* 토큰 만료여부 확인. */
private Boolean isTokenExpired(String token) {
return extractExpirationDate(token).before(new Date());
}
/* 토큰 엑서스 토큰을 생성한다. */
public String generateAccessToken(Token token, Date expired) {
RequestAttributes reqAttr = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes servlReqAttr = (ServletRequestAttributes)reqAttr;
HttpServletRequest req = servlReqAttr.getRequest();
Map<String, Object> claims = new HashMap<>();
claims.put("domain", token.getId().getDomain());
claims.put("ip", token.getId().getIp());
return createAccessToken(claims, token.getId().getDomain(), expired);
}
/* 토큰 엑서스 토큰을 생성한다. */
private String createAccessToken(Map<String, Object> claims, String subject, Date expired) {
return Jwts.builder().setClaims(claims).setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(expired) // 5minutes
.signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();
}
/* 토큰 리플래쉬토큰을 생성한다. */
public String generateRefreshToken(Token token, Date expired) {
return createRefreshToken(token.getId().getDomain(), expired);
}
/* 토큰 리플래쉬토큰을 생성한다. */
private String createRefreshToken(String subject, Date expired) {
return Jwts.builder().setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(expired) // 30minutes
.signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();
}
/* 토큰 생성(엑서스, 리플레시) 동시 생성. */
public TokenResponse generateToken(Token token) {
return TokenResponse.builder()
.accessToken(generateAccessToken(token, getAccessDate()))
.accessTokenExpired(getAccessDate().getTime())
// .refreshToken(generateRefreshToken(token, getRefreshDate()))
// .refreshTokenExpired(getRefreshDate().getTime())
.domain(token.getId().getDomain()).build();
}
/* 토큰 리플레쉬토큰으로 엑서스토큰을 재발급한다. */
public TokenResponse renewalToken(String accessToken, Token token) {
if(validateToken(accessToken, token)) {
Claims claims = extractAllClaims(accessToken);
return TokenResponse.builder()
.accessToken(generateAccessToken(token, getAccessDate()))
.accessTokenExpired(getAccessDate().getTime())
// .refreshToken(accessToken)
// .refreshTokenExpired(claims.getExpiration().getTime())
.domain(token.getId().getDomain()).build();
} else {
throw new CustomException(ErrorCode.EXPIRE_TOKEN);
}
}
/*토큰 정보로 jws 정보를 맵핑한다..*/
private Jws<Claims> parseToken(String token) {
return Jwts.parserBuilder().setSigningKey(SECRET_KEY).build().parseClaimsJws(token);
}
/* 토큰 리플레시토큰 만료기간을 연장한다. */
public TokenResponse extendRefreshToken(String refreshToken) {
Date refreshDate = getRefreshDate();
Jws<Claims> jws = parseToken(refreshToken);
jws.getBody().setExpiration(refreshDate).getSubject();
return TokenResponse.builder()
.refreshToken(refreshToken)
.refreshTokenExpired(refreshDate.getTime()).build();
}
/* 토큰 액세스토큰 만료기간을 연장한다. */
public TokenResponse extendAccessToken(String AccessToken) {
Date accessDate = getAccessDate();
Jws<Claims> jws = parseToken(AccessToken);
jws.getBody().setExpiration(accessDate).getSubject();
return TokenResponse.builder()
.accessToken(AccessToken)
.accessTokenExpired(accessDate.getTime()).build();
}
/* 토큰 만료 여부 확인. */
public Boolean validateToken(String accessToken, Token token) {
try {
final String domain = extractDomain(accessToken);
return (domain.equals(token.getId().getDomain()) && !isTokenExpired(accessToken));
} catch(ExpiredJwtException e) {
return false;
}
}
/*리플레시토큰 만료기간 생성 30분*/
private Date getRefreshDate() {
return new Date(System.currentTimeMillis() + (1000 * 60 * 60 * 4));
}
/*엑서스토큰 만료기간 생성 5분*/
private Date getAccessDate() {
Calendar cal = Calendar.getInstance();
Date dt = new Date();
cal.setTime(dt);
cal.add(Calendar.YEAR, 30);
return cal.getTime();
}
}

View File

@@ -0,0 +1,40 @@
package com.kospo.drm.config.security;
import org.postgresql.shaded.com.ongres.scram.common.bouncycastle.pbkdf2.SHA256Digest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.codec.Hex;
import org.springframework.security.crypto.password.*;
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
public class PasswordEncoder {
public String sha256Encoding(String password) {
String encPassword = "";
MessageDigest digest = null;
try {
digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(password.getBytes(StandardCharsets.UTF_8));
encPassword = new String(Hex.encode(hash));
return encPassword;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public boolean matches(String passwd1, String passwd2) {
if(passwd2.equals(sha256Encoding(passwd1))) {
return true;
} else {
return false;
}
}
}

View File

@@ -0,0 +1,114 @@
package com.kospo.drm.config.security;
import com.kospo.drm.config.filter.CustomBasicAuthenticationFilter;
import com.kospo.drm.config.filter.SecurityExceptionFilter;
import com.kospo.drm.config.jwt.JwtUtil;
import com.kospo.drm.repository.TokenRepository;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import java.util.List;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtUtil jwtUtil;
private final TokenRepository tokenRepository;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http,
TokenRepository tokenRepository) throws Exception {
http
.authorizeHttpRequests(authorizationManagerRequestMatcherRegistry -> authorizationManagerRequestMatcherRegistry
.requestMatchers("/admin*").hasRole("ADMIN")
.anyRequest().authenticated()
)
.csrf(AbstractHttpConfigurer::disable)
.formLogin(httpSecurityFormLoginConfigurer -> httpSecurityFormLoginConfigurer.disable())
.addFilterBefore(new CustomBasicAuthenticationFilter(authenticationManager(), tokenRepository, jwtUtil), UsernamePasswordAuthenticationFilter.class)
// .addFilterBefore(new SecurityExceptionFilter(), JwtRequestFilter.class)
.addFilterBefore(new SecurityExceptionFilter(), CustomBasicAuthenticationFilter.class)
.headers(httpSecurityHeadersConfigurer -> httpSecurityHeadersConfigurer.frameOptions(frameOptionsConfig -> frameOptionsConfig.disable()))
.sessionManagement(httpSecuritySessionManagementConfigurer ->
httpSecuritySessionManagementConfigurer
.maximumSessions(1)
.sessionRegistry(sessionRegistry())
)
.cors(httpSecurityCorsConfigurer -> httpSecurityCorsConfigurer.configurationSource(new CorsConfigurationSource() {
@Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
CorsConfiguration config = new CorsConfiguration();
// String origin = request.getHeader("Origin");
config.setAllowCredentials(true);
config.setAllowedOrigins(List.of("https://dmz.hmsn.ink","http://localhost:3000", "http://hmsn.ink:3000", "http://hmsn.ink:8010", "http://hmsn.ink", "https://svcm.hmsn.ink", "http://182.227.15.92:3000"));
config.setAllowedMethods(List.of("POST","GET","DELETE","PUT","OPTIONS"));
config.setAllowedHeaders(List.of("*"));
config.setExposedHeaders(List.of("*"));
return config;
}
}));
return http.build();
}
private String getEncoding (String str) {
BCryptPasswordEncoder b = new BCryptPasswordEncoder();
return b.encode(str);
}
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Bean
public AuthenticationManager authenticationManager() {
AuthenticationManager manager = new AuthenticationManager() {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
return authentication;
}
};
return manager;
}
/*특정 url 필터 제외 처리*/
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers(
"/api/token/generate", "/api/token"
// -- Swagger UI v2
, "/v2/api-docs/**", "/swagger-resources/**", "/v3/api-docs/**"
, "/swagger-ui/**", "/swagger/**", "/api-docs/**"
);
}
@Bean
public SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(sessionRegistry());
}
}

View File

@@ -0,0 +1,6 @@
package com.kospo.drm.config.security;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class SecurityWebInitializer extends AbstractSecurityWebApplicationInitializer {
}

View File

@@ -0,0 +1,45 @@
package com.kospo.drm.config.swagger;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SwaggerConfig {
private final String JWTSCHEMENAME = "jwtAuth";
private SecurityRequirement securityRequirement = new SecurityRequirement().addList(JWTSCHEMENAME);
@Bean
public OpenAPI openAPI() {
// return new OpenAPI()
// .info(apiInfo())
// .components(new Components().addSecuritySchemes("basicScheme", new SecurityScheme()
// .type(SecurityScheme.Type.HTTP).scheme("basic")));
// }
return new OpenAPI()
.info(apiInfo())
.addSecurityItem(securityRequirement)
.components(new Components()
.addSecuritySchemes(JWTSCHEMENAME, new SecurityScheme()
.name(JWTSCHEMENAME)
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
.name("Authentication"))
);
}
private Info apiInfo() {
return new Info()
.title("소액계약거래")
.description("소액계약거래 api")
.version("1.0.0");
}
}

View File

@@ -0,0 +1,144 @@
package com.kospo.drm.config.utils;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Sort;
import javax.xml.bind.DatatypeConverter;
import java.io.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@Slf4j
public class CommonUtils {
private static HashMap<String, Set<HashMap<String, Integer>>> listenings;
public CommonUtils() {
}
public static String getClientIp(HttpServletRequest request) {
String ip = "";
ip = request.getHeader("X-FORWARDED-FOR");
if (ip == null) {
ip = request.getHeader("Proxy-Client-IP");
log.info("Proxy-Client-IP : " + ip);
}
if (ip == null) {
ip = request.getHeader("WL-Proxy-Client-IP");
log.info("WL-Proxy-Client-IP : " + ip);
}
if (ip == null) {
ip = request.getHeader("HTTP_CLIENT_IP");
log.info("HTTP_CLIENT_IP : " + ip);
}
if (ip == null) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
log.info("HTTP_X_FORWARDED_FOR : " + ip);
}
if (ip == null) {
ip = request.getRemoteAddr();
log.info("getRemoteAddr : "+ip);
}
log.info("Result : IP Address : "+ip);
return ip;
}
public static Sort convertSort(String field) {
String[] propertyAndDirection = field.split(",");
if(propertyAndDirection.length == 1) propertyAndDirection = field.split(" ");
String property = propertyAndDirection[0];
Sort.Direction direction = Sort.DEFAULT_DIRECTION;
if(propertyAndDirection.length > 1) {
String directionString = propertyAndDirection[1];
direction = Sort.Direction.fromOptionalString(directionString)
.orElse(Sort.DEFAULT_DIRECTION);
}
Sort.Order order = new Sort.Order(direction, property);
return Sort.by(order);
}
public static String dateFormat(Date setDate, String pattern) {
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
return sdf.format(setDate);
}
public static String dateFormat(String pattern) {
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
return sdf.format(new Date());
}
public static Date plusDays(Date dt, int day) {
Calendar cal = Calendar.getInstance();
cal.setTime(dt);
cal.add(Calendar.DAY_OF_MONTH, day);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
System.out.println(sdf.format(cal.getTime()));
return cal.getTime();
}
public static String stringToPlusYear(String str, int year) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date dt = null;
try {
dt = sdf.parse(str);
} catch (ParseException e) {
throw new RuntimeException(e);
}
Calendar cal = Calendar.getInstance();
cal.setTime(dt);
cal.add(Calendar.YEAR, year);
SimpleDateFormat result = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return result.format(cal.getTime());
}
public static String stringToPlusDay(String str, String pattern, int day) {
SimpleDateFormat sdf = new SimpleDateFormat( pattern);
Date dt = null;
try {
dt = sdf.parse(str);
} catch (ParseException e) {
throw new RuntimeException(e);
}
Calendar cal = Calendar.getInstance();
cal.setTime(dt);
cal.add(Calendar.DAY_OF_MONTH, day);
SimpleDateFormat result = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return result.format(cal.getTime());
}
public static Date stringToDate(String dd) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
Date result = null;
try {
result = sdf.parse(dd);
System.out.println(sdf.format(result));
} catch (ParseException e) {
throw new RuntimeException(e);
}
return result;
}
public static Date stringToDate(String dd, String pattern) {
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
Date result = null;
try {
result = sdf.parse(dd);
System.out.println(sdf.format(result));
} catch (ParseException e) {
throw new RuntimeException(e);
}
return result;
}
}

View File

@@ -0,0 +1,89 @@
package com.kospo.drm.config.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.FileCopyUtils;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAKeyGenParameterSpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class CryptoUtil {
public static String encrypt(String plainText) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, loadPublicKey());
byte[] encryptedBytes = cipher.doFinal(plainText.getBytes());
return Base64.getEncoder().encodeToString(encryptedBytes);
}
public static String decrypt(String encryptText) throws Exception {
encryptText = encryptText.replaceAll(" ", "+");
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, loadPrivateKey());
byte[] decryptBytes = cipher.doFinal(Base64.getDecoder().decode(encryptText));
return new String(decryptBytes);
}
private static PublicKey loadPublicKey() throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
ClassPathResource resource = new ClassPathResource("public.key");
byte[] keyBytes = FileCopyUtils.copyToByteArray(resource.getInputStream());
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(spec);
}
private static PrivateKey loadPrivateKey() throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
ClassPathResource resource = new ClassPathResource("private.key");
byte[] keyBytes = FileCopyUtils.copyToByteArray(resource.getInputStream());
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(spec);
}
private static byte[] readKeyFromFile(String fileName) {
File file = new File(fileName);
byte[] keyBytes = new byte[(int) file.length()];
try(FileInputStream fis = new FileInputStream(file)) {
fis.read(keyBytes);
} catch (IOException e) {
throw new RuntimeException(e);
}
return keyBytes;
}
private static void saveKeyToFile(String fileName, byte[] keyBytes) {
File file = new File(fileName);
try(FileOutputStream fos = new FileOutputStream(file)) {
fos.write(keyBytes);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
// System.out.println(encrypt("psn14020"));
// KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA");
// keygen.initialize(new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4));
// KeyPair pair = keygen.generateKeyPair();
//
// saveKeyToFile("C:\\Users\\psn14020\\IdeaProjects\\talk\\src\\main\\resources\\private.key", pair.getPrivate().getEncoded());
// saveKeyToFile("C:\\Users\\psn14020\\IdeaProjects\\talk\\src\\main\\resources\\public.key", pair.getPublic().getEncoded());
}
}

View File

@@ -0,0 +1,70 @@
package com.kospo.drm.config.utils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import javax.xml.bind.DatatypeConverter;
import java.awt.image.BufferedImage;
import java.io.*;
@Component
public class FileUtils {
public synchronized boolean fileUpload(String full, String base64) {
boolean result = false;
File file = new File(full);
BufferedOutputStream bos = null;
try {
byte[] data = DatatypeConverter.parseBase64Binary(base64);
bos = new BufferedOutputStream(new FileOutputStream(file));
bos.write(data);
bos.flush();
result = true;
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (bos != null) {
try {
bos.close();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
return result;
}
public synchronized boolean fileDelete(String full) {
File file = new File(full);
if(!file.exists()) {
return false;
} else {
file.delete();
return true;
}
}
public synchronized byte[] fileRead(String full) {
File file = new File(full);
if(file.exists()) {
BufferedImage originalImage = null;
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
return fis.readAllBytes();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return null;
}
}

View File

@@ -0,0 +1,42 @@
package com.kospo.drm.config.utils;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Marshaller;
import jakarta.xml.bind.Unmarshaller;
import org.springframework.context.annotation.Configuration;
import javax.xml.bind.DatatypeConverter;
import java.awt.image.BufferedImage;
import java.io.*;
@Configuration
public class XmlUtils {
public Object xmlToModel(String xml , Class cla) throws JAXBException, IOException, NoSuchMethodException {
ByteArrayInputStream bais = new ByteArrayInputStream(xml.getBytes());
JAXBContext jaxbContext = JAXBContext.newInstance(cla);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
Object obj = unmarshaller.unmarshal(bais);
bais.close();
return obj;
}
public String modelToXml(Object obj) throws JAXBException, IOException {
JAXBContext jaxbContext = JAXBContext.newInstance(obj.getClass());
Marshaller marshaller = jaxbContext.createMarshaller();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(obj, baos);
String list = baos.toString();
baos.close();
return list;
}
}

View File

@@ -0,0 +1,89 @@
package com.kospo.drm.controller;
import com.kospo.drm.config.exception.CustomErrorResponse;
import com.kospo.drm.dto.req.DrmRequest;
import com.kospo.drm.dto.req.TokenRequest;
import com.kospo.drm.dto.res.DrmResponse;
import com.kospo.drm.dto.res.TokenResponse;
import com.kospo.drm.service.DrmService;
import com.kospo.drm.service.TokenService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@Tag(name = "Drm Api", description = "drm 관리")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api")
public class DrmController {
private final TokenService tokenService;
private final DrmService drmService;
@Operation(summary = "복호화", description = "문서파일 복호화")
@ApiResponses({
@ApiResponse(description = "Success", responseCode = "200",
content = @Content(mediaType = "application/json", schema = @Schema(oneOf =
{DrmResponse.class }))),
@ApiResponse(description = "Not found", responseCode = "404",
content = @Content(mediaType = "text/plain", schema = @Schema(oneOf =
{String.class}))),
@ApiResponse(description = "Internal Error", responseCode = "500",
content = @Content(mediaType = "application/json", schema = @Schema(oneOf =
{CustomErrorResponse.class })))
})
@PostMapping("/drm/decrypt")
public ResponseEntity decrypt(
HttpServletRequest request,
@RequestBody DrmRequest drmRequest
) {
return ResponseEntity.ok(drmService.decrypt(drmRequest));
}
@Operation(summary = "도메인 등록", description = "토큰을 생성한다.")
@ApiResponses({
@ApiResponse(description = "Success", responseCode = "200",
content = @Content(mediaType = "application/json", schema = @Schema(oneOf =
{TokenResponse.class }))),
@ApiResponse(description = "Not found", responseCode = "404",
content = @Content(mediaType = "text/plain", schema = @Schema(oneOf =
{String.class}))),
@ApiResponse(description = "Internal Error", responseCode = "500",
content = @Content(mediaType = "application/json", schema = @Schema(oneOf =
{CustomErrorResponse.class })))
})
@PostMapping("/token/generate")
public ResponseEntity generate(
HttpServletRequest request,
@RequestBody TokenRequest tokenRequest
) {
return ResponseEntity.ok(tokenService.generateToken(tokenRequest));
}
@Operation(summary = "토큰 조회", description = "도메인 아이피로 토큰 조회")
@ApiResponses({
@ApiResponse(description = "Success", responseCode = "200",
content = @Content(mediaType = "application/json", schema = @Schema(oneOf =
{TokenResponse.class }))),
@ApiResponse(description = "Not found", responseCode = "404",
content = @Content(mediaType = "text/plain", schema = @Schema(oneOf =
{String.class}))),
@ApiResponse(description = "Internal Error", responseCode = "500",
content = @Content(mediaType = "application/json", schema = @Schema(oneOf =
{CustomErrorResponse.class })))
})
@PostMapping("/token")
public ResponseEntity search(
HttpServletRequest request,
@RequestBody TokenRequest tokenRequest
) {
return ResponseEntity.ok(tokenService.generateToken(tokenRequest));
}
}

View File

@@ -0,0 +1,30 @@
package com.kospo.drm.dto.req;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.List;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "암호화 목록")
public class DrmRequest {
@Schema(description = "첨부파일명")
private List<EncryptFile> encryptFiles;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "암호화 파일")
public static class EncryptFile {
@Schema(description = "첨부파일명")
private String fileName;
@Schema(description = "첨부파일(byte)")
private String data;
}
}

View File

@@ -0,0 +1,17 @@
package com.kospo.drm.dto.req;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "토큰 생성")
public class TokenRequest {
@Schema(description = "도메인")
private String domain;
@Schema(description = "아이피")
private String ip;
}

View File

@@ -0,0 +1,31 @@
package com.kospo.drm.dto.res;
import com.kospo.drm.dto.req.DrmRequest;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.List;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "복호화 목록")
public class DrmResponse {
@Schema(description = "첨부파일명")
private List<DrmResponse.DecryptFile> decryptFiles;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "복호화 파일")
public static class DecryptFile {
@Schema(description = "첨부파일명")
private String fileName;
@Schema(description = "첨부파일(byte)")
private String data;
}
}

View File

@@ -0,0 +1,40 @@
package com.kospo.drm.dto.res;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.kospo.drm.model.Token;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
@Schema(name = "TokenResponse", description = "토큰")
public class TokenResponse implements Serializable {
@Schema(name = "domain", description = "도메인주소")
private String domain;
@Schema(name = "accessToken", description = "엑서스 토큰")
private String accessToken;
@Schema(name = "accessTokenExpired", description = "엑서스 토큰 만료시간")
private Long accessTokenExpired;
@Schema(name = "refreshToken", description = "리플레시 토큰")
private String refreshToken;
@Schema(name = "refreshTokenExpired", description = "리플레시 토큰 만료시간")
private Long refreshTokenExpired;
public static TokenResponse from(Token token) {
return TokenResponse.builder()
.accessToken(token.getToken())
.domain(token.getId().getDomain())
.accessTokenExpired(token.getTokenExpire())
.build();
}
}

View File

@@ -0,0 +1,14 @@
package com.kospo.drm.exception;
import com.kospo.drm.config.exception.ErrorCode;
import lombok.*;
@Getter
public class CustomException extends RuntimeException{
private ErrorCode errorCode;
public CustomException(ErrorCode errorCode) {
this.errorCode = errorCode;
}
}

View File

@@ -0,0 +1,7 @@
package com.kospo.drm.exception;
public class CustomExpireTokenException extends RuntimeException{
public CustomExpireTokenException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,7 @@
package com.kospo.drm.exception;
public class CustomMalformedJwtException extends RuntimeException{
public CustomMalformedJwtException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,25 @@
package com.kospo.drm.exception;
import com.kospo.drm.config.exception.ErrorCode;
import lombok.*;
@Getter
public class CustomMessageException extends RuntimeException{
private CusMessage cusMessage;
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public static class CusMessage {
String code;
String message;
}
public CustomMessageException(CusMessage cusMessage) {
this.cusMessage = cusMessage;
}
}

View File

@@ -0,0 +1,7 @@
package com.kospo.drm.exception;
public class CustomNoAccountException extends RuntimeException{
public CustomNoAccountException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,7 @@
package com.kospo.drm.exception;
public class CustomNoMatchTokenException extends RuntimeException{
public CustomNoMatchTokenException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,7 @@
package com.kospo.drm.exception;
public class CustomNoResultException extends RuntimeException{
public CustomNoResultException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,7 @@
package com.kospo.drm.exception;
public class CustomNoSameIpException extends RuntimeException{
public CustomNoSameIpException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,7 @@
package com.kospo.drm.exception;
public class CustomNoTokenException extends RuntimeException{
public CustomNoTokenException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,33 @@
package com.kospo.drm.model;
import jakarta.persistence.Column;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import jakarta.validation.constraints.Size;
import lombok.*;
import java.sql.Timestamp;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "token")
public class Token {
@EmbeddedId
private TokenId id;
@Size(max = 500)
@Column(name = "token", length = 500)
private String token;
@Column(name = "token_expire")
private long tokenExpire;
@Column(name = "ins_date")
private Timestamp insDate;
}

View File

@@ -0,0 +1,45 @@
package com.kospo.drm.model;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.*;
import org.hibernate.Hibernate;
import java.io.Serializable;
import java.util.Objects;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Embeddable
public class TokenId implements Serializable {
private static final long serialVersionUID = -5489321559837728499L;
@Size(max = 200)
@NotNull
@Column(name = "domain", nullable = false, length = 200)
private String domain;
@Size(max = 20)
@NotNull
@Column(name = "ip", nullable = false, length = 20)
private String ip;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
TokenId entity = (TokenId) o;
return Objects.equals(this.domain, entity.domain) &&
Objects.equals(this.ip, entity.ip);
}
@Override
public int hashCode() {
return Objects.hash(domain, ip);
}
}

View File

@@ -0,0 +1,8 @@
package com.kospo.drm.repository;
import com.kospo.drm.model.Token;
import com.kospo.drm.model.TokenId;
import org.springframework.data.jpa.repository.JpaRepository;
public interface TokenRepository extends JpaRepository<Token, TokenId> {
}

View File

@@ -0,0 +1,8 @@
package com.kospo.drm.service;
import com.kospo.drm.dto.req.DrmRequest;
import com.kospo.drm.dto.res.DrmResponse;
public interface DrmService {
DrmResponse decrypt(DrmRequest drmRequest);
}

View File

@@ -0,0 +1,8 @@
package com.kospo.drm.service;
import com.kospo.drm.dto.req.TokenRequest;
import com.kospo.drm.dto.res.TokenResponse;
public interface TokenService {
TokenResponse generateToken(TokenRequest tokenRequest);
}

View File

@@ -0,0 +1,56 @@
package com.kospo.drm.service.impl;
import com.kospo.drm.dto.req.DrmRequest;
import com.kospo.drm.dto.res.DrmResponse;
import com.kospo.drm.service.DrmService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
@Service
@RequiredArgsConstructor
public class DrmServiceImpl implements DrmService {
@Value("${server.drm.attach}")
private String drmAttach;
@Override
public DrmResponse decrypt(DrmRequest drmRequest) {
List<DrmResponse.DecryptFile> decryptFiles = drmRequest.getEncryptFiles().stream().map(en -> {
String fileName = en.getFileName();
String data = en.getData();
String[] reData = data.split(",");
byte[] decodedFile = Base64.getDecoder()
.decode(reData[1].getBytes(StandardCharsets.UTF_8));
String psyFileName = UUID.randomUUID().toString();
Path encryptFile = Paths.get(drmAttach + "/enc", psyFileName);
Path decryptFile = Paths.get(drmAttach + "/dec", psyFileName);
String resultData = "";
try {
Files.write(encryptFile, decodedFile);
/*복호화 로직 시작*/
/*복호화 로직 종료*/
Files.copy(encryptFile, decryptFile);
resultData = Base64.getEncoder().encodeToString(Files.readAllBytes(decryptFile));
} catch (IOException e) {
throw new RuntimeException(e);
}
return DrmResponse.DecryptFile.builder().fileName(fileName).data(resultData).build();
}).toList();
return DrmResponse.builder().decryptFiles(decryptFiles).build();
}
}

View File

@@ -0,0 +1,53 @@
package com.kospo.drm.service.impl;
import com.kospo.drm.config.exception.ErrorCode;
import com.kospo.drm.config.jwt.JwtUtil;
import com.kospo.drm.dto.req.TokenRequest;
import com.kospo.drm.dto.res.TokenResponse;
import com.kospo.drm.exception.CustomException;
import com.kospo.drm.model.Token;
import com.kospo.drm.model.TokenId;
import com.kospo.drm.repository.TokenRepository;
import com.kospo.drm.service.TokenService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.sql.Timestamp;
import java.util.Date;
import java.util.Optional;
@Service
@RequiredArgsConstructor
public class TokenServiceImpl implements TokenService {
private final TokenRepository tokenRepository;
private final JwtUtil jwtUtil;
@Override
public TokenResponse generateToken(TokenRequest tokenRequest) {
Token token = Token.builder().id(
TokenId.builder()
.domain(tokenRequest.getDomain())
.ip(tokenRequest.getIp())
.build()
).build()
;
TokenResponse tokenResponse = jwtUtil.generateToken(token);
token.setToken(tokenResponse.getAccessToken());
token.setInsDate(new Timestamp(new Date().getTime()));
tokenRepository.save(token);
return tokenResponse;
}
public TokenResponse findToken(TokenRequest tokenRequest) {
Optional<Token> optionalToken = tokenRepository.findById(TokenId.builder()
.domain(tokenRequest.getDomain())
.ip(tokenRequest.getIp())
.build());
if(optionalToken.isEmpty()) throw new CustomException(ErrorCode.AUTHENTICATION);
return TokenResponse.from(optionalToken.get());
}
}

View File

@@ -0,0 +1,5 @@
package javax.servlet.http;
public interface HttpServletRequest extends jakarta.servlet.http.HttpServletRequest {
}

View File

@@ -0,0 +1,5 @@
package javax.servlet.http;
public interface HttpServletResponse extends jakarta.servlet.http.HttpServletResponse {
}

View File

@@ -0,0 +1,4 @@
package javax.servlet.http;
public interface HttpSession extends jakarta.servlet.http.HttpSession {
}

View File

@@ -0,0 +1,135 @@
package javax.servlet.http;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Hashtable;
import java.util.ResourceBundle;
import java.util.StringTokenizer;
import jakarta.servlet.ServletInputStream;
/** @deprecated */
public class HttpUtils {
private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings";
private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.http.LocalStrings");
public HttpUtils() {
}
public static Hashtable<String, String[]> parseQueryString(String s) {
String[] valArray = null;
if (s == null) {
throw new IllegalArgumentException();
} else {
Hashtable<String, String[]> ht = new Hashtable();
StringBuilder sb = new StringBuilder();
String key;
for(StringTokenizer st = new StringTokenizer(s, "&"); st.hasMoreTokens(); ht.put(key, valArray)) {
String pair = st.nextToken();
int pos = pair.indexOf(61);
if (pos == -1) {
throw new IllegalArgumentException();
}
key = parseName(pair.substring(0, pos), sb);
String val = parseName(pair.substring(pos + 1, pair.length()), sb);
if (!ht.containsKey(key)) {
valArray = new String[]{val};
} else {
String[] oldVals = (String[])ht.get(key);
valArray = new String[oldVals.length + 1];
for(int i = 0; i < oldVals.length; ++i) {
valArray[i] = oldVals[i];
}
valArray[oldVals.length] = val;
}
}
return ht;
}
}
public static Hashtable<String, String[]> parsePostData(int len, ServletInputStream in) {
if (len <= 0) {
return new Hashtable();
} else if (in == null) {
throw new IllegalArgumentException();
} else {
byte[] postedBytes = new byte[len];
try {
int offset = 0;
do {
int inputLen = in.read(postedBytes, offset, len - offset);
if (inputLen <= 0) {
String msg = lStrings.getString("err.io.short_read");
throw new IllegalArgumentException(msg);
}
offset += inputLen;
} while(len - offset > 0);
} catch (IOException e) {
throw new IllegalArgumentException(e.getMessage());
}
try {
String postedBody = new String(postedBytes, 0, len, "8859_1");
return parseQueryString(postedBody);
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException(e.getMessage());
}
}
}
private static String parseName(String s, StringBuilder sb) {
sb.setLength(0);
for(int i = 0; i < s.length(); ++i) {
char c = s.charAt(i);
switch (c) {
case '%':
try {
sb.append((char)Integer.parseInt(s.substring(i + 1, i + 3), 16));
i += 2;
} catch (NumberFormatException var6) {
throw new IllegalArgumentException();
} catch (StringIndexOutOfBoundsException var7) {
String rest = s.substring(i);
sb.append(rest);
if (rest.length() == 2) {
++i;
}
}
break;
case '+':
sb.append(' ');
break;
default:
sb.append(c);
}
}
return sb.toString();
}
public static StringBuffer getRequestURL(HttpServletRequest req) {
StringBuffer url = new StringBuffer();
String scheme = req.getScheme();
int port = req.getServerPort();
String urlPath = req.getRequestURI();
url.append(scheme);
url.append("://");
url.append(req.getServerName());
if (scheme.equals("http") && port != 80 || scheme.equals("https") && port != 443) {
url.append(':');
url.append(req.getServerPort());
}
url.append(urlPath);
return url;
}
}

View File

@@ -0,0 +1,4 @@
package javax.servlet.http;
public class ServletException extends jakarta.servlet.ServletException {
}

View File

@@ -0,0 +1,27 @@
import org.jasypt.encryption.StringEncryptor;
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
public class jwtSample {
private static final String SECRET_KEY = "5840f916c19111ee86fcf38ac250f21377907a8ac19111eebab6e7179f8e5c87";
public static void main(String[] args) {
System.out.println(stringEncryptor().decrypt("gLQTvX57MBGvHAhEGckIJbVdgG5w1YPO+bZk5+xOvg6DhAIcGcyXib8T605t2Icd"));
System.out.println(stringEncryptor().decrypt("KpBRMJPNmBe/zi0mPo32beSSXteAaSEp/Kf7dVV3ss8Qi4mS2bJ+P0eFw2Qs15sV"));
}
public static StringEncryptor stringEncryptor() {
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
config.setPassword("kospo2025"); // 암호화키
config.setAlgorithm("PBEWITHHMACSHA512ANDAES_256"); // 알고리즘
config.setKeyObtentionIterations("1000"); // 반복할 해싱 회수
config.setPoolSize("1"); // 인스턴스 pool
config.setProviderName("SunJCE");
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator"); // salt 생성 클래스
config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator");
config.setStringOutputType("base64"); //인코딩 방식
encryptor.setConfig(config);
return encryptor;
}
}

View File

@@ -0,0 +1,79 @@
jasypt:
encryptor:
key: kospo2025
bean: jasyptEncryptorAES
property:
prefix: ENC(
suffix: )
server:
address: 0.0.0.0
port: 8010
servlet:
encoding:
charset: UTF-8
enabled: true
force: true
session:
timeout: 1800s
drm:
attach: '/appl/drm/attach/'
jetty:
max-http-form-post-size: 100MB
spring:
application:
name: drm
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB
data:
rest:
detection-strategy: annotated
devtools:
livereload:
enabled: true
remote:
restart:
enabled: false
jpa:
show-sql: false
hibernate:
ddl-auto: validate
naming:
physical-strategy: org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy
open-in-view: false
mvc:
pathmatch:
matching-strategy: ant_path_matcher
datasource:
hikari:
username: ENC(gLQTvX57MBGvHAhEGckIJbVdgG5w1YPO+bZk5+xOvg6DhAIcGcyXib8T605t2Icd)
password: ENC(KpBRMJPNmBe/zi0mPo32beSSXteAaSEp/Kf7dVV3ss8Qi4mS2bJ+P0eFw2Qs15sV)
connection-timeout: 20000
maximum-pool-size: 30
data-source-properties:
cachePrepStmts: true
prepStmtCacheSize: 200
prepStmtCacheSqlLimit: 2048
useServerPrepStmts: true
url: jdbc:log4jdbc:postgresql://hmsn.ink:35432/svcm
driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
jwt:
key: 5840f916c19111ee86fcf38ac250f21377907a8ac19111eebab6e7179f8e5c87
springdoc:
packages-to-scan: com.kospo.drm
api-docs:
path: /api-docs
groups:
enabled: true
swagger-ui:
path: /swagger-ui.html
enabled: true
groups-order: ASC
tags-sorter: alpha
operations-sorter: alpha
display-request-duration: true
doc-expansion: none
cache:
disabled: true
model-and-view-allowed: true

View File

@@ -0,0 +1,79 @@
jasypt:
encryptor:
key: kospo2025
bean: jasyptEncryptorAES
property:
prefix: ENC(
suffix: )
server:
address: 0.0.0.0
port: 8010
servlet:
encoding:
charset: UTF-8
enabled: true
force: true
session:
timeout: 1800s
drm:
attach: 'C:/appl/drm/attach/'
jetty:
max-http-form-post-size: 100MB
spring:
application:
name: drm
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB
data:
rest:
detection-strategy: annotated
devtools:
livereload:
enabled: true
remote:
restart:
enabled: false
jpa:
show-sql: false
hibernate:
ddl-auto: validate
naming:
physical-strategy: org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy
open-in-view: false
mvc:
pathmatch:
matching-strategy: ant_path_matcher
datasource:
hikari:
username: ENC(gLQTvX57MBGvHAhEGckIJbVdgG5w1YPO+bZk5+xOvg6DhAIcGcyXib8T605t2Icd)
password: ENC(KpBRMJPNmBe/zi0mPo32beSSXteAaSEp/Kf7dVV3ss8Qi4mS2bJ+P0eFw2Qs15sV)
connection-timeout: 20000
maximum-pool-size: 30
data-source-properties:
cachePrepStmts: true
prepStmtCacheSize: 200
prepStmtCacheSqlLimit: 2048
useServerPrepStmts: true
url: jdbc:log4jdbc:postgresql://hmsn.ink:35432/svcm
driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
jwt:
key: 5840f916c19111ee86fcf38ac250f21377907a8ac19111eebab6e7179f8e5c87
springdoc:
packages-to-scan: com.kospo.drm
api-docs:
path: /api-docs
groups:
enabled: true
swagger-ui:
path: /swagger-ui.html
enabled: true
groups-order: ASC
tags-sorter: alpha
operations-sorter: alpha
display-request-duration: true
doc-expansion: none
cache:
disabled: true
model-and-view-allowed: true

View File

@@ -0,0 +1,79 @@
jasypt:
encryptor:
key: kospo2025
bean: jasyptEncryptorAES
property:
prefix: ENC(
suffix: )
server:
address: 0.0.0.0
port: 8010
servlet:
encoding:
charset: UTF-8
enabled: true
force: true
session:
timeout: 1800s
drm:
attach: '/appl/drm/attach/'
jetty:
max-http-form-post-size: 100MB
spring:
application:
name: drm
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB
data:
rest:
detection-strategy: annotated
devtools:
livereload:
enabled: true
remote:
restart:
enabled: false
jpa:
show-sql: false
hibernate:
ddl-auto: validate
naming:
physical-strategy: org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy
open-in-view: false
mvc:
pathmatch:
matching-strategy: ant_path_matcher
datasource:
hikari:
username: ENC(gLQTvX57MBGvHAhEGckIJbVdgG5w1YPO+bZk5+xOvg6DhAIcGcyXib8T605t2Icd)
password: ENC(KpBRMJPNmBe/zi0mPo32beSSXteAaSEp/Kf7dVV3ss8Qi4mS2bJ+P0eFw2Qs15sV)
connection-timeout: 20000
maximum-pool-size: 30
data-source-properties:
cachePrepStmts: true
prepStmtCacheSize: 200
prepStmtCacheSqlLimit: 2048
useServerPrepStmts: true
url: jdbc:log4jdbc:postgresql://hmsn.ink:35432/svcm
driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
jwt:
key: 5840f916c19111ee86fcf38ac250f21377907a8ac19111eebab6e7179f8e5c87
springdoc:
packages-to-scan: com.kospo.drm
api-docs:
path: /api-docs
groups:
enabled: true
swagger-ui:
path: /swagger-ui.html
enabled: true
groups-order: ASC
tags-sorter: alpha
operations-sorter: alpha
display-request-duration: true
doc-expansion: none
cache:
disabled: true
model-and-view-allowed: true

View File

@@ -0,0 +1,3 @@
spring:
profiles:
active: local

View File

@@ -0,0 +1,4 @@
log.config.path=./logs/
log.config.filename=api_log
log.level=debug

View File

@@ -0,0 +1,4 @@
log.config.path=./logs/
log.config.filename=api_log
log.level=warn

View File

@@ -0,0 +1,144 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 60초마다 설정 파일의 변경을 확인 하여 변경시 갱신 -->
<configuration scan="true" scanPeriod="60 seconds">
<!--springProfile 태그를 사용하면 logback 설정파일에서 복수개의 프로파일을 설정할 수 있다.-->
<springProfile name="prod">
<property resource="logback-prod.properties"/>
</springProfile>
<springProfile name="dev">
<property resource="logback-dev.properties"/>
</springProfile>
<!--Environment 내의 프로퍼티들을 개별적으로 설정할 수도 있다.-->
<!-- <springProperty scope="context" name="LOG_LEVEL" source="logging.level.root"/> -->
<!-- log level -->
<property name="LOG_LEVEL" value="${log.level}"/>
<!-- log file path -->
<property name="LOG_PATH" value="${log.config.path}"/>
<!-- log file name -->
<property name="LOG_FILE_NAME" value="${log.config.filename}"/>
<!-- err log file name -->
<property name="ERR_LOG_FILE_NAME" value="err_log"/>
<!-- pattern -->
<property name="LOG_PATTERN" value="%-5level %d{yy-MM-dd HH:mm:ss}[%thread] [%logger{0}:%line] - %msg%n"/>
<!-- Console Appender -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<filter class="com.kospo.drm.config.filter.LoggingFilter"/>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- File Appender -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="com.kospo.drm.config.filter.LoggingFilter"/>
<!-- 파일경로 설정 -->
<file>${LOG_PATH}/${LOG_FILE_NAME}.log</file>
<!-- 출력패턴 설정-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${LOG_PATTERN}</pattern>
</encoder>
<!-- Rolling 정책 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- .gz,.zip 등을 넣으면 자동 일자별 로그파일 압축 -->
<fileNamePattern>${LOG_PATH}/${LOG_FILE_NAME}.%d{yyyy-MM-dd}_%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- 파일당 최고 용량 kb, mb, gb -->
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!-- 일자별 로그파일 최대 보관주기(~일), 해당 설정일 이상된 파일은 자동으로 제거-->
<maxHistory>30</maxHistory>
<!--<MinIndex>1</MinIndex>
<MaxIndex>10</MaxIndex>-->
</rollingPolicy>
</appender>
<!-- 에러의 경우 파일에 로그 처리 -->
<appender name="Error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>error</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<file>${LOG_PATH}/${ERR_LOG_FILE_NAME}.log</file>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${LOG_PATTERN}</pattern>
</encoder>
<!-- Rolling 정책 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- .gz,.zip 등을 넣으면 자동 일자별 로그파일 압축 -->
<fileNamePattern>${LOG_PATH}/${ERR_LOG_FILE_NAME}.%d{yyyy-MM-dd}_%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- 파일당 최고 용량 kb, mb, gb -->
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!-- 일자별 로그파일 최대 보관주기(~일), 해당 설정일 이상된 파일은 자동으로 제거-->
<maxHistory>60</maxHistory>
</rollingPolicy>
</appender>
<!-- root레벨 설정 -->
<root level="${LOG_LEVEL}" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
<appender-ref ref="Error"/>
</root>
<!-- 특정패키지 로깅레벨 설정 -->
<logger name="org.springframework" level="WARN" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
<appender-ref ref="Error"/>
</logger>
<!-- log4jdbc 옵션 설정 -->
<logger name="jdbc" level="OFF" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
<appender-ref ref="Error"/>
</logger>
<!-- sql문만 로깅할지 여부 -->
<logger name="jdbc.sqlonly" level="${LOG_LEVEL}" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
<appender-ref ref="Error"/>
</logger>
<!-- 쿼리문 수행시간 로깅 여부 -->
<logger name="jdbc.sqltiming" level="OFF" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
<appender-ref ref="Error"/>
</logger>
<!-- ResultSet외 모든 JDBC 호출 정보 로깅할지 여부 -->
<logger name="jdbc.audit" level="OFF" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
<appender-ref ref="Error"/>
</logger>
<!-- ResultSet 포함 모든 JDBC 호출 정보를 로깅 -->
<logger name="jdbc.resultset" level="OFF" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
<appender-ref ref="Error"/>
</logger>
<logger name="jdbc.resultsettable" level="OFF" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
<appender-ref ref="Error"/>
</logger>
<!-- connection open close 로깅 여부 -->
<logger name="jdbc.connection" level="OFF" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
<appender-ref ref="Error"/>
</logger>
<logger name="com.zaxxer.hikari.pool.HikariProxyResultSet" level="OFF" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
<appender-ref ref="Error"/>
</logger>
</configuration>

Binary file not shown.

Binary file not shown.