Spring/Spring 이야기

localhost HTTPS 적용 (spring boot)

seungdols 2023. 7. 31. 10:41

mkcert install

brew install mkcert
brew install nss # firefox 사용 예정이라면, 추가

mkcert localhost

명령어

  • mkcert -install [-uninstall 삭제]
> mkcert -install
    The local CA is now installed in the system trust store! ⚡️
The local CA is now installed in the Firefox trust store (requires browser restart)! 🦊
> mkcert -pkcs12 localhost

Created a new certificate valid for the following names 📜
 - "localhost"

The PKCS#12 bundle is at "./localhost.p12" ✅

The legacy PKCS#12 encryption password is the often hardcoded default "changeit" ℹ️

It will expire on 26 October 2025 🗓
> mkcert localhost

Created a new certificate valid for the following names 📜
 - "localhost"

The certificate is at "./localhost.pem" and the key at "./localhost-key.pem" ✅

It will expire on 26 October 2025 🗓

spring boot config - application.yml

server:  
    port: 8080  
    ssl:  
        enabled: true  
        key-store: classpath:cert/localhost.p12  
        key-store-type: PKCS12  
        key-store-password: changeit # 비밀번호 지정 안한 경우

HTTPS open feign 오류시

ref.
https://stackoverflow.com/questions/49810760/make-feign-client-to-take-truststore-from-custom-property/49853452#49853452

server:
  port: 8080
  ssl:
    enabled: true
    key-store: classpath:cert/localhost.p12
    key-store-type: PKCS12
    key-store-password: changeit
  http2:
    enabled: true
@Component
class FeignClientConfiguration(
    val serverProperties: ServerProperties,
    @Value("\${server.ssl.key-store}") val keyStore: Resource
) {
    @Bean
    @Throws(Exception::class)
    fun feignClient(): Client {
        val keyStorePassword = serverProperties.ssl.keyStorePassword

        return try {
            Client.Default(
                sslSocketFactory(keyStore.inputStream, keyStorePassword),
                null
            )
        } catch (e: Exception) {
            throw Exception("Error in initializing feign client", e)
        }
    }

    @Throws(NoSuchAlgorithmException::class, KeyStoreException::class, CertificateException::class, IOException::class, KeyManagementException::class)
    private fun sslSocketFactory(trustStoreStream: InputStream, trustStorePassword: String): SSLSocketFactory? {
        val sslContext: SSLContext = SSLContext.getInstance("TLSv1.2")
        val tmf = createTrustManager(trustStoreStream, trustStorePassword)
        sslContext.init(arrayOf<KeyManager>(), tmf.trustManagers, null)
        return sslContext.socketFactory
    }

    @Throws(KeyStoreException::class, IOException::class, NoSuchAlgorithmException::class, CertificateException::class)
    private fun createTrustManager(trustStoreStream: InputStream, trustStorePassword: String): TrustManagerFactory {
        val trustStore: KeyStore = KeyStore.getInstance(KeyStore.getDefaultType())
        trustStore.load(trustStoreStream, trustStorePassword.toCharArray())
        val tmf = TrustManagerFactory
            .getInstance(TrustManagerFactory.getDefaultAlgorithm())
        tmf.init(trustStore)
        return tmf
    }
}

위 코드에서 아래처럼 작성 하면 오류가 발생한다.

val keyStoreResource: Resource = ClassPathResource(serverProperties.ssl.keyStore)
Caused by: java.io.FileNotFoundException: class path resource [classpath:cert/localhost.p12] cannot be opened because it does not exist

원인은 그렇다. ClassPathResource를 이용하면, classpath: prefix가 없는 경로를 써넣어주어야 정상적으로 동작 하게 된다.

@Component
class FeignClientConfiguration(
    @Value("\${server.ssl.key-store}") val keyStore: Resource,
    @Value("\${server.ssl.key-store-password}") val keyStorePassword: String
) {
    @Bean
    @Throws(Exception::class)
    fun feignClient(): Client {
        return try {
            Client.Default(
                sslSocketFactory(keyStore.inputStream, keyStorePassword),
                null
            )
        } catch (e: Exception) {
            throw Exception("Error in initializing feign client", e)
        }
    }

    @Throws(NoSuchAlgorithmException::class, KeyStoreException::class, CertificateException::class, IOException::class, KeyManagementException::class)
    private fun sslSocketFactory(trustStoreStream: InputStream, trustStorePassword: String): SSLSocketFactory? {
        val sslContext: SSLContext = SSLContext.getInstance("TLSv1.2")
        val tmf = createTrustManager(trustStoreStream, trustStorePassword)
        sslContext.init(arrayOf<KeyManager>(), tmf.trustManagers, null)
        return sslContext.socketFactory
    }

    @Throws(KeyStoreException::class, IOException::class, NoSuchAlgorithmException::class, CertificateException::class)
    private fun createTrustManager(trustStoreStream: InputStream, trustStorePassword: String): TrustManagerFactory {
        val trustStore: KeyStore = KeyStore.getInstance(KeyStore.getDefaultType())
        trustStore.load(trustStoreStream, trustStorePassword.toCharArray())
        val tmf = TrustManagerFactory
            .getInstance(TrustManagerFactory.getDefaultAlgorithm())
        tmf.init(trustStore)
        return tmf
    }
}

굳이 ServerProperties를 쓰지 않고, @Value를 이용해서 가져오는 방법도 있다.

반응형