- Crypto SpringBoot Example
- 번외 -
CryptoSession
Factory 설정 및 사용 방법- 🚦 1.
CryptoSessionInit<T extends Enum<T>>
타입별 초기화 객체 사용 방법- 📌
CryptoSessionInit<T extends Enum<T>>
구현 - 📌
CryptoSessionInit<T extends Enum<T>>
객체 초기화 방법- ► 1. BASE_PATH
CryptoSessionInit
초기화 예시 - ► 2. BYTES
CryptoSessionInit
초기화 예시 - ► 3. INPUT_STREAM
CryptoSessionInit
초기화 예시 - ► 4. PATH
CryptoSessionInit
초기화 예시 - ► 5. MAP
CryptoSessionInit
초기화 예시 - ► 6. PARAMS
CryptoSessionInit
초기화 예시 - ► 5. LOCAL_MAP
CryptoSessionInit
초기화 예시 - ► 6. LOCAL_PARAMS
CryptoSessionInit
초기화 예시
- ► 1. BASE_PATH
- 📌
- 🚦 2.
CryptoSessionFactory<T extends Enum<T>>
클래스 구현 및 설정 방법 - 🚦 3.
CryptoSessionFactoryBean<T extends Enum<T>>
클래스 구현 및 설정 방법
- 🚦 1.
Crypto 암복호화 라이브러리는 DB 암호화 솔루션 외부 공개용 프로젝트로 개발된 라이브러리 이며 비공개 버전은 AWS KMS 와 AES 암호화 알고리즘을 조합한 3중 암호화 방식을 적용하여 데이터를 안전하게 암복호화 하는 라이브러리이다
외부 공개용으로 수정한 버전에서는 AWS KMS 를 사용하지 않고 AES 암호화 알고리즘만을 사용하여 데이터를 암복호화 한다
설정 내용을 AWS 타입과 LOCAL 타입으로 구분해놓았으니 외부 공개용 버전을 사용할 때는 LOCAL 타입으로 설정하여 사용하면 된다
또한 Rust로 개발하여 JNI(Java Native Interface) 라이브러리 형태로 제공하여 보안성을 강화한 암복호화 라이브러리이다
본 예제 프로젝트는 SpringBoot 프로젝트에서 Crypto 암복호화 라이브러리를 설정 및 사용하는 방법을 설명한다
본 예제 프로젝트에서는 아래와 같은 내용을 다룬다
- Maven, Gradle Build 도구에서 Crypto 암복호화 라이브러리 추가하는 방법
CryptoSession
을 설정하고 사용하는 방법CryptoSession
을 Bean으로 설정하여 사용하는 방법JPA
,Mybatis
,Querydsl
등과 연동하여 사용하는 방법- 외장 톰캣 서버 배포시 구성 방법
Application 구동 후 아래의 URL 로 접속
Crypto 암복호화 테스트 용 Swagger API Docs 제공
- OS
- Windows x86_64
- MacOS Apple Silicon(M1 이상)
- Linux x86_64
- Linux AArch64(ARM64)
- JDK
- JDK 9 이상
- JDK 1.8 (별도로 제공)
AWS
타입의 경우 Crypto Repository 저장소는 AWS S3로 제공되며 AWS_ACCESS_KEY_ID
와 AWS_SECRET_ACCESS_KEY
를 사용하여 접근이 가능하므로
Gradle 및 Maven 설정 파일에 해당 정보를 추가하여 사용할 수 있다
LOCAL
타입의 경우는 Crypto Repository 저장소는 Local로 제공되며 libs
폴더에 crypto.jar
파일을 추가하여 사용할 수 있다
아래의 이유로 JDK 1.8
버전과 JDK 9
이상 버전을 구분하여 제공함
JDK 9
이상에서는 아래의 이유로 deprecated 된 finalize
메소드를 제거 하고
JDK 9
에서 추가된 Cleaner
와 AutoCloseable
인터페이스를 구현하여 리소스 정리 방식을 개선하였다
finalize 를 제거한 이유:
Finalizer 는 예측할 수 없고, 상황에 따라 위험할 수 있어 불필요하며, 오작동, 낮은 성능, 이식성 문제의 원인으로 기본적으로는 쓰지 말아야 한다
LOCAL
타입의 경우는 Local Library 로 제공되므로 libs
폴더에 crypto.jar
파일을 추가하고 build.gradle
설정에 추가하여 사용할 수 있다
build.gradle 설정
build.gradle
설정 참고: ${projectDir}/build.gradle
repositories {
mavenCentral()
flatDir { dirs 'libs' } // Local JAR 파일이 있는 디렉토리명 입력
}
dependencies 에 crypto-core
Dependency 추가
dependencies {
// Crypto Local Library
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
LOCAL
타입의 경우는 Local Library 로 제공되므로 libs
폴더에 crypto.jar
파일을 추가하고 pom.xml
설정에 추가하여 사용할 수 있다
pom.xml 설정
pom.xml
설정 참고: ${projectDir}/pom.xml
repositories
태그에 Crypto Repository 저장소 추가
<repositories>
<repository>
<id>local-libs</id>
<url>file://${project.basedir}/libs</url>
</repository>
</repositories>
dependencies
태그에 crypto-core
Dependency 추가
<dependency>
<groupId>com.freelife.crypto</groupId>
<artifactId>crypto-lib</artifactId>
<version>1.0.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/libs/crypto.jar</systemPath>
</dependency>
아래의 경로 중 하나의 gradle.properties
파일 내에 Crypto Repository 저장소의 awsAccessKeyId
와 awsSecretAccessKey
를 추가
~/.gradle/gradle.properties
$projectDir/gradle.properties
$projectDir/gradle/wrapper/gradle-wrapper.properties
build.gradle 설정
build.gradle
설정 참고: ${projectDir}/build.gradle
repositories {
mavenCentral()
maven {
url "s3://crypto-dev-repo"
credentials(AwsCredentials) {
// ### Gradle.properties 에 등록된 값 사용 ###
accessKey "$awsAccessKeyId"
secretKey "$awsSecretAccessKey"
// ### 직접 입력 ###
// accessKey 'AXXXXXXXXXXXXXXXXXXX'
// secretKey '53gXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
// ### Gradle Build 시 Property로 전달 (사용 예: -PawsAccessKeyId=AX -PawsSecretAccessKey=53gXXX ) ###
// accessKey = project.findProperty("awsAccessKeyId")
// secretKey = project.findProperty("awsSecretAccessKey")
// ### 시스템 환경변수 사용 ###
// accessKey System.getenv("$awsAccessKeyId")
// secretKey System.getenv("$awsSecretAccessKey")
}
}
}
dependencies 에 crypto-core
Dependency 추가
dependencies {
// Crypto
// JDK 9 이상에서 사용
implementation 'com.freelife.crypto:crypto-core:0.0.2.RC1'
// JDK 1.8 에서 사용
// implementation 'com.freelife.crypto:crypto-core-jdk1.8:0.0.2.RC1'
}
~/.m2/settings.xml
파일에 Crypto Repository 저장소의
username
에는 AWS_ACCESS_KEY_ID
를, password
에는 AWS_SECRET_ACCESS_KEY
를 추가
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<servers>
<server>
<id>crypto-dev-repo</id>
<username>AXXXXXX</username>
<password>5XXXXXXXXXXXXXXX</password>
</server>
</servers>
</settings>
pom.xml 설정
pom.xml
설정 참고: ${projectDir}/pom.xml
build
태그 내에 AWS S3 Repository 저장소를 사용하기 위한 Extension 추가
<build>
<extensions>
<!-- AWS S3 Repository 사용을 위한 Extension -->
<extension>
<groupId>com.github.platform-team</groupId>
<artifactId>aws-maven</artifactId>
<version>6.0.0</version>
</extension>
</extensions>
</build>
repositories
태그에 Crypto Repository 저장소 추가
<repositories>
<repository>
<id>crypto-dev-repo</id>
<name>Crypto AWS S3 Repository</name>
<url>s3://crypto-dev-repo</url>
</repository>
</repositories>
dependencies
태그에 crypto-core
Dependency 추가
<!-- JDK 9 이상에서 사용 -->
<dependency>
<groupId>com.freelife.crypto</groupId>
<artifactId>crypto-core</artifactId>
<version>0.0.2.RC1</version>
</dependency>
<!-- JDK 1.8에서 사용 -->
<!--
<dependency>
<groupId>com.freelife.crypto</groupId>
<artifactId>crypto-core-jdk1.8</artifactId>
<version>0.0.2.RC1</version>
</dependency>
-->
Crypto 암복호화 라이브러리를 사용하기 위해서는 CryptoSession
객체를 설정파일로 초기화하고 사용해야 한다
CryptoSession
인스턴스는 config.json
설정 파일을 읽어들여 암복호화에 필요한 정보를 설정하고 사용한다
config.json
파일은 Crypto 암복호화 라이브러리의 설정 파일로 암복호화에 필요한 정보를 설정한다
config.json
파일은 Crypto 암복호화 라이브러리의 CryptoSession
을 사용하기 위한 필수 설정 파일로
기본 경로에 위치시키거나 파라메터로 전달하여 사용할 수 있다
Crypto config.json 파일은 사내 프로젝트용으로 진행한 AWS
타입과 LOCAL
타입 두가지가 있다
AWS
타입은 아래와 같은 속성들로 구성되어 있다
aws_kms_key_arn
: AWS KMS Key ARNaws_access_key_id
: AWS KMS에 접근하기 위한 AWS Access Key IDaws_secret_access_key
: AWS KMS에 접근하기 위한 AWS Secret Access Keyseed
: Crypto 암복호화 수행 시 필요한 Seedcredential
: Crypto 암복호화 수행 시 필요한 Credential
LOCAL
타입은 아래와 같은 속성들로 구성되어 있다
key
: 이중 암호화 처리를 위한 핵심 Secret Keyiv
: 이중 암호화 처리를 위한 핵심 초기화 백터(Initialization Vector)seed
: Crypto 암복호화 수행 시 필요한 Seedcredential
: Crypto 암복호화 수행 시 필요한 Credential
별도의 설정 파라메터를 넘겨주지 않으면 기본 경로에 위치한 config.json
파일을 사용한다
현재 지원되는 기본 경로는 아래와 같다
${projectDir}/crypto/config.json
/var/opt/crypto/config.json
이번 챕터에서는 Bean
설정 없이 CryptoSession
객체를 설정하고 사용하는 방법을 설명한다
CryptoSession 사용시에는 반드시
CryptoSession
객체를Bean
으로 설정하여 사용하기 바란다
CryptoSession 객체 초기화시 최초 1회 AWS KMS와 통신을 하게 되는데Bean
으로 설정하지 않으면 반복적으로 객체 초기화를 시도하여
매번 AWS KMS 와 통신을 하게 되어 성능에도 문제가 되고 비용적인 측면에서도 비효율적이므로 반드시Bean
으로 설정하여 사용하기를 권장한다
Crypto 암복호화 라이브러리를 사용하기 위해서는 CryptoSession
객체 생성 후 설정 정보로 초기화하고 사용해야 한다
CryptoSession 객체 초기화는 CryptoSession 설정 파일인 config.json
파일을 특정 경로에 위치 시키거나 파라메터로 전달하여 사용할 수 있다
아래의 예시는 기본 경로(${projectDir}/crypto/config.json
)에 있는 설정 정보로 객체를 초기화하고
Crypto 암복호화 라이브러리를 사용하여 문자열을 암복호화 하는 간단한 예시이다
// CryptoSession 객체 생성 및 기본 경로(${projectDir}/crypto/config.json)로 초기화
CryptoSession session = new CryptoSession();
// 암호화할 문자열
String plaintext = "Hello Crypto!";
// 암호화
String encrypt = session.encrypt(plaintext);
// 복호화
String decrypt = session.decrypt(encrypt);
CryptoSession
객체 초기화를 위한 파라메터 전달 방식의 설정 방법은 아래와 같다
resources/crypto/hotel/config.json
ClassPath 경로에 있는 파일을 사용하는 예시
Resource resource = new ClassPathResource(Path.of("crypto", "hotel", "config.json").toString());
CryptoSession session = new CryptoSession(resource.getFile().getPath());
config.json
파일을 바이트 배열로 변환하여 사용하는 예시
ClassPathResource resource = new ClassPathResource(Path.of("crypto", "hotel", "config.json").toString());
CryptoSession session = new CryptoSession(resource.getInputStream().readAllBytes());
config.json
파일을 InputStream
으로 변환하여 사용하는 예시
ClassPathResource resource = new ClassPathResource(Path.of("crypto", "hotel", "config.json").toString());
CryptoSession session = new CryptoSession(resource.getInputStream());
config.json
파일을 Map
으로 변환하여 사용하는 AWS
타입 예시
String awsKmsKeyArn = "arn:aws:kms:ap-northeast-2:123456789012:key/12345678-1234-1234-1234-123456789012";
String awsAccessKeyId = "AKXXXXXXXX;
String awsSecretAccessKey = "5XXXXXXXX;
String seed = "AXXXXXXXXXXXXXXXXXX";
String credential = "BXXXXXXXXXXXXXXXXXXXX";
CryptoSession session = new CryptoSession(
Map.of(
"aws_kms_key_arn", awsKmsKeyArn,
"aws_access_key_id", awsAccessKeyId,
"aws_secret_access_key", awsSecretAccessKey,
"seed", seed,
"credential", credential
)
);
config.json
파일을 Map
으로 변환하여 사용하는 LOCAL
타입 예시
String key = "KXXXXXXXXXXXXXXXX";
String iv = "00000000000000000000000000000000;
String seed = "AXXXXXXXXXXXXXXXXXX";
String credential = "BXXXXXXXXXXXXXXXXXXXX";
CryptoSession session = new CryptoSession(
Map.of(
"key", key,
"iv", iv,
"seed", seed,
"credential", credential
)
);
config.json
파일을 파라메터로 전달하여 사용하는 AWS
타입 예시
String awsKmsKeyArn = "arn:aws:kms:ap-northeast-2:123456789012:key/12345678-1234-1234-1234-123456789012";
String awsAccessKeyId = "AKXXXXXXXX;
String awsSecretAccessKey = "5XXXXXXXX;
String seed = "AXXXXXXXXXXXXXXXXXX";
String credential = "BXXXXXXXXXXXXXXXXXXXX";
CryptoSession session = new CryptoSession(awsKmsKeyArn, awsAccessKeyId, awsSecretAccessKey, seed, credential);
config.json
파일을 파라메터로 전달하여 사용하는 LOCAL
타입 예시
String key = "KXXXXXXXXXXXXXXXX";
String iv = "00000000000000000000000000000000;
String seed = "AXXXXXXXXXXXXXXXXXX";
String credential = "BXXXXXXXXXXXXXXXXXXXX";
CryptoSession session = new CryptoSession(key, iv, seed, credential);
다시 한번 강조하자면 Crypto 라이브러리는 JNI 라이브러리로 작성되어 있어 메모리 누수가 발생할 수 있으므로
반드시 CryptoSession
인스턴스를 Bean
으로 설정하여 사용하기를 권장한다
또한 CryptoSession
인스턴스 최초 생성시 AWS KMS와 통신을 하는데 Bean
으로 설정하지 않으면 매번 통신을 하게 되어 성능에도 문제가 되고
비용적인 측면에서도 비효율적이므로 반드시 Bean
으로 설정하여 사용하기를 권장한다
CryptoSession
객체를Bean
으로 설정하여 사용하면 객체 초기화로 인한 AWS KMS 반복 호출이나 성능 이슈 없이
Crypto 암복호화 라이브러리를 효율적으로 사용할 수 있으므로 반드시Bean
으로 설정하여 사용하기를 권장한다
자세한 사용법은 아래의 코드를 참고하기 바란다
기본 경로 ${projectDir}/crypto/config.json
에 위치한 config.json
파일을 사용하는 CryptoSession
Bean
설정 예시
@Configuration
public class CryptoConfig {
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
public CryptoSession cryptoSession() throws Exception {
return new CryptoSession();
}
}
CryptoSession
객체를 다중으로 사용할 경우 @Primary
어노테이션을 사용하여 기본 Bean
을 설정해 Bean
생성 충돌을 방지한다
같은 타입의 다중 Bean
사용시에는 @Qualifier
, @Resource
어노테이션을 사용해 Bean
을 선택하여 사용할 수 있다
@Configuration
public class CryptoConfig {
@Primary
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
public CryptoSession cryptoSession() throws Exception {
return new CryptoSession();
}
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
public CryptoSession secondCryptoSession() {
Resource resource = new ClassPathResource(Path.of("crypto", "hotel", "config.json").toString());
return new CryptoSession(resource.getFile().getPath());
}
}
CryptoSession
Bean을 사용하는 방법은 아래와 같다
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class CryptoFactoryConfigTest {
// @Autowired: 필드 타입을 기준으로 빈을 찾음
// @Resource: 필드 이름을 기준으로 빈을 찾음
// 지정된 bean 이름으로 사용하기 위해서 `@Resource` 어노테이션을 사용하여 Bean을 찾아 사용할 수 있다
@Resource
private CryptoSession cryptoSession;
@Resource
private CryptoSession secondCryptoSession;
@Test
void base_Initializing_cryptoSession() {
String plaintext = "Hello Crypto!";
String encrypt = cryptoSession.encrypt(plaintext);
String decrypt = cryptoSession.decrypt(encrypt);
String encryptHotel = secondCryptoSession.encrypt(plaintext);
String decryptHotel = secondCryptoSession.decrypt(encryptHotel);
}
}
Swagger API Docs 의 JPA Example API 코드 참고
JPA 에서 Entity의 특정 속성을 암복호화 하기 위해서 Converter 를 구현하여 사용 할 수 있다
JPA 에서 Crypto 라이브러리를 Converter 로 사용하기 위해서는 @Converter
어노테이션을 사용하여 AttributeConverter
를 구현해야 한다
@RequiredArgsConstructor
@Converter
public class CryptoConverter implements AttributeConverter<String, String> {
@Resource
private final CryptoSession basicCryptoSession;
/**
* 엔티티 -> DB 시 데이터 변환
* Base64 인코딩
* @param value
* @return
*/
@Override
public String convertToDatabaseColumn(String value) {
if (StringUtils.isEmpty(value)) return value;
String encrypt = basicCryptoSession.encrypt(value);
log.debug("[Crypto] Converter Encrypt : {}", encrypt);
return encrypt;
}
/**
* DB -> 엔티티 시 데이터 변환
* @param value
* @return
*/
@Override
public String convertToEntityAttribute(String value) {
if (StringUtils.isEmpty(value)) return value;
String decrypt = basicCryptoSession.decrypt(value);
log.debug("[Crypto] Converter Decrypt : {}", decrypt);
return decrypt;
}
}
JPA Entity 에 Converter 를 적용하기 위해서는 @Convert
어노테이션을 사용하여 Converter 를 지정해야 한다
- Entity 속성에 Converter 적용 코드: ${projectDir}/src/main/java/com/freelife/domain/Member.java
@Data
@Entity
@NoArgsConstructor
public class Member {
...
/**
* CryptoConverter 를 사용하여 암복호화 처리
*/
@Convert(converter = CryptoConverter.class)
private String password;
...
}
Swagger API Docs 의 Querydsl Example API 코드 참고
Querydsl 은 JPA 구성과 동일하여 추가 설명은 생략한다
Swagger API Docs 의 Mybatis Example API 코드 참고
Mybatis 는 TypeHandler
를 구현하고 Mapper 에 적용하여 Crypto 라이브러리를 사용해서 특정 속성을 암복호화 시킬 수 있다
Mybatis 에서 Crypto 라이브러리를 사용하기 위해서는 TypeHandler
를 구현해야 한다
@Component
public class CryptoHandler implements TypeHandler<String> {
@Resource
private CryptoSession basicCryptoSession;
public String encrypt(String value) {
if (StringUtils.isEmpty(value)) return value;
String encrypt = basicCryptoSession.encrypt(value);
log.debug("[Crypto] Converter Encrypt : {}", encrypt);
return encrypt;
}
public String decrypt(String value) {
if (StringUtils.isEmpty(value)) return value;
String decrypt = basicCryptoSession.decrypt(value);
log.debug("[Crypto] Converter Decrypt : {}", decrypt);
return decrypt;
}
@Override
public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
if ( StringUtils.isNotEmpty(parameter) )
parameter = encrypt(parameter);
ps.setString(i, parameter);
}
@Override
public String getResult(ResultSet rs, String columnName) throws SQLException {
return decrypt(rs.getString(columnName));
}
@Override
public String getResult(ResultSet rs, int columnIndex) throws SQLException {
return decrypt(rs.getString(columnIndex));
}
@Override
public String getResult(CallableStatement cs, int columnIndex) throws SQLException {
return decrypt(cs.getString(columnIndex));
}
}
Mybatis Mapper 에서 Crypto 라이브러리를 사용하기 위해서는 TypeHandler
를 지정해야 한다
- Mapper 설정 코드: ${proejctDir}/src/main/resources/mapper/MemberMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.freelife.repository.mybatis.MemberMapper">
<resultMap id="member" type="Member">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="nickName" column="nick_name"/>
<result property="password" column="password" typeHandler="CryptoHandler"/>
<result property="encPassword" column="enc_password" typeHandler="CryptoNoDecHandler"/>
</resultMap>
<insert id="save" parameterType="Member" useGeneratedKeys="true"
keyProperty="id" keyColumn="id">
INSERT INTO member (name, nick_name, password, enc_password)
VALUES (#{member.name},
#{member.nickName},
#{member.password, typeHandler=CryptoHandler},
#{member.encPassword, typeHandler=CryptoNoDecHandler});
</insert>
<update id="update" parameterType="Member">
UPDATE member
SET name = #{member.name},
nick_name = #{member.nickName},
password = #{member.password, typeHandler=CryptoHandler},
enc_password = #{member.encPassword, typeHandler=CryptoNoDecHandler}
WHERE id = #{member.id};
</update>
</mapper>
JNI 라이브러리 사용시에는 아래와 같은 문제가 발생할 수 있어 Tomcat 전체를 재시작하여 JVM을 종료하고,
네이티브 라이브러리 및 리소스를 완전히 정리하는 것이 좋지만 Context Reload로 서비스를 운영하고자 한다면 별도의 추가 작업이 필요하다
- 네이티브 라이브러리 언로드(Native Library Unload) 이슈
- 리소스 누수(Resource Leak)
- 객체 상태 불일치(Object State Inconsistency)
- 다중 클래스 로더 문제(Multiple Class Loader Issue)
위의 문제들을 해결하기 위해 빌드 도구에서는 JNI 라이브러리를 제외하고 외장 톰캣 라이브러리 폴더에 직접 추가 해야 한다
$TOMCAT_HOME/lib
폴더에 crypto-core-0.0.2.RC1.jar
파일을 직접 추가
외장 톰캣에 배포시 build.gradle
파일에 complieOnly
설정을 추가하여 라이브러리를 제외하고 배포할 수 있다
dependencies {
compileOnly 'com.freelife.crypto:crypto-core:0.0.2.RC1'
testCompileOnly 'com.freelife.crypto:crypto-core:0.0.2.RC1'
}
외장 톰캣에 배포시 pom.xml
파일에 provided
설정을 추가하여 라이브러리를 제외하고 배포할 수 있다
<dependency>
<groupId>com.freelife.crypto</groupId>
<artifactId>crypto-core</artifactId>
<version>0.0.2.RC1</version>
<scope>provided</scope>
</dependency>
부하 테스트는 locust
툴로 테스트
# locust 테스트에 필요한 패키지 설치
$ brew install python
$ brew install locust
# 아래의 명령으로 테스트
$ locust -f ./find-all.py --host=https://localhost:8080
[2024-11-11 17:41:49,492] home/INFO/locust.main: Starting Locust 2.32.2
[2024-11-11 17:41:49,493] home/INFO/locust.main: Starting web interface at http://0.0.0.0:8089
- 테스트에 사용한 locust 스크립트 참고: test_locust_script
- 참고
CryptoSessionInit<T extends Enum<T>>
객체는 다중 CryptoSession
객체를 타입세이프하게 생성하기 위한 초기화 클래스로
CryptoSessionFactory
, CryptoSessionFactoryBean
, CryptoSessionImpl
클래스를 사용하여 다중 CryptoSession
객체를 초기화할 경우
편리한 초기 설정을 돕고자 만들어졌다
- CryptoSessionInit 구현 예시 코드: ${proejctDir}/src/main/java/com/freelife/factory/CryptoSessionInit.java
- CryptoSessionType Enum 클래스 구현 예시 코드: ${proejctDir}/src/main/java/com/freelife/factory/CryptoSessionType.java
CryptoSessionInit
객체는 CryptoSession
에서 가능한 총 8가지 타입(BASE_PATH
, BYTES
, INPUT_STREAM
, PATH
, MAP
, PARAMS
, LOCAL_MAP
, LOCAL_PARAMS
)의
CryptoSession
객체를 초기화할 수 있는 메서드를 제공한다
CryptoSessionInit
객체 사용을 위해 각 서비스에서 사용할 CryptoSessionType
Enum 클래스를 커스텀하게 생성하여 타입을 지정해야 된다
CryptoSessionType
생성 예시
public enum CryptoSessionType {
HOTEL, // 호텔 서비스용 CryptoSession 타입
AIR, // 항공 서비스용 CryptoSession 타입
TOACT // TOACT 서비스용 CryptoSession 타입
}
설정하는 방법은 위에서 설명한 CryptoSession
객체 초기화를 위한 설정 방법과 비슷하므로 타입별 메서드를 사용하는 방법만 참고하면 된다
BASE_PATH
타입은 기본 설정 타입으로 단일 DB를 사용하는 경우에는 아래의 기본 경로에 config.json
파일을 위치시키고 사용할 수 있다
현재 지원되는 기본 경로는 아래와 같다
${projectDir}/crypto/config.json
/var/opt/crypto/config.json
CryptoSession
객체 초기화시 별도의 파라메터를 넘겨주지 않으면 기본 경로에 위치한 config.json
파일을 사용한다
만약 기본 경로에 위치한 config.json
파일이 존재하지 않으면 에러가 발생하므로 기본 경로 타입으로 설정시
반드시 기본 경로에 config.json
파일을 생성 후 사용하기 바란다
CryptoSessionInit<CryptoSessionType> basePathInit = CryptoSessionInit.ofBasePath(CryptoSessionType.HOTEL);
BYTES
타입은 config.json
파일을 바이트 배열로 변환하여 사용하는 타입이다
ClassPathResource resource = new ClassPathResource(Path.of("crypto", "hotel", "config.json").toString());
CryptoSessionInit<CryptoSessionType> bytesInit = CryptoSessionInit.ofBytes(
CryptoSessionType.HOTEL,
resource.getInputStream().readAllBytes()
);
INPUT_STREAM
타입은 config.json
파일을 InputStream
으로 변환하여 사용하는 타입이다
ClassPathResource resource = new ClassPathResource(Path.of("crypto", "hotel", "config.json").toString());
CryptoSessionInit<CryptoSessionType> inputStreamInit = CryptoSessionInit.ofInputStream(
CryptoSessionType.HOTEL,
resource.getInputStream()
);
PATH
타입은 config.json
파일의 경로를 지정하여 사용하는 타입이다
Resource resource = new ClassPathResource(Path.of("crypto", "hotel", "config.json").toString());
CryptoSessionInit<CryptoSessionType> pathInit = CryptoSessionInit.ofPath(
CryptoSessionType.HOTEL,
resource.getFile().getPath()
);
MAP
타입은 설정 정보를 Map
으로 변환하여 사용하는 타입이다
String awsKmsKeyArn = "arn:aws:kms:ap-northeast-2:123456789012:key/12345678-1234-1234-1234-123456789012";
String awsAccessKeyId = "AKXXXXXXXX;
String awsSecretAccessKey = "5XXXXXXXX;
String seed = "AXXXXXXXXXXXXXXXXXX";
String credential = "BXXXXXXXXXXXXXXXXXXXX";
CryptoSessionInit<CryptoSessionType> mapInit = CryptoSessionInit.ofMap(
CryptoSessionType.HOTEL,
Map.of(
"aws_kms_key_arn", awsKmsKeyArn,
"aws_access_key_id", awsAccessKeyId,
"aws_secret_access_key", awsSecretAccessKey,
"seed", seed,
"credential", credential
)
);
PARAMS
타입은 설정 정보를 파라메터로 전달하여 사용하는 타입이다
String awsKmsKeyArn = "arn:aws:kms:ap-northeast-2:123456789012:key/12345678-1234-1234-1234-123456789012";
String awsAccessKeyId = "AKXXXXXXXX;
String awsSecretAccessKey = "5XXXXXXXX;
String seed = "AXXXXXXXXXXXXXXXXXX";
String credential = "BXXXXXXXXXXXXXXXXXXXX";
CryptoSessionInit<CryptoSessionType> paramsInit = CryptoSessionInit.ofParams(
CryptoSessionType.HOTEL,
awsKmsKeyArn, awsAccessKeyId, awsSecretAccessKey, seed, credential
);
LOCAL_MAP
타입은 설정 정보를 AWS 정보를 제외하고 key
, iv
를 포함해 Map
으로 변환하여 사용하는 타입이다
String key = "KXXXXXXXXXXXXXXXX";
String iv = "00000000000000000000000000000000;
String seed = "AXXXXXXXXXXXXXXXXXX";
String credential = "BXXXXXXXXXXXXXXXXXXXX";
CryptoSessionInit<CryptoSessionType> mapInit = CryptoSessionInit.ofLocalMap(
CryptoSessionType.HOTEL,
Map.of(
"key", key,
"iv", iv,
"seed", seed,
"credential", credential
)
);
LOCAL_PARAMS
타입은 LOCAL 설정 정보를 파라메터로 전달하여 사용하는 타입이다
String key = "KXXXXXXXXXXXXXXXX";
String iv = "00000000000000000000000000000000;
String seed = "AXXXXXXXXXXXXXXXXXX";
String credential = "BXXXXXXXXXXXXXXXXXXXX";
CryptoSessionInit<CryptoSessionType> paramsInit = CryptoSessionInit.ofLocalParams(
CryptoSessionType.HOTEL,
key, iv, seed, credential
);
CryptoSessionFactory<T extends Enum<T>>
는 CryptoSessionInit<T extends Enum<T>>
객체를 사용하여 CryptoSession
다중 Bean
설정을 지원하기 위한 Factory 클래스이다
자세한 사용법은 아래의 코드를 참고하기 바란다
- CryptoSessionFactory 구현 예시 코드: ${proejctDir}/src/main/java/com/freelife/factory/CryptoSessionFactory.java
- 설정 예시 코드: ${proejctDir}/src/main/java/com/freelife/factory/CryptoFactoryConfig.java
public enum CryptoSessionType {
HOTEL, // 호텔 서비스용 CryptoSession 타입
AIR, // 항공 서비스용 CryptoSession 타입
TOACT // TOACT 서비스용 CryptoSession 타입
}
@Configuration
public class CryptoFactoryConfig {
@Bean
public CryptoSessionFactory<CryptoSessionType> cryptoSessionFactory() {
Resource hotelConfig = new ClassPathResource(Path.of("crypto", "hotel", "config.json").toString());
Resource airConfig = new ClassPathResource(Path.of("crypto", "air", "config.json").toString());
List<CryptoSessionInit<CryptoSessionType>> cryptoSessionInits = List.of(
// HOTEL CryptoSession 생성
CryptoSessionInit.ofPath(CryptoSessionType.HOTEL, hotelConfig.getFile().getPath()),
// AIR CryptoSession 생성
CryptoSessionInit.ofPath(CryptoSessionType.AIR, airConfig.getFile().getPath())
);
return new CryptoSessionFactory<>(cryptoSessionInits);
}
}
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class CryptoFactoryConfigTest {
// @Autowired: 필드 타입을 기준으로 빈을 찾음
// @Resource: 필드 이름을 기준으로 빈을 찾음
// 지정된 bean 이름으로 사용하기 위해서 `@Resource` 어노테이션을 사용하여 Bean을 찾아 사용할 수 있다
@Resource
private CryptoSessionFactory<CryptoSessionType> cryptoSessionFactory;
@Test
void cryptoSessionFactoryTest() {
String plainText = "Hello Crypto!";
// HOTEL CryptoSession 사용
CryptoSession hotelCryptoSession = cryptoSessionFactory.of(CryptoSessionType.HOTEL);
// AIR CryptoSession 사용
CryptoSession airCryptoSession = cryptoSessionFactory.of(CryptoSessionType.AIR);
// HOTEL CryptoSession 암복호화
String encryptHotel = hotelCryptoSession.encrypt(plainText);
String decryptHotel = hotelCryptoSession.decrypt(encryptHotel);
// AIR CryptoSession 암복호화
String encryptAir = airCryptoSession.encrypt(plainText);
String decryptAir = airCryptoSession.decrypt(encryptAir);
}
}
CryptoSessionFactoryBean<T extends Enum<T>>
는 SpringFramework 의 AbstractFactoryBean<T>
추상 클래스를 상속받아
쉽게 FactoryBean 구현할 수 있도록 도와주는 클래스이다
Crypto 암복호화 라이브러리에서는 종속성 문제로 인해 직접 제공하지 않고 사용을 위해서는 별도의 구성이 필요하다
자세한 사용법은 아래의 코드를 참고하기 바란다
CryptoSessionFactoryBean
구현 예시 코드: ${proejctDir}/src/main/java/com/freelife/factory/factorybean/CryptoSessionFactoryBean.javaCryptoSessions
구현 예시 코드: ${proejctDir}/src/main/java/com/freelife/factory/factorybean/CryptoSessions.java- 설정 예시 코드: ${proejctDir}/src/main/java/com/freelife/factory/factorybean/CryptoFactoryBeanConfig.java
AbstractFactoryBean<T>
추상 클래스를 상속받아 Crypto 객체 초기화를 위한 CryptoSessionFactoryBean
클래스를 구성
public class CryptoSessionFactoryBean<T extends Enum<T>> extends AbstractFactoryBean<CryptoSession> {
private CryptoSessionInit<T> cryptoSessionInit;
CryptoSessionFactoryBean(CryptoSessionInit<T> cryptoSessionInit) {
this.cryptoSessionInit = cryptoSessionInit;
}
@Override
public Class<?> getObjectType() {
return CryptoSession.class;
}
@Override
protected CryptoSession createInstance() throws Exception {
log.info("Initialize CryptoSession FactoryBean: {}: {}",cryptoSessionInit.getCryptoSessionType(), cryptoSessionInit.getInitType());
return CryptoSession.of(cryptoSessionInit);
}
public static <T extends Enum<T>> CryptoSessionFactoryBean<T> of(CryptoSessionInit<T> cryptoSessionInit) {
if (cryptoSessionInit == null)
throw new IllegalArgumentException("CryptoSessionInit must not be null");
return new CryptoSessionFactoryBean<>(cryptoSessionInit);
}
}
다중 CryptoSession
객체를 저장하기 위한 CryptoSessions
객체 생성
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CryptoSessions<T extends Enum<T>> {
private Map<T, CryptoSession> cryptoSessionMap = new HashMap<>();
private CryptoSession hotelCryptoSession;
private CryptoSession airCryptoSession;
}
CryptoSessionFactoryBean
클래스를 사용하여 CryptoSessionFactory
Bean 과 CryptoSessions
Bean 설정
@Configuration
@RequiredArgsConstructor
public class CryptoFactoryBeanConfig {
private final CryptoProperties cryptoProperties;
/**
* Hotel CryptoSession Bean 생성
* FactoryBean 타입으로 리턴하지만 실제 사용시에는 CryptoSession 타입으로 사용
* ex1: @Resource(name = "hotelCryptoSession") CryptoSession hotelCryptoSession;
* ex2: @Autowired CryptoSession hotelCryptoSession;
*/
@Bean
public CryptoSessionFactoryBean<CryptoSessionType> hotelCryptoSession() throws IOException {
ClassPathResource classPathResource = new ClassPathResource(cryptoProperties.getHotel().getPath());
return CryptoSessionFactoryBean.of(
CryptoSessionInit.ofInputStream(
CryptoSessionType.HOTEL,
classPathResource.getInputStream()));
}
/**
* Air CryptoSession Bean 생성
* FactoryBean 타입으로 리턴하지만 실제 사용시에는 CryptoSession 타입으로 사용
* ex1: @Resource(name = "airCryptoSession") CryptoSession airCryptoSession;
* ex2: @Autowired CryptoSession airCryptoSession;
*/
@Bean
public CryptoSessionFactoryBean<CryptoSessionType> airCryptoSession() throws IOException {
ClassPathResource classPathResource = new ClassPathResource(cryptoProperties.getAir().getPath());
return CryptoSessionFactoryBean.of(
CryptoSessionInit.ofBytes(
CryptoSessionType.AIR,
classPathResource.getInputStream().readAllBytes()));
}
/**
* 생성된 FactoryBean 을 CryptoSessions 에 등록하여 다중 세션을 관리
*/
@Bean
public CryptoSessions<CryptoSessionType> cryptoSessions() throws Exception {
CryptoSessions<CryptoSessionType> cryptoSessions = new CryptoSessions<>();
cryptoSessions.setHotelCryptoSession(hotelCryptoSession().getObject());
cryptoSessions.setAirCryptoSession(airCryptoSession().getObject());
return cryptoSessions;
}
}
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class CryptoFactoryBeanConfigTest {
@Resource
private CryptoSession hotelCryptoSession;
@Resource
private CryptoSession airCryptoSession;
@Resource
private CryptoSessions<CryptoSessionType> cryptoSessions;
@Test
void CryptoFactoryBeanHotelTest() {
String plainText = "Hello Crypto!";
// HOTEL CryptoSession(FactoryBean) 사용
String encrypt = hotelCryptoSession.encrypt(plainText);
String decrypt = hotelCryptoSession.decrypt(encrypt);
}
@Test
void CryptoFactoryBeanAirTest() {
String plainText = "Hello Crypto!";
// AIR CryptoSession(FactoryBean) 사용
String encrypt = airCryptoSession.encrypt(plainText);
String decrypt = airCryptoSession.decrypt(encrypt);
}
@Test
void cryptoSessionsTest() {
String plainText = "Hello Crypto!";
// HOTEL CryptoSession(CryptoSessions) 사용
String encryptHotel = cryptoSessions.getHotelCryptoSession().encrypt(plainText);
String decryptHotel = cryptoSessions.getHotelCryptoSession().decrypt(encryptHotel);
// AIR CryptoSession(CryptoSessions) 사용
String encryptAir = cryptoSessions.getAirCryptoSession().encrypt(plainText);
String decryptAir = cryptoSessions.getAirCryptoSession().decrypt(encryptAir);
}
}