지난 5월 26일 전시회, 6월 10일 최종 보고서 제출을 마지막으로 산학 연계 프로젝트를 완료하였다. 

프로젝트를 완료하고도 기말고사 때문에 정신이 없어서 이제야 후기를 작성한다.. 

 

5월 26일 전시회 사진 

오전 10시 30분부터 오후 3시까지 진행했다. 

오전 11시 30분에 개회식을 시작하고 오후 1시부터 심사위원분들이 부스를 돌아다니면서 설명을 듣고, 프로젝트를 평가받았다. 상위 7팀에게는 상장과 상금을 부여한다. (총장상, 최우수상, 우수상, 장려상 순)

남은 시간동안은 부스에 방문하는 분들에게 프로젝트를 소개하는 시간을 가졌다. 

또는 전시회 다른 한 편에서 진행하고 있는 Job Fair에 참여하기도 했다.

 

사실 우리 팀은 플랫폼으로 프로젝트를 진행했고, 플랫폼은 평가를 잘받는 편이 아니여서 (보통 신기술인 인공지능, VR, 딥러닝쪽에서 평가를 더 잘 받는다) 상에 대한 기대는 내려놓고 있었다. 

 

게다가 바로 옆 부스가 VR로 신생아 키우기 시뮬레이션을 주제로 프로젝트를 진행했는데, 너무 잘했고 그 부스에 시선이 많이 쏠렸었다. (학교 방송팀에서도 그쪽 사진을 많이 찍어서 사진기를 피해다니라 바빴었다..ㅎㅎ) 

 

 

 

 

그런데!!!

너무 영광스럽게도 우수상을 받게 되었다. 

전시회 이전에 지도 교수님과 면담을 할때도 

'바로 출시해도 될 것같다. 너무 과하게 잘해서 오히려 팀원들이 너무 힘들었던 것은 아닌지 걱정된다.'

라는 평을 받았었는데, 심사 위원분들도 프로젝트의 규모와 완성도를 좋게 봐주신것 같다. 

 

개인적으로는 조금 아쉬운 부분이 있기도 하고, 너무 힘들기도 했지만 약 1년동안의 노력이 보답받은 것같아서 기뻤다. 

같이 고생해준 팀원들에게도 다시 한번 감사하다. 

 

프로젝트를 진행하는 데 집중하느라 블로그 글을 많이 못 썼는데, 

이제부터라도 조금씩 회고하며 정리해보려고 한다. 

 

여담으로 프로젝트 서버를 업체측에서 감사하게도

특별한 일이 없으면 닫지 않겠다고 해주셔서 사이트에 아직도 접속 가능하다. 

(다만, 이걸 그냥 공개하기에는 악의적인 공격을 받을 수 있지 않을까하는 우려가 있어서 

신뢰할 수 있는 사람에게만 공개하려고 한다)

 

 

음... 후기를 적기 전까지만 해도 할 말이 많이 남아 있었던 것같은데,

막상 쓰려고 하니 무슨 말을 쓰려고 했는지 기억이 잘 안난다.ㅎㅎ 

 

그러므로 전시회에 대한 후기는 여기서 끝

프로젝트 기술 스택

spring boot 2.5.4

gradle 7.1.1

mybatis 

 

지난 포스팅에서 언급했 듯이 설정 파일을 분리해서 읽는 방식에는 문제가 있었고, 이를 해결하기 위해 Spring Cloud Config를 적용하게 되었다. 

사실 이전에도 Spring Cloud Config에 대해서 알고 있었고, 지난 여름 방학동안 다른 프로젝트에 이를 적용해보려고 했었는 데 적용하는 데 실패했었다. 

당시에는 해결방안을 몰라서 적용하지 못했었고, 이번에도 그에 대한 두려움이 있어서 다른 방식을 이용하게 된 것이였다. 하지만 이번 기회에 다시 재도전하여 설정을 적용해봤다. 

 

 

Spring Cloud Config란? 

Spring Cloud Config는 외부 설정 정보를 서버에 올려두고, 클라이언트 어플리케이션에서 이를 사용할 수 있는 기능이다. 예를 들어 github의 private repository에 default, dev, prod의 설정 정보를 올려두고, 이 repository를 Cloud Config Server와 연결하면, client application에서 profile에 따라 Clould Config Server의 설정 파일을 이용할 수 있는 것이다. 당연히 서로 다른 client application에서도 같은 서버를 이용한다면, 같은 설정 정보를 이용하는 것도 가능하다. 

 

1. 개발 환경 분리 및 설정 파일 작성

개발 환경(Phase)이란?

개발 환경이란 Application이 동작하는 환경을 의미한다. 일반적으로 프로그램을 개발할 때, 우선 local에서 application을 실행하면서 디버깅이나 테스틀 진행하고, 실제 운영 환경과 비슷한 dev 환경이나, beta, rc 환경에 순차적으로 배포하면서 테스트를 진행하게 된다. 이러한 테스트를 통과한 코드를 운영 환경(prod)에서 배포하게 된다. 

개발 환경 마다 다른 설정을 적용할 수 있도록 Spring Boot에서는 Profile 기능을 제공하고 있다. 

 

각 개발환경에 대한 특징이 궁금하다면 아래의 블로그를 참고하면 좋을 것이다. 

https://ozofweird.tistory.com/entry/%EC%B0%B8%EA%B3%A0%ED%95%98%EA%B8%B0-%EC%A2%8B%EC%9D%80-%EB%82%B4%EC%9A%A9-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EB%B6%84%EB%A6%AC

 

[참고하기 좋은 내용] 개발 환경 분리

1. 개발 환경 분리 1) Local 말 그대로 개발할 때의 각자 개발자 PC 환경을 뜻한다. 이 때 중요한 점은 코드를 합칠 때의 문제가 발생하지 않도록 모든 개발자가 동일한 개발 환경을 사용해야한 다는

ozofweird.tistory.com

https://jungseob86.tistory.com/11

 

[기타] 개발 환경(Phase)이란 무엇일까?

개발 환경(Phase)란? 개발 환경( Phase )은 Application이 동작하는 환경을 의미한다. 따라서 대부분의 Framework는 각각의 개발 환경마다 설정 값을 다르게 셋팅할 수 있는 기능이 있다. 대표적으로 Spring B

jungseob86.tistory.com

 

내가 우리팀에 적용하기 위해 구상한 개발환경은 다음과 같다. 

defualt(local)

: 우리팀의 경우 개발 단계에서 나는 local DB에서 개발을 진행한 다음 서버의 DB에 통합하는 방법을 이용하고 있고, 다른 팀원의 경우 서버의 DB에서 개발을 진행하고 있다. 따라서 defualt 환경의 경우 개발자 마다 별도로 설정 파일을 작성하고, 이를 이용할 수 있도록 구성했다. 

또한 일반적으로도 개발자마다 local DB의 계정명과 비밀번호가 다르다는 점을 고려하면 Spring Cloud Config의 설정 정보를 공통적으로 이용하긴 힘들다는 것이 나의 생각이다. 

결국은 "설정 정보의 민감한 정보 숨기기 1"에서 사용했던 설정 정보를 2가지로 나누는 방식을 유지할 것이다. 

그러나, DB 외의 다른 설정 정보 (예를 틀어 jwt 암호화 키, Open API의 키 값과 비밀번호 등 )은 Spring Cloud Config의 설정을 이용할 수 있도록 구성했다. 

 

wholeinone-defualt.yml

(이때 각 환경별 설정 정보의 파일 이름은 {어플리케이션 이름}-{환경 이름}.yml 형식으로 작성한다. )

config-name: default
searchapi:
  naverid: {client 키 값}
  naversecret: {secret 값}

jwt:
  secret:
    user_info_password_key: {암호화 키}
    token_validity_in_seconds: {만료 시간}

앞서 말했 듯이 DB 정보 외의 설정 정보를 작성한다. 

 

dev

: 운영환경과 유사하게 만든 개발 서버, 서버의 DB를 이용한다. 

 

wholeinone-dev.yml

config-name: dev
spring:
  datasource:
    hikari:
      driver-class-name : {driver}
      jdbc-url : {url}
      user-name : (계정명)
      password : {비밀번호}

searchapi:
  naverid: {client 키}
  naversecret: {secret}

jwt:
  secret:
    user_info_password_key:{암호화 키}
    token_validity_in_seconds: {만료 시간}

참고로 localhost를 기준으로 jdbc-url를 작성하는 방법은 다음과 같다.  

aws의 RDS를 이용하는 경우 localhost대신 RDS의 endpoint를 작성하면된다. 

jdbc-url: jdbc:mysql://localhost:3306/{DB이름}?allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=utf8&useSSL=false

allowPublicKeyRetrieval=true: mysql 8.0이후로 외부에서 mysql db 접속시 필수로 필요 (기본 false)

useUnicode=true: unicode로 전송한다.

characterEncoding=utf8: utf8로 인코딩한다. 

useSSL=false: ssl 접속을 사용하지 않는다 (기본 값 true)

이외에도 다양한 옵션을 추가할 수 있다. 

 

prod 

: 원래라면 운영 환경이지만, 우선은 front 분들이 이용하는 환경이라고 생각하고 구성했다. dev와 마찬가지로 개발 서버를 이용하지만, 오류 발생시 slack을 통해 오류 메시지를 출력한다는 점이 다르다. 또한 서버에서 배포시 사용하는 환경이다.

(slack 연동 방법은 추후 소개)

wholeinone-prod.yml

config-name: prod
spring:
  datasource:
    hikari:
      driver-class-name : {driver}
      jdbc-url : {url}
      username : {계정명}
      password : {비밀번호}

searchapi:
  naverid: {client 키}
  naversecret: {secret}

jwt:
  secret:
    user_info_password_key: {client 키}
    token_validity_in_seconds: {만료시간}

logging:
  slack:
    webhook-uri: {url}
  config: classpath:logback-spring.xml

jdbc-url에 서버의 DB 연결정보를 적는다. 

 

이렇게 작성한 wholeinone-default.yml, wholeinone-dev.yml, wholeinone-prod.yml 파일을 깋허브 private 저장소에 올린다. 나는 wholeinone-config라는 이름의 저장소에 올렸다. 

 

2. Clould Config Server Application 구현

의존성 추가(bulid.gradle)

dependencies {
   implementation 'org.springframework.cloud:spring-cloud-config-server'
   testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

dependencyManagement {
   imports {
      mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
   }
}

그리고 본인의 Server 프로젝트에서 main 함수가 있는 클래스에 @EnableConfigServer를 추가한다. 

아래는 예시

@EnableConfigServer
@SpringBootApplication
public class WholeinoneConfigServerApplication {
   public static void main(String[] args) {
      SpringApplication.run(WholeinoneConfigServerApplication.class, args);
   }
}

application.yml

server에서 활용할 설정정보가 저장된 깃허브 계정과 연결을 위한 설정을 작성한다. 

spring:
  application:
    name: configserver
  cloud:
    config:
      server:
        git:
          uri: https://github.com/{깃허브 계정}/{설정 정보를 올린 깃허브 저장소}
          username: {깃허브 계정}
          password: {깃허브 계정의 키값}
        default-label: master //main이 되는 브랜치 (개인마다 다를 수 있으니 확인 필요)

server:
  port: 8082 // Server가 실행될 포트

여기서 password는 깃허브의 personal access token을 입력하면 되는데 발급 방법은 다음과 같다. 

https://record-of-coding.tistory.com/24

 

Github의 access token 발급하기 (원격으로 깃허브 로그인하기)

Settings > Developer Settings > Personal access tokens Github의 우측 상단의 프로필 사진을 클릭하고 Settings 클릭 Settings 페이지의 좌측 사이드바에서 Developer Settings 클릭 Developer settings에서..

record-of-coding.tistory.com

 

서버를 실행하고,

http://localhost:8082/wholeinone/defualt 등으로 get 요청을 보냈을 때 wholeinone-default.yml의 값이 정상적으로 출력된다면 config server가 정상적으로 작동하고 있는 것이다. 

({어플리케이션 이름}-{환경 이름}.yml 형식으로 설정 정보의 파일명을 작성하게 되는 데, http://localhost:8082/{어플리케이션 이름}/{환경 이름}과 같은 url로 get요청을 보내면 된다. )

 

3. Client Application 구현

설정정보를 이용하는 스프링 프로젝트에 spring cloud config의 의존성을 추가한다. 

build.gradle

{
   //Config Server Client
   implementation 'org.springframework.cloud:spring-cloud-starter-config'
}

dependencyManagement {
   imports {
      mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
   }
}

스프링 프로젝트에서 client의 설정 정보를 읽기 전에 server의 설정정보를 읽어와야 하는데, 이를 위해서 bootstrap.yml을 작성한다. bootstrap.yml은 스트링 부트 프로젝트가 시작할 때 application.yml 보다 먼저 로드된다. 

그런데 bootstrap.yml을 자동으로 읽어오는 기능이 최신의 spring cloud에서 빠지게 되어 의존성을 추가해줘야 한다. 

(사실 전에 구현에 실패했던 이유가 이 의존성을 추가해야 한다는 것을 몰라서였다..)

   implementation 'org.springframework.cloud:spring-cloud-starter-bootstrap'

bootstrap.yml

spring:
  cloud:
    config:
      uri: {spring cloud server가 실행되고 있는 uri}:{포트}
      name: {프로젝트 이름}

 

설정 정보 읽기 

로컬의 설정 정보를 읽는 방법과 spring cloud config를 사용하는 방법 2가지를 이용하기 때문에 설정 정보를 읽는 방법도 약간 복잡하게 구성하게 되었다. 

 

설정 정보를 읽는 클래스를 3가지로 구성하게 되었는 데 각 클래스의 기능은 다음과 같다. 

DefaultGlobalPropertyConfig

: defualt 프로필에서 local의 설정 정보를 통해 개발자들마다 서로 다른 설정 정보를 구성하고 읽어 올 수 있다. 

GlobalPropertyConfig

:  dev, prod 프로필에서 사용하는 서버의 DB 정보를 읽어온다. 

SecretPropertyConfig

: default, dev, prod 에서 공통으로 사용하는 secret 값을 설정 정보에서 읽어온다 (예를 들어 jwt 암호화 키 등)

 

DefaultGlobalPropertyConfig

전체 코드는 "설정 정보의 민감한 정보 숨기기 1"과 동일한데, Profile 설정과 RefreshScope 어노테이션만 추가한다.

@Profile("default")는 default 프로필에서 이 빈을 사용하겠다는 의미이다.

@RefreshScope은 서버의 설정 정보에 변경이 생겼을 때 변경 정보를 갱신할 수 있다. 

@Configuration
@Profile("default")
@RefreshScope
@PropertySources({
        @PropertySource( value = "classpath:/properties/config.yaml", ignoreResourceNotFound = true )//db 설정 파일 경로
//        @PropertySource( value = "file:${user.home}/env/dev-config.properties", ignoreResourceNotFound = true) // 배포시 배포 환경의 디렉토리 주소
})

@Getter
public class DefaultGlobalPropertyConfig {
    @Value("${driver-class-name}")
    private String driverClassName;

    @Value("${jdbc-url}")
    private String url;

    @Value("${username}")
    private String username;

    @Value("${password}")
    private String password;
}

 

GlobalPropertyConfig

!default는 default가 아닌 profile에서 이 빈을 이용하겠다는 의미이다. 

@Configuration
@Profile("!default")
@RefreshScope
@Getter
public class GlobalPropertyConfig {
    @Value("${spring.datasource.hikari.driver-class-name}")
    private String driverClassName;

    @Value("${spring.datasource.hikari.jdbc-url}")
    private String url;

    @Value("${spring.datasource.hikari.username}")
    private String username;

    @Value("${spring.datasource.hikari.password}")
    private String password;

}

 

SecretPropertyConfig

모든 프로필에서 공통적으로 사용할 설정 정보를 주입받는다. 

@Configuration
@Getter
@RefreshScope
public class SecretPropertyConfig {
    @Value("${jwt.secret.user_info_password_key}")
    private String userInfoPasswordKey;

    @Value("${jwt.secret.token_validity_in_seconds}")
    private String tokenValidityInSeconds;

    @Value("${searchapi.naverid}")
    private String naverId;

    @Value("${searchapi.naversecret}")
    private String naverSecret;
}

 

문제는 DBConfig 인데, 

원래는 하나의 DBConfig에서 프로필마다 다른 클래스를 이용하도록 구현하고 싶었는 데, 방법을 모르겠어서 그냥 DBConfig도 2가지를 만들게 되었다. 

Default 프로필에서 사용하는 DB 설정

@Configuration
@EnableTransactionManagement
@Profile("default")
public class DefaultDBConfig {
    @Autowired
    DefaultGlobalPropertyConfig defaultGlobalPropertyConfig;

    @Bean
    @ConfigurationProperties(prefix = "dev.spring.datasource.hikari") //다음의 prefix로 시작하는 설정을 이용해서 hikariCP의 설정 파일을 만듦
    public HikariConfig hikariConfig() {
        return new HikariConfig();
    }

    @Bean
    @Primary
    public DataSource customDataSource() { // 위에서 만든 설정 파일을 이용해서 디비와 연결하는 데이터 소스를 생성
        return DataSourceBuilder
                .create()
                .url(defaultGlobalPropertyConfig.getUrl())
                .driverClassName(defaultGlobalPropertyConfig.getDriverClassName())
                .username(defaultGlobalPropertyConfig.getUsername())
                .password(defaultGlobalPropertyConfig.getPassword())
                .build();
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception { //DataSource를 참조하여 MyBatis와 Mysql 서버를 연동시켜준다.
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(customDataSource());
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        factoryBean.setMapperLocations(resolver.getResources("classpath:/mapper/**/*Mapper.xml"));
        //Mapper 파일 위치를 설정. [classpath]: resource 폴더 의미, [/mapper/**/]: mapp 폴더 밑의 모든 폴더를 의미, [*Mapper.xml]: 이름이 Mapper로 끝나고 확장자가 xml인 모든 파일을 의미

        factoryBean.setTypeAliasesPackage("com.naturemobility.seoul.domain");
        factoryBean.setConfiguration(mybatisConfig()); //Mybatis의 설정파일의 위치를 참조
        return factoryBean.getObject();
    }

    @Bean
    @ConfigurationProperties(prefix = "dev.mybatis.configuration")
    public org.apache.ibatis.session.Configuration mybatisConfig() {
        return new org.apache.ibatis.session.Configuration();
    }

    @Bean
    public SqlSessionTemplate sqlSession() throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory());
    }

    @Bean
    public PlatformTransactionManager transactionManager(){
        return new DataSourceTransactionManager(customDataSource());
    }
}

dev, prod에서 사용하는 DB설정 

@Profile("!default")
@Configuration
@EnableTransactionManagement
public class DBConfig {
    @Autowired
    GlobalPropertyConfig globalPropertyConfig;

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.hikari") //다음의 prefix로 시작하는 설정을 이용해서 hikariCP의 설정 파일을 만듦
    public HikariConfig hikariConfig() {
        return new HikariConfig();
    }

    @Bean
    @Primary
    public DataSource customDataSource() { // 위에서 만든 설정 파일을 이용해서 디비와 연결하는 데이터 소스를 생성
        return DataSourceBuilder
                .create()
                .url(globalPropertyConfig.getUrl())
                .driverClassName(globalPropertyConfig.getDriverClassName())
                .username(globalPropertyConfig.getUsername())
                .password(globalPropertyConfig.getPassword())
                .build();
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception { //DataSource를 참조하여 MyBatis와 Mysql 서버를 연동시켜준다.
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(customDataSource());
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        factoryBean.setMapperLocations(resolver.getResources("classpath:/mapper/**/*Mapper.xml"));
        //Mapper 파일 위치를 설정. [classpath]: resource 폴더 의미, [/mapper/**/]: mapp 폴더 밑의 모든 폴더를 의미, [*Mapper.xml]: 이름이 Mapper로 끝나고 확장자가 xml인 모든 파일을 의미

        factoryBean.setTypeAliasesPackage("com.naturemobility.seoul.domain");
        factoryBean.setConfiguration(mybatisConfig()); //Mybatis의 설정파일의 위치를 참조
        return factoryBean.getObject();
    }

    @Bean
    @ConfigurationProperties(prefix = "mybatis.configuration")
    public org.apache.ibatis.session.Configuration mybatisConfig() {
        return new org.apache.ibatis.session.Configuration();
    }

    @Bean
    public SqlSessionTemplate sqlSession() throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory());
    }

    @Bean
    public PlatformTransactionManager transactionManager(){
        return new DataSourceTransactionManager(customDataSource());
    }
}

 

문제점

앞서 잠깐 언급했듯이 설정 정보를 private 저장소에 올렸음에도 http://localhost:8082/{어플리케이션 이름}/{환경명} 으로 get 요청을 보내면 설정 정보가 그대로 노출되게 된다. 

따라서 이러한 문제점을 해결하기 위해 spring clould config에서는 암호화 기능을 제공한다. 

설정 정보를 암호화하는 방법은 다음에 소개하도록 한다. 

 

후기 

지난 포스팅에서 설정 파일을 2개로 나누는 경우 생기는 문제(1. 설정 방법이 다소 복잡함, 2. 설정 정보의 변경이 어렵고 배포 시 파일 동기화가 불편함)가 있어서 이와 같은 방법을 적용하게 되었는 데, 적어도 설정 정보의 변경 및 동기화가 어렵다는 문제를 해결할 수 있었다.

다만, 개발자마다 다른 설정 정보를 사용할 수 있도록 설계하면서 설정 방법이 더 복잡하게 된 것같다...

만약에 다음에 다른 프로젝트를 하게 된다면 spring cloud config만 이용하여 설정 방법을 간단하게 구성하게 될 것같다. 

그럼에도 지난번에 적용에 실패했던 방법을 이번에 시도하여 설공했다는 것이 개인적으로 의미가 있다. 

 

 

참고 블로그

Spring Cloud Config

https://wonit.tistory.com/502

 

[Spring Cloud Config] Application의 설정 정보 (application.yml) 를 중앙에서 관리하기 (by native repository)

해당 글은 Spring Cloud Netflix Eureka 와 Spring Cloud Gateway 의 Built-in Route로 Predicates와 Filter 조작하기)에 의존하는 글입니다. 실습 환경을 따라하시려면 Eureka와 Gateway 글에 나온 실습을 따라하..

wonit.tistory.com

https://madplay.github.io/post/introduction-to-spring-cloud-config

 

Spring Cloud Config: 소개와 예제

스프링 설정이 바뀌었을 때 빌드, 배포없이 갱신하려면 어떻게 해야할까? 스프링의 설정 파일들을 어떻게 외부로 분리시킬 수 있을까?

madplay.github.io

https://pjh3749.tistory.com/276

 

Spring Cloud Config를 활용하여 설정값(properties), 비밀번호 숨겨서 배포하기

Spring Cloud Config Server 구축하기 Spring Cloud Config 를 사용하여 비밀번호나 민감한 key들을 숨겨서 관리하는 방법을 알아보겠습니다. 전체적인 구상도는 다음과 같습니다. 깃허브 private 저장소에 yml

pjh3749.tistory.com

 

'프로젝트 > 스크린 골프장' 카테고리의 다른 글

전시회 후기  (0) 2022.06.16
[Spring Boot] 설정 파일의 민감한 정보 숨기기 1  (0) 2022.01.15
DB 및 전체 프로젝트 구조  (0) 2022.01.09
UI 프레임워크  (0) 2022.01.09
경쟁 앱 분석  (0) 2022.01.09

프로젝트 기술 스택 및 버전

spring boot 2.5.4

gradle 7.1.1

mybatis 

 

프로젝트 초기 구조

DB password, id, jwt 토큰 암호화 키 등 설정파일의 민감한 정보는 당연하지만 github에 공개적으로 올리면 악용될 여지가 있어, 이러한 민감한 정보는 숨기는 것이 중요하다. 

이러한 민감한 정보를 숨기는 방법에는 여러가지가 있지만, 이번에 적용했던 방법은 설정파일을 2개로 나누는 것이다.

 

처음에 이용했던 방식은 다음과 같다. 

  1. 설정파일을 2개로 분리한다.
  2. 민감한 정보가 들어 있는 설정 파일의 경우 gitignore를 통해서 git에 올리지 않는다. 
  3. @PropertySource를 통해 2개의 설정 파일을 읽는다. 

설정 파일 분리

application.yml : 공통 설정

이 프로젝트는 mybatis를 이용하고 있어, 이에 대한 설정을 추가했다. 

(참고로 현재 프로젝트에서는 mybatis외에도 logback에 대한 설정도 되어 있지만 생략했다)

# MySQL
spring:
  datasource:
    hikari:
      connection-test-query : SELECT NOW() FROM dual
      
# mybatis
mybatis:
  configuration:
    cache-enabled : false
    jdbc-type-for-null : NULL
    map-underscore-to-camel-case : true

config.yml : DB 설정 정보

DB 연결 정보, id, password 등 민감한 정보를 담는다.

그리고 여기서 주의할 점이 username으로 적으면 여기서 설정된 계정명이 아니라 컴퓨터의 유저 이름을 읽게 된다. 따라서 username이 아니라 user-name 등으로 바꾸는 것을 권장한다. 

 

내가 의도했던 것은 username과 password를 통해 팀원과 같은 계정을 사용하기를 원했는 데,

이와 같은 문제떄문에 팀원분이 DB를 연결하는 데 애를 먹으셨다. 

이 글을 읽는 다른 분들은 꼭  username이 아니라 다른 이름으로 설정하시길 바란다. 

# MySQL
spring:
  datasource:
    hikari:
      driver-class-name : com.mysql.cj.jdbc.Driver
      jdbc-url: {url}
      user-name : {계정명}
      password : {비밀번호}

 

설정파일 읽기

@Configuration 어노테이션을 통해 Bean을 생성하고, 

@PropertySources 를 통해 설정파일을 읽는다. 

classpath는 resources 폴더까지를 의미하므로 그 아래부터의 설정 파일의 경로를 적으면 Spring에서 설정파일을 읽을 수 있다. 

그리고 @Value를 통해 값을 주입한다. 

@Configuration
@PropertySources({
        @PropertySource( value = "classpath:/properties/config.yaml", ignoreResourceNotFound = true )//db 설정 파일 경로
//        @PropertySource( value = "file:${user.home}/env/dev-config.properties", ignoreResourceNotFound = true) // 배포시 배포 환경의 디렉토리 주소
})

@Getter
public class DefaultGlobalPropertyConfig {
    @Value("${driver-class-name}")
    private String driverClassName;

    @Value("${jdbc-url}")
    private String url;

    @Value("${user-name}")
    private String username;

    @Value("${password}")
    private String password;
}

 

설정정보 이용하기

@Configuration
@EnableTransactionManagement
public class DefaultDBConfig {
    //설정파일을 읽은 클래스를 생성한다.
    @Autowired
    DefaultGlobalPropertyConfig defaultGlobalPropertyConfig;

    @Bean
    @ConfigurationProperties(prefix = "dev.spring.datasource.hikari") //다음의 prefix로 시작하는 설정을 이용해서 hikariCP의 설정 파일을 만듦
    public HikariConfig hikariConfig() {
        return new HikariConfig();
    }

    @Bean
    @Primary
    public DataSource customDataSource() { // 위에서 만든 설정 파일을 이용해서 디비와 연결하는 데이터 소스를 생성
        return DataSourceBuilder
                .create()
                .url(defaultGlobalPropertyConfig.getUrl())
                .driverClassName(defaultGlobalPropertyConfig.getDriverClassName())
                .username(defaultGlobalPropertyConfig.getUsername())
                .password(defaultGlobalPropertyConfig.getPassword())
                .build();
    }

	// 커스텀한 DataSource를 이용해서 MyBatis와 DataSource 연결
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception { //DataSource를 참조하여 MyBatis와 Mysql 서버를 연동시켜준다.
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(customDataSource());
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        factoryBean.setMapperLocations(resolver.getResources("classpath:/mapper/**/*Mapper.xml"));
        //Mapper 파일 위치를 설정. [classpath]: resource 폴더 의미, [/mapper/**/]: mapp 폴더 밑의 모든 폴더를 의미, [*Mapper.xml]: 이름이 Mapper로 끝나고 확장자가 xml인 모든 파일을 의미

        factoryBean.setTypeAliasesPackage("com.naturemobility.seoul.domain");
        factoryBean.setConfiguration(mybatisConfig()); //Mybatis의 설정파일의 위치를 참조
        return factoryBean.getObject();
    }

    @Bean
    @ConfigurationProperties(prefix = "dev.mybatis.configuration")
    public org.apache.ibatis.session.Configuration mybatisConfig() {
        return new org.apache.ibatis.session.Configuration();
    }

    @Bean
    public SqlSessionTemplate sqlSession() throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory());
    }

    @Bean
    public PlatformTransactionManager transactionManager(){
        return new DataSourceTransactionManager(customDataSource());
    }
}

 

문제점

그런데 이와 같이 설정을 했는 데 발생했던 문제는 다음과 같다. 

 

1. 다른 팀원이 DB 설정 정보를 작성하기 어려움 

설정파일을 하나 만들고 임의의 폴더 위치에 둔 다음, @PropertySource에 폴더의 주소를 적기만 하면되기 때문에 설정하기 쉬울 것이라고 생각했었는데, 나름 쉽게 설명드렸음에도 다른 팀원 분이 DB 설정을 하기 힘들어 하셨다... 

git ignore로 인해 파일이 공유되지 않다보니 상대방 입장에서는 이해하기 힘들다는 것을 고려하지 못했다. 

이는 아예 설정이 완료된 프로젝트 전체(config.yml 포함)을 따로 전달해드려서 해결하긴 했지만, 처음 의도대로 username이 개인 PC의 username으로 설정되는 바람에 한 번 더 어려움을 겪어야 했다. 

이 문제도 root 계정에서 개인 PC의 username의 계정을 하나 만드는 것으로 해결했었다. 

 

2. 배포시 설정파일 관리의 어려움

API를 서버에 올릴 때, jenkins와 github web hook를 이용해서 배포를 했었다. 

그런데 git ignore로 인해 설정파일이 빠지게 되면서 빌드 시에 에러가 발생했다. 

이는 약간의 꼼수로 서버에 동일한 설정파일을 만들어두고, jenkins에서 빌드 시에 복사해서 사용하도록 했었다. 

그러나 이렇게 관리를 하면 매번 새로운 설정 정보가 생길 경우, 직접 server에 있는 파일을 수정해야해서 매우 번거로웠다. 

 

이와 같은 문제를 해결하기 위해 spring cloud config 를 적용하게 되었다. 

 

프로젝트 깃허브

https://github.com/nh0317/KW-Whole_In_One-Backend

 

GitHub - nh0317/KW-Whole_In_One-Backend

Contribute to nh0317/KW-Whole_In_One-Backend development by creating an account on GitHub.

github.com

 

참고했던 블로그

https://4urdev.tistory.com/48

 

Spring Boot - (6) 민감 정보 숨기기(DB id, password, etc.)

GitHub 소스 위치 : https://github.com/Simplify-study/SpringBootSample.git 여기까지 개발하고 나서 GitHub에 소스 코드를 공유하려고 보니, Database에 대한 접속 정보가 그대로 노출되어 있습니다. applicat..

4urdev.tistory.com

DB

  프로젝트의 전체 DB의 모습이다. 지금 서버에 올라간 DB는 총 19개이지만 프로젝트를 진행하면서 기능의 변경이 일어났고, 그로 인해 사용하지 않는 DB 3개를 제외하여 16개만 나타냈다. 그러나 프로젝트를 진행하면서 DB가 추가되고 있어 최종적으로는 20개 가까이의 DB 테이블이 생성될 것으로 예상하고 있다. 

 

클라이언트 페이지 설계

전체적인 클라이언트 페이지 간의 관계를 나타낸 것이다. 실제로는 각 페이지에서 더 세부적인 기능이 있으며, 로그인과 회원가입 기능도 제공한다. 

 

사장님 페이지 설계 

마찬가지로 사장님 페이지 간의 관계를 나타낸 것이다. 

실제로는 사이드 메뉴를 통해 모든 페이지를 접근할 수 있기 때문에 거의 모두 동등한 깊이를 가진다고 볼 수 있다. 

 

  유즈 케이스 다이어그램이라던가 다양한 방식으로 현재 프로젝트의 구조를 설명할 수 있겠지만, 프로젝트 규모가 꽤 커서 혼자 한 번에 정리하기 어려운 점이 있어 우선은 이정도로 설명하고 각 기능을 구현한 방법을 포스팅하면서 (가능하다면) 자세히 설명할 것이다.

+ Recent posts