config: local reposilite 환경으로 변경
This commit is contained in:
@@ -11,7 +11,7 @@ hibernate.dialect=org.hibernate.dialect.Oracle12cDialect
|
|||||||
# eLink default functions
|
# eLink default functions
|
||||||
# set System property in real system
|
# set System property in real system
|
||||||
#inst.Name=${HOSTNAME}
|
#inst.Name=${HOSTNAME}
|
||||||
inst.Name=agwSvr11
|
inst.Name=agwSvr63
|
||||||
eai.jdbc.Name=jdbc/dsOBP_AGW
|
eai.jdbc.Name=jdbc/dsOBP_AGW
|
||||||
eai.rmiport=30111
|
eai.rmiport=30111
|
||||||
eai.rmiserviceport=30112
|
eai.rmiserviceport=30112
|
||||||
|
|||||||
+6
-2
@@ -20,13 +20,17 @@ def queryDslVersion = "5.0.0"
|
|||||||
def hibernateVersion = "5.6.15.Final"
|
def hibernateVersion = "5.6.15.Final"
|
||||||
|
|
||||||
def generatedJavaDir = "$buildDir/generated/java"
|
def generatedJavaDir = "$buildDir/generated/java"
|
||||||
def nexusUrl = "https://nexus.eactive.synology.me:8090"
|
def reposiliteUrl = "http://localhost:8080"
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
maven {
|
maven {
|
||||||
url "${nexusUrl}/repository/maven-public/"
|
url "${reposiliteUrl}/private"
|
||||||
allowInsecureProtocol = true
|
allowInsecureProtocol = true
|
||||||
|
credentials {
|
||||||
|
username = reposiliteUser
|
||||||
|
password = reposilitePassword
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 |
|
||||||
+1
-1
Submodule elink-online-common updated: 7aa230f156...1f24dc24b5
+1
-1
Submodule elink-online-core-jpa updated: 92aeddecd0...76342bfaef
+2
-1
@@ -1,6 +1,7 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
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
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
|
|||||||
+104
@@ -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=<reposilite-token>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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` 블록 수정 필요
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<configuration scan="true" scanPeriod="10 seconds">
|
|
||||||
<!-- Wire 로그 차단 -->
|
|
||||||
<logger name="org.apache.hc.client5.http.impl.Wire" level="OFF" additivity="false" />
|
|
||||||
|
|
||||||
<!-- HttpClient 내부 로그 차단 -->
|
|
||||||
<logger name="org.apache.hc.core5.http.impl.io" level="INFO" additivity="false" />
|
|
||||||
<logger name="org.apache.hc.client5.http.impl" level="INFO" additivity="false" />
|
|
||||||
|
|
||||||
<!-- 콘솔 로그 Appender -->
|
|
||||||
<!--
|
|
||||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
|
||||||
<encoder>
|
|
||||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
|
||||||
</encoder>
|
|
||||||
</appender>
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!-- 파일 로그 Appender -->
|
|
||||||
<!--
|
|
||||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
|
||||||
<file>logs/app.log</file>
|
|
||||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
|
||||||
<fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
|
|
||||||
<maxHistory>30</maxHistory>
|
|
||||||
</rollingPolicy>
|
|
||||||
<encoder>
|
|
||||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
|
||||||
</encoder>
|
|
||||||
</appender>
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!-- 루트 로거 설정 -->
|
|
||||||
<!--
|
|
||||||
<root level="INFO">
|
|
||||||
<appender-ref ref="CONSOLE" />
|
|
||||||
<appender-ref ref="FILE" />
|
|
||||||
</root>
|
|
||||||
-->
|
|
||||||
|
|
||||||
</configuration>
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user