[E-commerce App] 설정 정보의 암호화 처리
application.yml 혹은 bootstrap.yml에 있는 plain text 값들을 암호화처리하는 방법에 대해 알아보자.
개요
대칭키 (Shared)
- 암/복호화 시에 같은 키를 사용한다.
비대칭키 (RSA Keypair)
- 암호화 할 때 사용되는 키와 복호화 할 때 사용되는 키가 다르다.
- 공개키와 사설키를 사용한다.
- 키 값을 생성하기 위해 Java keytool을 사용한다.
흐름

민감한 환경 변수가 담긴 application.yml 파일을 그대로 git repository에 올리게되면, 보안적인 이슈가 발생할 수 있으므로 암호화를 진행할 것이다.
이 때 텍스트가 암호화 되어있다는 표시로, 텍스트 앞에 {cipher}를 붙인다.
암호화 된 값이 Spring Cloud Coonfig Server에서 읽혀지고,
각각의 마이크로 서비스들에게 전달되는 시점에, key를 이용해 cipher text를 복호화 한 뒤에 사용한다.
이렇게 repository 혹은 파일시스템에 민감한 데이터 혹은 secret 값을 저장시켜 놓는다고 하더라도 외부에 노출될 수 있는 위험을 최소화 할 수 있다.
JCE (Java Cryptography Extension)
java8(jdk8)을 사용한다면 JCE를 추가적으로 사용해야한다.
🚨 주의
- java8(jdk8)을 사용한다면, encryption 혹은 decryption 할 때 아래와 같은 문제가 발생할 수 있다.
- Illegal key size or default parameters
- Unsupported keysize of algorithm parameters- 이 경우에는 아래 사이트에서 필요한 파일을 추가로 설치해야 한다.
- https://www.oracle.com/java/technologies/javase-jce-all-downloads.html- 이 과정은 java11(jdk11) 이상을 사용할 때에는 생략할 수 있다.
JCE를 사용해야한다고 하면, 위에서 말한 파일을 다운로드 받은 후에 아래 과정을 따라하자.

대칭키를 이용한 암호화
🍊 config-service
pom.xml
bootstrap이 이미 추가되어있다면 추가하지 않아도 된다.
<!-- Bootstrap -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
bootstrap.yml
기존에 bootstrap.yml 파일이 없었기 때문에 새로 생성한다.
대칭키 방식은 암/복호화 시 같은 키를 사용한다.
이때 사용될 키를 bootstrap.yml에 정의하자. (키는 마음대로 정의하면 된다.)
encrypt:
key: abcdefghijklmnopqrstuvwxyz0123456789
테스트
암호화가 잘 되는지 테스트를 해보자.
service-discovery와 방금 설정한config-service두 서버를 기동한 뒤에,
포스트맨에서http://localhost:8888/encrypt에 다음 요청을 해보자.
암호화가 되었다.
이번에는 cipher text를 가지고 복호화 테스트를 해보자.
http://localhost:8888/decrypt에 다음 요청을 해보자.
복호화도 잘 되는 것을 확인할 수 있다.
🍊 user-service
application.yml
application.yml에 있던 db 연동 시 필요한 설정 정보를 별도의 파일로 따로 분리시키자.
이는 config-service의 user-service.yml로 이동시킬 것이다.
아래에서는 어떤 정보가 사리졌는지 나타내기 위해 주석으로 표시했지만, 실제로는 삭제한다.
# datasource:
# driver-class-name: org.h2.Driver
# url: jdbc:h2:mem:testdb
# username: sa
# password: 1234
bootstrap.yml
읽어오고자 하는 설정 파일의 정보를 명시한다.
이 때 name에 config-service라는 Configuration Server의 이름을 적게되면 application.yml 파일을 읽어오고,
user-service와 같은 특정 이름을 명시하면 user-service.yml을 읽어온다.
spring:
cloud:
config:
uri: http://127.0.0.1:8888
name: user-service # 🌟 native-file-repo 안에 있는 user-service.yml 파일을 읽어온다.
🍊 native-file-repo
user-service.yml
아까 user-service에서 잘라낸 db 연결 정보를 user-service.yml로 붙여넣는다.
이 때, h2 database의 password(= 1234)를 암호화해서 넣어준다.
spring:
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:testdb
username: sa
password: '{cipher}c0c61dbe3eb9ef876f5b1810bd510a49d2ee8aa679956f68d9d1d4ff3b661108'
...
파일을 저장한 뒤에 config-service를 재기동해준다.
참고
암호화는 위에서 했던 것처럼 포스트맨을 이용하면 된다.
테스트
테스트를 위해 gateway-service도 기동해준다. (현재 service-discovery -> config-service -> gateway-service -> user-service가 기동되어있다.)
http://localhost:8888/user-service/default에 접속해보자.
아래 사진에서 웹 브라우저를 통해 보이는 단계가 user라는 마이크로 서비스가 사용하는 시점이라고 볼 수 있다.

spring.password를 보면, 정상적으로 1234라는 plain text를 읽어오는 것을 확인할 수 있다.
따라서 저장할 때에는 암호화된 cypher text를 저장하고, 사용할 때에는 위 사진에서 처럼 decrypted 된 plain text를 사용함을 알 수 있다.
만약 패스워드에 잘못된 암호문을 넣는다면?
native-file-repo/user-service.yml에서 password에
'{cipher}c0c61dbe3eb9ef876f5b1810bd510a49d2ee8aa679956f68d9d1d4ff3b661108_wrong'과 같이 일부러 잘못된 암호문을 넣어보자.
http://localhost:8888/user-service/default페이지를 새로고침 하게 되면, 아래와 같이<n/a>로 표시됨을 알 수 있다.
h2-console 접속 테스트
localhost:8761으로 유레카 홈에 들어가서 user-service의 포트번호를 확인한 뒤, 주소 마지막을 /h2-console로 변경해 h2 콘솔로 접속한다.
이전에 했던 것처럼 패스워드 없이 test connection을 시도해보자.

패스워드 없이는 접속되지 않는 것을 확인했다.
이번에는 패스워드에 1234를 입력해보자.

우리가 암호문으로 설정한대로 1234를 입력해야 접속이 되는 것을 확인할 수 있다.
비대칭키를 이용한 암호화
키 생성
- public, private key 생성 -> JDK keytool 이용
- 보통은 암호화에 private key를 사용하고 복호화에 public key를 사용하지만 반대가 되어도 상관없다. (암/복호화 키가 서로 다르기만 하면)
# key가 저장될 디렉토리 생성
$ mkdir ${user.home}/study/msa/keystore
# keystore 디렉토리로 이동
$ cd keystore
# key 생성
$ keytool -genkeypair -alias apiEncryptionKey -keyalg RSA \
-dname "CN=Ann, OU=API Development, O=test.co.kr, L=Seoul, C=KR" \
-keypass "test1234" -keystore apiEncryptionKey.jks -storepass "test1234"

-alias: 생성된 키에 대한 별칭, alias를 통해 호출하고 사용 (여기서는 생성하고자 하는 키 파일과 같은 형식으로 지정)-keyalg: 키 생성 시, 사용되는 알고리즘-dname: 키 생성 시, 셀프 인증에 사용되는 부가 정보
- CN: Company Name
- OU: Organization Unit
- O: Organization
- L: Location
- C: Country-keypass: 키 생성 시, 패스워드 (임의로 생성)-keystore: 키의 실제 파일 이름 (.jks)-storepass: 저장되는 키 파일의 패스워드 (임의로 생성)
생성된 키 정보를 자세히 확인해보고 싶다면, 아래 커맨드를 입력한다.
$ keytool -list -keystore apiEncryptionKey.jks -v
패스워드를 물어보면, 아까 지정한 test1234를 입력한다.

그럼 위 사진과 같은 결과가 나오는데, PrivateKeyEntry를 보면, 이 키는 private key 임을 알 수 있다.
그렇다면 이 private key에서 public key를 끄집어내보자.
$ keytool -export -alias apiEncryptionKey -keystore apiEncryptionKey.jks -rfc -file trustServer.cer
패스워드를 물어보면, 마찬가지로 test1234를 입력한다.

성공적으로 공개키도 발급받았다.
그런데 .cer 확장자는 인증서 파일이다.
인증서 파일을 jks 파일로 변환하여 사용할 수도 있다.
$ keytool -import -alias trustServer -file trustServer.cer -keystore publicKey.jks

위 박스에는 키의 정보가 표시되고, 마지막에 예를 입력하면 jks 파일로 변환할 수 있다.
이 키 정보를 아까처럼 자세히 확인해보자.
$ keytool -list -keystore publicKey.jks -v

trustedCertEntry를 보면, 이 키는 public key 임을 알 수 있다.
참고
사실 Spring Cloud Config Server에서는 비밀키(apiEncryptionKey.jks)만 사용할 것이다.
나머지는 나중에 공개키가 필요한 상황이 생긴다면, 사용해볼 수 있을 것이다.
🍊 config-service
이제 생성한 비밀키의 위치와 파일명을 Configuration Server에 명시하자.
bootstrap.yml
encrypt:
key-store:
location: file://${user.home}/study/msa/keystore/apiEncryptionKey.jks
password: test1234
alias: apiEncryptionKey
이제 config-service를 재기동하자.
참고
어디까지가${user.home}일까?
keystore 디렉토리에서pwd를 입력하게되면 아래와 같이 나오는데,
여기서/Users/사용자명까지가${user.home}이다.
포스트맨 암호화 테스트
대칭키 때와 같은 방법으로 테스트한다.
대칭키 때보다 더 복잡하게 암호화가 되었다.
이번에는 cipher text를 가지고 복호화 테스트를 해보자.
복호화도 잘 되는 것을 확인할 수 있다.
🍊 native-file-repo
user-service.yml
아까 user-service에서 잘라낸 db 연결 정보를 user-service.yml로 붙여넣는다.
이 때, h2 database의 password(= 1234)를 암호화해서 넣어준다.
spring:
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:testdb
username: sa
password: '{cipher}AAQCgo+KT8kG9ISz3Rr0FEG2uoOR9jJXhjMzbTWgcDQtTh4rShUrj3KtiOfFaN8Gk/MoJeyszPKouvCrQihy1SyVjNiZuGaIXkk2bf4HTMayE4IP1xvLF7nOEHbvxuZR6dv2UUVK2eJcQ+bskIk9+fBjVL9C4t1Gw/VDA9rMVzu55VYcsdsRSEnIfSCUlOzaWaH0XsuiDbZ5UI1LxqEB3hXLkazCWkahj2jFoKElzZ1kZ/QRnOFQTtXrv3jtf0P0ytwTVBqEa5Gjz4JJLr3tybc7i0avs+tqXjWKj35C/3Aq+sDH1L3sl+JNns4QiDclTpHTt+wDg/pqlg2MhZ0HAOIJNBtuP5P/qk6Pwk0fCUyWhuifmOMo9OuBBZowXsMk0Hqg=' # 🌟 비대칭키 암호화 방식 사용
...
위 암호문은 기존 비밀번호인 1234를 비대칭키 암호화 한 것이다.
파일을 저장한 뒤에 웹 브라어저에서 값을 확인해보자.
테스트
http://localhost:8888/user-service/default에 접속해보자.

아까와 마찬가지로 정상적으로 1234라는 plain text를 읽어오는 것을 확인할 수 있다.
🍊 gateway-service
지금까지는 db 정보를 암호화해서 사용했다면, 이번에는 토큰의 secret 정보를 암호화해서 사용해보자.
bootstrap.yml
현재 gateway-service는 native-file-repo 안에 있는 ecommerce.yml 파일에서 구성 정보를 읽어오는 것을 확인할 수 있다.
spring:
cloud:
config:
uri: http://127.0.0.1:8888
name: ecommerce # 🌟 native-file-repo 안에 있는 ecommerce.yml 파일을 읽어온다.
🍊 native-file-repo
ecommerce.yml
기존 token.secret에는 아래와 같이 plain text가 노출되어 있었다.
token:
expiration_time: 864000000
secret: user_token_native_ecommerce
gateway:
ip: 127.0.0.1
이를 아래처럼 암호문으로 변경하자.
token:
expiration_time: 864000000
secret: '{cipher}AQA4EotojUaSDNpQ5raB7YyPaZ+kOFdojsDz4UCo/cjHnAjQgZDrZFFvJxFPvYgNAUzQb2RJENdTsMai3WOhWxSJPWaWG/0l4qD3El0/eslslIfuvGMyrwTBaE97M05q/wRmjfBL/uW0pKmjAzNXX3NkGlVezq5HoNr+k9f4FEhz6CEdSnRsOndLkMr9AwOZFFzp1E/jCHdrQ80AZKdMPpCZLR8sSU/NZa28jL9odkKKGggJ6IFKTEBAIRbUwuHZX1jS+j9ugv+U6sD+QtzGEiNl0yoqoAeC52kfLJRsWS9JpA1HEJ0m+J2Bk4v7M37TtOXtPXJmsqEPuBNhLLuK48zN1nz9HKIqlMCc6J6Lr4v479l9c/73dtXNZNCgV609uzwWqTqa6/p1TiuH4NJszJyp'
gateway:
ip: 127.0.0.1
참고
암호화는 위에서 했던 것처럼 포스트맨을 이용하면 된다.
테스트
암/복호화 테스트
http://localhost:8888/ecommerce/default에 접속해보자.

정상적으로 user_token_native_ecommerce라는 plain text를 읽어오는 것을 확인할 수 있다.
gateway-service 기동 후 받아오는 토큰 값 확인
기존 application.yml 파일에 있던 토큰 정보를 주석처리 한다.
#token:
# secret: user_token
그리고 토큰 정보를 받아오는 곳에 break point를 찍고 디버깅 모드로 실행한다.
어떠한 user-service를 호출해도 게이트웨이를 거치기 때문에,
http://localhost:8000/user-service/health-check를 호출해보자.

암호문으로 등록한 token.secret의 정보를 정상적으로 복호화하여 사용하는 것을 확인할 수 있다.
그런데, 중요한 것은 user-service에서 사용하는 구성파일(native-repo-repository/user-service.yml)에 있는 토큰 값과 gateway-service에서 사용하는 구성파일(native-repo-repository/ecommerce.yml)에 있는 토큰 값이 같아야 한다는 것이다.
따라서 user-service.yml에 있는 토큰 정보를 ecommerce.yml에 있는 토큰 정보와 일치시킨다.
user-service.yml 변경 전
spring:
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:testdb
username: sa
password: '{cipher}AQCgo+KT8kG9ISz3Rr0FEG2uoOR9jJXhjMzbTWgcDQtTh4rShUrj3KtiOfFaN8Gk/MoJeyszPKouvCrQihy1SyVjNiZuGaIXkk2bf4HTMayE4IP1xvLF7nOEHbvxuZR6dv2UUVK2eJcQ+bskIk9+fBjVL9C4t1Gw/VDA9rMVzu55VYcsdsRSEnIfSCUlOzaWaH0XsuiDbZ5UI1LxqEB3hXLkazCWkahj2jFoKElzZ1kZ/QRnOFQTtXrv3jtf0P0ytwTVBqEa5Gjz4JJLr3tybc7i0avs+tqXjWKj35C/3Aq+sDH1L3sl+JNns4QiDclTpHTt+wDg/pqlg2MhZ0HAOIJNBtuP5P/qk6Pwk0fCUyWhuifmOMo9OuBBZowXsMk0Hqg='
token:
expiration_time: 864000000
secret: user_token_native_user_service # 토큰 정보 평문
gateway:
ip: 127.0.0.1
user-service.yml 변경 후
spring:
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:testdb
username: sa
password: '{cipher}AQCgo+KT8kG9ISz3Rr0FEG2uoOR9jJXhjMzbTWgcDQtTh4rShUrj3KtiOfFaN8Gk/MoJeyszPKouvCrQihy1SyVjNiZuGaIXkk2bf4HTMayE4IP1xvLF7nOEHbvxuZR6dv2UUVK2eJcQ+bskIk9+fBjVL9C4t1Gw/VDA9rMVzu55VYcsdsRSEnIfSCUlOzaWaH0XsuiDbZ5UI1LxqEB3hXLkazCWkahj2jFoKElzZ1kZ/QRnOFQTtXrv3jtf0P0ytwTVBqEa5Gjz4JJLr3tybc7i0avs+tqXjWKj35C/3Aq+sDH1L3sl+JNns4QiDclTpHTt+wDg/pqlg2MhZ0HAOIJNBtuP5P/qk6Pwk0fCUyWhuifmOMo9OuBBZowXsMk0Hqg='
token:
expiration_time: 864000000
secret: '{cipher}AQA4EotojUaSDNpQ5raB7YyPaZ+kOFdojsDz4UCo/cjHnAjQgZDrZFFvJxFPvYgNAUzQb2RJENdTsMai3WOhWxSJPWaWG/0l4qD3El0/eslslIfuvGMyrwTBaE97M05q/wRmjfBL/uW0pKmjAzNXX3NkGlVezq5HoNr+k9f4FEhz6CEdSnRsOndLkMr9AwOZFFzp1E/jCHdrQ80AZKdMPpCZLR8sSU/NZa28jL9odkKKGggJ6IFKTEBAIRbUwuHZX1jS+j9ugv+U6sD+QtzGEiNl0yoqoAeC52kfLJRsWS9JpA1HEJ0m+J2Bk4v7M37TtOXtPXJmsqEPuBNhLLuK48zN1nz9HKIqlMCc6J6Lr4v479l9c/73dtXNZNCgV609uzwWqTqa6/p1TiuH4NJszJyp' # 🌟 토큰 정보 암호문
gateway:
ip: 127.0.0.1
🌟 참고
이렇게 두 토큰 정보를 양쪽에 똑같이 맞추는 방법도 있지만, 하나로 모을 수도 있다.
하나로 모은다는 것은,
ecommerce.yml이든user-service.yml이든 상위 개념으로application.yml을 가지기 때문에,
ecommerce.yml과user-service.yml모두token.secret정보를 지우고,application.yml에 정의하게 되면,
두 서비스 모두application.yml에 있는token.secret정보를 공통으로 가지고 온다.
💛 개인 공부 기록용 블로그입니다. 👻








