Home > Backend Development > πŸ“š[Backend Development] πŸš€ ν™˜κ²½ λ³€μˆ˜(Environment Variable) μΉ˜ν™˜ κΈ°λŠ₯

πŸ“š[Backend Development] πŸš€ ν™˜κ²½ λ³€μˆ˜(Environment Variable) μΉ˜ν™˜ κΈ°λŠ₯
Backend Development Server Java Security

πŸš€ GitHub에 λΉ„λ°€λ²ˆν˜Έκ°€ μ˜¬λΌκ°”λ‹€λ©΄? ν™˜κ²½ λ³€μˆ˜λ‘œ ν•΄κ²°ν•˜κΈ°

😱 이런 κ²½ν—˜ μžˆμœΌμ‹ κ°€μš”?

# application.yml
spring:
  datasource:
    password: myRealPassword123!  # 😨 이거 κ·ΈλŒ€λ‘œ GitHub에...?

μ½”λ“œλ₯Ό μž‘μ„±ν•˜λ‹€ 보면 DB λΉ„λ°€λ²ˆν˜Έ, API ν‚€, μΈμ¦μ„œ λΉ„λ°€λ²ˆν˜Έ λ“± λ―Όκ°ν•œ 정보λ₯Ό μ„€μ • νŒŒμΌμ— λ„£μ–΄μ•Ό ν•  λ•Œκ°€ μžˆμŠ΅λ‹ˆλ‹€.

ν•˜μ§€λ§Œ μ΄λŒ€λ‘œ GitHub에 올리면? πŸ”₯ λ³΄μ•ˆ 사고 μ§ν–‰μž…λ‹ˆλ‹€.


πŸ’‘ ν•΄κ²°μ±…: ν™˜κ²½ λ³€μˆ˜(Environment Variable) μΉ˜ν™˜

Spring BootλŠ” ${ν™˜κ²½λ³€μˆ˜λͺ…:κΈ°λ³Έκ°’} λ¬Έλ²•μœΌλ‘œ ν™˜κ²½ λ³€μˆ˜λ₯Ό μžλ™μœΌλ‘œ μΉ˜ν™˜ν•΄μ€λ‹ˆλ‹€.

μž‘λ™ 원리

password: ${MY_SECRET_PASSWORD:defaultPassword}
  1. 운영체제 ν™˜κ²½λ³€μˆ˜ MY_SECRET_PASSWORDκ°€ 있으면 β†’ κ·Έ κ°’ μ‚¬μš©
  2. μ—†μœΌλ©΄ β†’ κΈ°λ³Έκ°’ defaultPassword μ‚¬μš©

μ΄λ ‡κ²Œ ν•˜λ©΄:

  • βœ… μ†ŒμŠ€μ½”λ“œμ—λŠ” μ§„μ§œ λΉ„λ°€λ²ˆν˜Έκ°€ μ—†μŒ
  • βœ… GitHub에 μ˜¬λ €λ„ μ•ˆμ „
  • βœ… μ„œλ²„λ§ˆλ‹€ λ‹€λ₯Έ λΉ„λ°€λ²ˆν˜Έ μ‚¬μš© κ°€λŠ₯

πŸ“ Step 1: application.yml μ„€μ •

IRC μ„œλ²„μ˜ SSL μΈμ¦μ„œ λΉ„λ°€λ²ˆν˜Έλ₯Ό ν™˜κ²½ λ³€μˆ˜λ‘œ κ΄€λ¦¬ν•΄λ΄…μ‹œλ‹€.

irc:
  ssl:
    keystore-path: classpath:keystone.p12
    keystore-password: ${IRC_KEYSTORE_PASSWORD:password}  # πŸ‘ˆ 핡심!
    keystore-type: PKCS12

πŸ” μ½”λ“œ μ„€λͺ…

  • IRC_KEYSTORE_PASSWORD: 운영체제 ν™˜κ²½λ³€μˆ˜ 이름
  • password: 둜컬 개발용 κΈ°λ³Έκ°’ (운영 ν™˜κ²½μ—μ„œλŠ” μ ˆλŒ€ μ‚¬μš© μ•ˆ 됨)

πŸ“ Step 2: Java μ½”λ“œ μž‘μ„± (SslConfig.java)

ν™˜κ²½ λ³€μˆ˜λ‘œ 받은 값을 μ‹€μ œλ‘œ μ‚¬μš©ν•˜λŠ” μ½”λ“œμž…λ‹ˆλ‹€.

package com.ircproject.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.InputStream;
import java.security.KeyStore;

@Configuration
public class SslConfig {

    @Value("${irc.ssl.keystore-path}")
    private String keystorePath;

    @Value("${irc.ssl.keystore-password}")  // πŸ‘ˆ ν™˜κ²½λ³€μˆ˜ μžλ™ μ£Όμž…
    private String keystorePassword;

    @Value("${irc.ssl.keystore-type}")
    private String keystoreType;

    private final ResourceLoader resourceLoader;

    public SslConfig(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    @Bean
    public SSLContext sslContext() throws Exception {
        // 1. Keystore λ‘œλ“œ (JAR λ‚΄λΆ€ νŒŒμΌλ„ 읽을 수 μžˆλ„λ‘ ResourceLoader μ‚¬μš©)
        KeyStore keyStore = KeyStore.getInstance(keystoreType);
        Resource resource = resourceLoader.getResource(keystorePath);

        try (InputStream stream = resource.getInputStream()) {
            keyStore.load(stream, keystorePassword.toCharArray());
        }

        // 2. KeyManager μ΄ˆκΈ°ν™” (μ„œλ²„ 신뢄증)
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(
            KeyManagerFactory.getDefaultAlgorithm()
        );
        kmf.init(keyStore, keystorePassword.toCharArray());

        // 3. TrustManager μ΄ˆκΈ°ν™” (ν΄λΌμ΄μ–ΈνŠΈ 검증)
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(
            TrustManagerFactory.getDefaultAlgorithm()
        );
        tmf.init(keyStore);

        // 4. SSLContext 생성
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

        return sslContext;
    }
}

πŸ’‘ μ™œ ResourceLoaderλ₯Ό μ‚¬μš©ν•˜λ‚˜μš”?

일반 File ν΄λž˜μŠ€λŠ” JAR둜 νŒ¨ν‚€μ§•ν•˜λ©΄ μž‘λ™ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
ResourceLoaderλ₯Ό μ‚¬μš©ν•˜λ©΄ JAR λ‚΄λΆ€μ˜ νŒŒμΌλ„ μ •μƒμ μœΌλ‘œ 읽을 수 μžˆμŠ΅λ‹ˆλ‹€.


πŸ–₯️ Step 3: λ‘œμ»¬μ—μ„œ μ‹€ν–‰ν•˜κΈ°

방법 A: IntelliJ IDEA

  1. Run/Debug Configurations 클릭
  2. Environment variables μž…λ ₯창에 μΆ”κ°€:
    IRC_KEYSTORE_PASSWORD=mySecretPassword123
    
  3. Apply β†’ Run

![IntelliJ ν™˜κ²½λ³€μˆ˜ μ„€μ • μ˜ˆμ‹œ]

방법 B: 터미널 (Mac/Linux)

# ν™˜κ²½λ³€μˆ˜ μ„€μ • ν›„ μ‹€ν–‰
export IRC_KEYSTORE_PASSWORD=mySecretPassword123
./gradlew bootRun

λ˜λŠ” ν•œ μ€„λ‘œ:

# μΌνšŒμ„± ν™˜κ²½λ³€μˆ˜ μ„€μ •
IRC_KEYSTORE_PASSWORD=mySecretPassword123 ./gradlew bootRun

방법 C: 터미널 (Windows)

set IRC_KEYSTORE_PASSWORD=mySecretPassword123
gradlew.bat bootRun

☁️ Step 4: AWS/운영 ν™˜κ²½μ—μ„œ μ‹€ν–‰ν•˜κΈ°

AWS EC2 μ˜ˆμ‹œ

# ~/.bashrc λ˜λŠ” ~/.profile에 μΆ”κ°€
export IRC_KEYSTORE_PASSWORD=μš΄μ˜ν™˜κ²½_μ§„μ§œ_λΉ„λ°€λ²ˆν˜Έ

# μ„œλ²„ μ‹€ν–‰
java -jar irc-server.jar

Docker μ˜ˆμ‹œ

# Dockerfile
FROM openjdk:17-jdk-slim
COPY build/libs/irc-server.jar app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
# μ‹€ν–‰ μ‹œ ν™˜κ²½λ³€μˆ˜ μ£Όμž…
docker run -e IRC_KEYSTORE_PASSWORD=μš΄μ˜λΉ„λ°€λ²ˆν˜Έ my-irc-server

Kubernetes μ˜ˆμ‹œ

# deployment.yaml
apiVersion: v1
kind: Secret
metadata:
  name: irc-secrets
type: Opaque
stringData:
  keystorePassword: "μš΄μ˜λΉ„λ°€λ²ˆν˜Έ"
---
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
      - name: irc-server
        env:
        - name: IRC_KEYSTORE_PASSWORD
          valueFrom:
            secretKeyRef:
              name: irc-secrets
              key: keystorePassword

βœ… μ΄λ ‡κ²Œ ν•˜λ©΄ μ–»λŠ” 3κ°€μ§€ 이점

ν•­λͺ© μ„€λͺ…
πŸ”’ λ³΄μ•ˆ GitHub에 λΉ„λ°€λ²ˆν˜Έκ°€ μ ˆλŒ€ λ…ΈμΆœλ˜μ§€ μ•ŠμŒ
πŸ› οΈ μœ μ§€λ³΄μˆ˜μ„± ν™˜κ²½λ³„λ‘œ λ‹€λ₯Έ μ„€μ • μ‰½κ²Œ 적용 κ°€λŠ₯
πŸš€ 배포 μ•ˆμ •μ„± JAR νŒ¨ν‚€μ§• μ‹œμ—λ„ 정상 μž‘λ™

πŸ’‘ μΆ”κ°€ 팁

1️⃣ .gitignore에 μΈμ¦μ„œ 파일 μΆ”κ°€

# Security / Secrets
*.p12
*.jks
*.key
*.pem
src/main/resources/keystone.p12

2️⃣ μ—¬λŸ¬ ν™˜κ²½λ³€μˆ˜ ν•œλ²ˆμ— μ„€μ •

# .env 파일 생성 (Gitμ—λŠ” μ˜¬λ¦¬μ§€ 말 것!)
IRC_KEYSTORE_PASSWORD=myPassword
DB_PASSWORD=dbPassword
API_KEY=myApiKey

# ν™˜κ²½λ³€μˆ˜ λ‘œλ“œ ν›„ μ‹€ν–‰
export $(cat .env | xargs) && ./gradlew bootRun

3️⃣ Spring Profileλ³„λ‘œ λ‹€λ₯Έ ν™˜κ²½λ³€μˆ˜ μ‚¬μš©

# application-dev.yml
irc:
  ssl:
    keystore-password: ${IRC_KEYSTORE_PASSWORD:devPassword}

# application-prod.yml
irc:
  ssl:
    keystore-password: ${IRC_KEYSTORE_PASSWORD}  # κΈ°λ³Έκ°’ μ—†μŒ (ν•„μˆ˜!)

⚠️ μ£Όμ˜μ‚¬ν•­

  1. 기본값을 λ„ˆλ¬΄ μ‰½κ²Œ λ§Œλ“€μ§€ λ§ˆμ„Έμš”
    • password, 1234 같은 기본값은 μœ„ν—˜ν•©λ‹ˆλ‹€
    • 개발 ν™˜κ²½μš©μœΌλ‘œλ§Œ μ‚¬μš©ν•˜κ³ , μš΄μ˜μ—μ„œλŠ” λ°˜λ“œμ‹œ ν™˜κ²½λ³€μˆ˜ μ„€μ •
  2. ν™˜κ²½λ³€μˆ˜ 이름은 λͺ…ν™•ν•˜κ²Œ
    • ❌ PASSWORD (λ„ˆλ¬΄ 일반적)
    • βœ… IRC_KEYSTORE_PASSWORD (λͺ…확함)
  3. λ―Όκ°ν•œ μ •λ³΄λŠ” μ ˆλŒ€ λ‘œκ·Έμ— 남기지 λ§ˆμ„Έμš”
    // ❌ μœ„ν—˜
    log.info("Password: {}", keystorePassword);
       
    // βœ… μ•ˆμ „
    log.info("SSL Context initialized successfully");
    

🎯 마무리

ν™˜κ²½ λ³€μˆ˜ μΉ˜ν™˜ κΈ°λŠ₯은 ν•„μˆ˜ λ³΄μ•ˆ νŒ¨ν„΄μž…λ‹ˆλ‹€.

μ§€κΈˆ λ‹Ήμž₯ ν”„λ‘œμ νŠΈμ˜ application.yml을 ν™•μΈν•΄λ³΄μ„Έμš”.
λΉ„λ°€λ²ˆν˜Έκ°€ ν•˜λ“œμ½”λ”©λ˜μ–΄ μžˆλ‹€λ©΄, 이 글을 μ°Έκ³ ν•΄μ„œ λ°”λ‘œ μˆ˜μ •ν•˜μ‹œκΈΈ λ°”λžλ‹ˆλ‹€! πŸš€