From b552df62c3e536ed07c6a73176dabf07ce693a42 Mon Sep 17 00:00:00 2001 From: curry772 Date: Fri, 3 Apr 2026 17:22:56 +0900 Subject: [PATCH] =?UTF-8?q?config:=20local=20reposilite=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WEB-INF/properties/env.D.properties | 2 +- build.gradle | 8 +- docs/JWT_SIGNATURE_VERIFICATION.md | 252 ++++++++++++++++++ elink-online-common | 2 +- elink-online-core-jpa | 2 +- gradle/wrapper/gradle-wrapper.properties | 3 +- reposilite.md | 104 ++++++++ src/main/resources/logback.xml | 42 --- 8 files changed, 367 insertions(+), 48 deletions(-) create mode 100644 docs/JWT_SIGNATURE_VERIFICATION.md create mode 100644 reposilite.md delete mode 100644 src/main/resources/logback.xml diff --git a/WebContent/WEB-INF/properties/env.D.properties b/WebContent/WEB-INF/properties/env.D.properties index 0bd3db5..ad663d6 100644 --- a/WebContent/WEB-INF/properties/env.D.properties +++ b/WebContent/WEB-INF/properties/env.D.properties @@ -11,7 +11,7 @@ hibernate.dialect=org.hibernate.dialect.Oracle12cDialect # eLink default functions # set System property in real system #inst.Name=${HOSTNAME} -inst.Name=agwSvr11 +inst.Name=agwSvr63 eai.jdbc.Name=jdbc/dsOBP_AGW eai.rmiport=30111 eai.rmiserviceport=30112 diff --git a/build.gradle b/build.gradle index 0f815bb..567572a 100644 --- a/build.gradle +++ b/build.gradle @@ -20,13 +20,17 @@ def queryDslVersion = "5.0.0" def hibernateVersion = "5.6.15.Final" def generatedJavaDir = "$buildDir/generated/java" -def nexusUrl = "https://nexus.eactive.synology.me:8090" +def reposiliteUrl = "http://localhost:8080" allprojects { repositories { maven { - url "${nexusUrl}/repository/maven-public/" + url "${reposiliteUrl}/private" allowInsecureProtocol = true + credentials { + username = reposiliteUser + password = reposilitePassword + } } } } diff --git a/docs/JWT_SIGNATURE_VERIFICATION.md b/docs/JWT_SIGNATURE_VERIFICATION.md new file mode 100644 index 0000000..c4e19a0 --- /dev/null +++ b/docs/JWT_SIGNATURE_VERIFICATION.md @@ -0,0 +1,252 @@ +# JWT 서명 검증 및 인증서 관리 + +## 1. 전체 구조 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Authorization Server (elink-oauth) │ +│ elink-oauth-dev.jks │ +│ ├── 개인키 (RSA Private Key) ← JWT 서명 생성용 │ +│ └── 공개키 인증서 (Certificate) │ +└─────────────────────────────────────────────────────────────────┘ + │ 공개키만 추출 + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ API Gateway (ApiAuthFilter) │ +│ elink-oauth-dev.pub │ +│ └── 공개키만 (RSA Public Key) ← JWT 서명 검증용만 │ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 2. JKS vs PUB 파일 차이점 + +| 항목 | `.jks` (Java KeyStore) | `.pub` (Public Key) | +|------|----------------------|---------------------| +| 형식 | 바이너리 (Java 전용) | PEM 텍스트 (`-----BEGIN PUBLIC KEY-----`) | +| 포함 내용 | 개인키 + 공개키 인증서 | 공개키만 | +| 보호 | KeyStore 비밀번호 + Key 비밀번호 | 없음 (공개 가능) | +| 사용처 | AuthorizationServerConfig - **토큰 서명** | ApiAuthFilter - **토큰 검증** | +| 코드 로딩 | `KeyStoreKeyFactory` → `KeyPair` | `X509EncodedKeySpec` → `RSAPublicKey` | +| 보안 등급 | **극비** (노출 시 토큰 위조 가능) | 공개 배포 가능 | + +### 코드에서의 사용 위치 + +```java +// AuthorizationServerConfig.java - JKS로 JWT 서명 +KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(keystore, keystorePassword.toCharArray()); +KeyPair keyPair = keyStoreKeyFactory.getKeyPair(keyAlias, keyPassword.toCharArray()); +converter.setKeyPair(keyPair); // 개인키로 JWT 서명 + +// ApiAuthFilter.java - PUB로 JWT 검증 +X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey)); +KeyFactory keyFactory = KeyFactory.getInstance("RSA"); +jwsVerifier = new RSASSAVerifier((RSAPublicKey) keyFactory.generatePublic(keySpec)); // 공개키로 검증만 +``` + +--- + +## 3. JWT 서명 검증 상세 동작 (`signedJWT.verify(jwsVerifier)`) + +### JWT 구조 + +``` +eyJhbGciOiJSUzI1NiJ9 . eyJzdWIiOiJ1c2VyMSIsImV4cCI6...} . MEUCIQDx... + Header Payload Signature + (Base64url) (Base64url) (Base64url) +``` + +### 서명 생성 과정 (Authorization 서버 측) + +``` +[Header].[Payload] + ↓ + SHA-256 해싱 + ↓ + digest (32 bytes) + ↓ + RSA 개인키로 암호화 (PKCS#1 v1.5 패딩) + ↓ + Signature (Base64url 인코딩) +``` + +### 서명 검증 과정 (ApiAuthFilter 측) + +``` +JWT 수신 + ↓ +① Header + Payload 분리 + "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyMSIs..." + ↓ +② Signature 파트를 Base64url 디코딩 + → 바이너리 서명값 확보 + ↓ +③ RSA 공개키로 서명값 복호화 ← RSASSAVerifier (Nimbus JOSE) + → digest_from_signature 추출 + ↓ +④ ①의 "Header.Payload" 문자열을 SHA-256 해싱 + → digest_from_message 계산 + ↓ +⑤ digest_from_signature == digest_from_message ? + → true : 서명 유효 (개인키 소유자가 서명한 것 확인) + → false : 서명 불일치 → Token invalid 예외 +``` + +### Nimbus JOSE 라이브러리 내부 흐름 + +```java +// RSASSAVerifier.verify() 내부 +public boolean verify(JWSHeader header, byte[] signingInput, Base64URL signature) { + String jcaAlg = "SHA256withRSA"; // RS256 알고리즘 + Signature verifier = Signature.getInstance(jcaAlg); + verifier.initVerify(publicKey); // RSA 공개키 초기화 + verifier.update(signingInput); // [Header].[Payload] 바이트 입력 + return verifier.verify(signature.decode()); // Signature 바이트와 대조 검증 +} +``` + +### 보안 원리 (RSA 비대칭 키) + +| 시도 | 결과 | +|------|------| +| Payload 변조 후 전송 | digest 불일치 → 검증 실패 | +| 개인키 없이 서명 위조 | RSA 수학적으로 불가능 | +| 공개키로 새 서명 생성 | 공개키로는 암호화 불가 | +| 만료된 토큰 재사용 | exp 체크에서 차단 | + +### ApiAuthFilter 검증 흐름 + +``` +클라이언트 요청 + ↓ +Bearer 토큰 추출 (Authorization 헤더 or access_token 파라미터) + ↓ +SignedJWT.parse(token) ← JWT 파싱 + ↓ +만료시간(exp) 확인 + ↓ +signedJWT.verify(jwsVerifier) ← RSA 공개키로 서명 검증 + ↓ +Scope 검증 (oob/public이면 패스, 아니면 API별 허용 scope 확인) + ↓ +client_id 확인 (헤더 vs 토큰 payload 비교) + ↓ +등록된 APP 및 API 접근 권한 확인 +``` + +--- + +## 4. Self-Signed 인증서 생성 방법 + +### 사전 요구사항 + +- **JDK** (keytool 포함) +- **OpenSSL** + +### 1단계: JKS 파일 생성 + +```bash +keytool -genkeypair \ + -alias elink-oauth \ + -keyalg RSA \ + -keysize 2048 \ + -sigalg SHA256withRSA \ + -validity 3650 \ + -keystore elink-oauth-dev.jks \ + -storepass elink1234 \ + -keypass elink1234 \ + -dname "CN=elink-oauth, OU=eActive, O=eActive, L=Seoul, ST=Seoul, C=KR" +``` + +> 옵션 설명 +> - `-alias` : 키 별칭 (코드의 `certification.keyAlias` 설정값과 일치해야 함) +> - `-keysize 2048` : RSA 키 길이 (최소 2048 권장) +> - `-validity 3650` : 유효기간 (일 단위, 3650 = 10년) +> - `-storepass` : KeyStore 전체 비밀번호 (`certification.keystorePassword`) +> - `-keypass` : 개별 키 비밀번호 (`certification.keyPassword`) + +### 2단계: JKS에서 공개키(.pub) 추출 + +**방법 A: keytool + openssl 조합** + +```bash +# JKS → 인증서(CER) 추출 +keytool -exportcert \ + -alias elink-oauth \ + -keystore elink-oauth-dev.jks \ + -storepass elink1234 \ + -file elink-oauth-dev.cer + +# CER에서 공개키 추출 → PEM 형식(.pub) +openssl x509 \ + -inform DER \ + -in elink-oauth-dev.cer \ + -pubkey -noout \ + -out elink-oauth-dev.pub +``` + +**방법 B: JKS → PKCS12 변환 후 openssl** + +```bash +# JKS → PKCS12 변환 +keytool -importkeystore \ + -srckeystore elink-oauth-dev.jks \ + -destkeystore elink-oauth-dev.p12 \ + -deststoretype PKCS12 \ + -srcalias elink-oauth \ + -srcstorepass elink1234 \ + -deststorepass elink1234 + +# PKCS12에서 공개키 추출 +openssl pkcs12 \ + -in elink-oauth-dev.p12 \ + -passin pass:elink1234 \ + -nokeys \ + -out temp-cert.pem + +openssl x509 \ + -in temp-cert.pem \ + -pubkey -noout \ + -out elink-oauth-dev.pub +``` + +### 3단계: 생성 결과 확인 + +```bash +# JKS 내용 확인 +keytool -list -v \ + -keystore elink-oauth-dev.jks \ + -storepass elink1234 + +# 공개키 내용 확인 +cat elink-oauth-dev.pub +# 결과: +# -----BEGIN PUBLIC KEY----- +# MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA... +# -----END PUBLIC KEY----- +``` + +--- + +## 5. 파일 배치 + +``` +Authorization Server (elink-oauth) +└── src/main/resources/certificate/ + └── elink-oauth-dev.jks ← 개인키 포함, 절대 외부 노출 금지 + +API Gateway (elink-online-common) +└── src/main/resources/certificate/ + └── elink-oauth-dev.pub ← 공개키만, 배포 가능 +``` + +## 6. 설정 파일 (`OAuthServer` PropGroup) + +| 프로퍼티 키 | 설명 | 사용 위치 | +|------------|------|----------| +| `certification.keystorePath` | JKS 파일 경로 | AuthorizationServerConfig | +| `certification.keystorePassword` | KeyStore 비밀번호 | AuthorizationServerConfig | +| `certification.keyAlias` | 키 별칭 | AuthorizationServerConfig | +| `certification.keyPassword` | 키 비밀번호 | AuthorizationServerConfig | +| `certification.publicKeyPath` | PUB 파일 경로 | ApiAuthFilter | diff --git a/elink-online-common b/elink-online-common index 7aa230f..1f24dc2 160000 --- a/elink-online-common +++ b/elink-online-common @@ -1 +1 @@ -Subproject commit 7aa230f1569e07ae734994f1cf1019dd93ffcccc +Subproject commit 1f24dc24b5c3201cd82b6961931d6514d517b660 diff --git a/elink-online-core-jpa b/elink-online-core-jpa index 92aedde..76342bf 160000 --- a/elink-online-core-jpa +++ b/elink-online-core-jpa @@ -1 +1 @@ -Subproject commit 92aeddecd0233cbfd971ed52c33a20834711a3aa +Subproject commit 76342bfaef2578ce322791c9f9a862dd98fb3010 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 54228ea..318ac4b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +#distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=file:///D:/installs/gradle-8.7-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists networkTimeout=10000 diff --git a/reposilite.md b/reposilite.md new file mode 100644 index 0000000..cd9c03a --- /dev/null +++ b/reposilite.md @@ -0,0 +1,104 @@ +# Reposilite 3 연동 가이드 + +## 구성 개요 + +기존 Nexus(`https://nexus.eactive.synology.me:8090`) 대신 로컬 Reposilite 3(`http://localhost:8080`)를 통해 의존성을 다운로드한다. +Reposilite의 `private` 레포지토리에 Nexus `maven-public`을 Mirror로 등록하여 캐싱한다. + +--- + +## 1. Reposilite 3 설정 + +관리자 패널 → **Repositories** → `private` 저장소 → **Edit** + +**Mirrored repositories** 항목에 추가: +``` +https://nexus.eactive.synology.me:8090/repository/maven-public/ +``` + +--- + +## 2. 변경된 파일 + +### `build.gradle` (루트) + +- `nexusUrl` → `reposiliteUrl` 변수명 변경 +- `allprojects.repositories` URL을 Reposilite로 변경 +- credentials를 `gradle.properties` 변수로 참조 + +```groovy +def reposiliteUrl = "http://localhost:8080" + +allprojects { + repositories { + maven { + url "${reposiliteUrl}/private" + allowInsecureProtocol = true + credentials { + username = reposiliteUser + password = reposilitePassword + } + } + } +} +``` + +### `elink-online-core/build.gradle` + +- 단독 빌드용 `repositories` 블록의 URL 및 credentials 동일하게 변경 + +```groovy +// FOR LOCAL +repositories { + maven { + url "${nexusUrl}/private" + allowInsecureProtocol = true + credentials { + username = reposiliteUser + password = reposilitePassword + } + } +} +``` + +### `gradle.properties` (신규 생성) + +인증 정보를 별도 파일로 분리. `.gitignore`에 이미 등록되어 있어 git에 올라가지 않는다. + +```properties +reposiliteUser=admin +reposilitePassword= +``` + +--- + +## 3. 서브모듈별 repositories 현황 + +| 모듈 | repositories 블록 | 비고 | +|------|------------------|------| +| `build.gradle` (root) | `allprojects { ... }` | 모든 서브모듈에 상속 | +| `elink-online-core` | 별도 정의 | 단독 빌드 대응용 (`// FOR LOCAL`) | +| `elink-online-core-jpa` | 없음 | root 상속 | +| `elink-online-emsclient` | 없음 | root 상속 | +| `elink-online-transformer` | 없음 | root 상속 | +| `elink-online-common` | 없음 | root 상속 | +| `kjb-errors` | 없음 | root 상속 (기존 `mavenCentral()` 제거) | + +--- + +## 4. 빌드 방법 + +```bash +# 캐시 삭제 후 Reposilite에서 새로 받기 +./gradlew build --refresh-dependencies + +# 테스트 스킵 +./gradlew build -x test --refresh-dependencies +``` + +--- + +## 참고 + +- `publishing` 블록(아티팩트 배포)은 각 서브모듈에서 여전히 Nexus URL을 사용 중 +- Reposilite로 배포까지 전환하려면 각 서브모듈의 `nexusUrl` 및 `publishing` 블록 수정 필요 diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml deleted file mode 100644 index 153d5aa..0000000 --- a/src/main/resources/logback.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - -