06. 스프링시큐리티 설정

스프링시큐리티는 인증/인가 담당하는 모듈 하나에 불과하지만 이것을 적용하기 위해서는 손대야할 것이 많다. 여기서는 웹보안에 대한 것만 간단하게 다룬다.  아래의 소스는 스프링시큐리티를 적용하기 위해 작성한 설정 클래스이다. 


1. SpringSecurityConfig 설정

30번 라인의 @EnableWebSecurity은 웹보안을 활성화 시키는 역하을 하고 31번 라인의 WebSecurityConfigurerAdapter은  웹보안을 위한 메소드를 확장시키고 구현을 재정의할 수 있도록 한다.
44번 라인의 configure(WebSecurity web)은 스프링시큐리티의 보안 설정에 적용받지 않을 URL을 등록한다. 주로 이미지 파일이나, CSS, JavaScript 라이브러리가 대상이다. 
49번 라인의 configure(HttpSecurity http)에서는 본격적인 보안 사항을 설정한다. http.formLogin() 에서는 로그인과 관련된 사항을 설정한다. loginPage("/login_form.html").permitAll()은 로그인 페이지가 어떤 URL 인지 지정하고, 로그인을 처리할 loginProcessingUrl("/auth") URL이 무엇인지 지정한다. 본인은 /login_f
orm.html URL을 처리할 컨트롤러를 controller.common.CommonController에 설정했다. usernameParameter("loginid").passwordParameter("passwd")는 로그인에 사용할 파라메터를 지정하는 부분인다. 마지막으로 successHandler()와 failureHandler()는 성공시 처리할 모듈과 실패시 처리할 모듈을 설정했다.
http.logout()에서는 로그아웃과 관련된 사항을 설정한다. logoutRequestMatcher()는 어떤 URL을 입력했을 경우 로그아웃 처리를 수행할 것인지를 지정하는 부분이며, logoutSuccessHandler()는 로그아웃시 처리할 모듈을 설정하는 부분이다. clearAuthentication(true).invalidateHttpSession(true)는 로그 아웃시 인증정보를 지우하고 세션을 무효화 시킨다는 설정이다.
http.exceptionHandling()은 예외사항을 설정한다. 여기서는 권한이 없는 URL에 접근했을 때 동작을 설정하는 부분으로 accessDeniedHandler() 통해 처리할 모듈을 설정했다.
http.authorizeRequests()는 URL별 권한 사항을 설정한다. 이 부분은 userService.findAllComMenuMngtAndAuthGrpMngt() 통해 메뉴정보와 권한 그룹정보를 조회한 후 URL별 권한 정보를 만들어 설정한다. 스프링시큐리티는 ROLE_PREFIX가 'ROLE_'로 설정되어 있는데 이것을 그대로 사용하기 위해 권한 그룹 정보의 시퀀스 앞에 'ROLE_G'라는 문자열을 붙여 설정하였다.
http.sessionManagement() 세션 정책을 설정한다. maximumSessions()에 1을 넣어 같은 아이디로 1명만 접속하도록 설정하였다. maxSessionsPreventsLogin(false)는 기존에 접속한 사용자가 있을 경우 기존 사용자의 접속을 끊고, 나중에 접속한 사용자가 접속되도록 한다. expiredSessionStrategy()을 통해 세션이 종료되었을 경우의 처리를 할 수 있도록 하였다.

82번 라인의 configure(AuthenticationManagerBuilder auth) 메소드는 로그인 처리를 위한 구성을 수행한다. 자세한 내용은 아래서 설명할 것이다.




2. configure(AuthenticationManagerBuilder auth)

configure(AuthenticationManagerBuilder auth)에서는 인증 처리를 위한 AuthenticationProvider를 구현한 구현체와 UserDetailsService를 구현한 구현체를 조합한다. AuthenticationProvider의 구현체의 주요 역할은 사용자 정보를 조회하고, 로그인시 입력 받은 패스워드를 확인하고, 사용자의 권한을 확인하여 인증토큰을 발행하는 역할을 한다. UserDetailsService의 구현체는 사용자 정보를 조회하는 역할을 하며, UserDetails 구현체를 반환 한다.


여기서는 AuthenticationProvider의 구현체로 SecurityAuthenticationProvider를 만들어 사용한다. 아래는 SecurityAuthenticationProvider의 소스이다. 25번 라인과 같이 AuthenticationProvider를 implements 하면서 시작한다. 44번 라인에서는 사용자 정보를 조회한다. 여기서는 UserDetails 구현체를 받는데 본인 나름대로 편리하다고 생각되는 방향으로 수정해서 사용했다. 46번 라인은 패스워드를 비교하는 부분으로 현재의 passwordEncoder는 사실상 패스워드를 암호화 하거나 하지는 않는다. 이것을 암호화 모듈로 바꿔주려면 SpringSecurityConfig.java의 90번 라인의 NoOpPasswordEncoder.getInstance() 수정하면 된다. 51번 라인은 본인이 추가한 부분으로 사용자에게 지정된 IP에서 접속했는가를 확인하는 부분이다. 사용가능 IP 목록이 없으면 이 부분은 그냥 넘어가도록 했다. 사용가능 IP 목록은 관리자와 같은 특수한 사용자 위한 것으로 특정한 PC에서만 접속할 권한을 부여할 경우 사용한다. 68번 라인은 사용자가 사용할 수 있는 권한(ROLE)을 부여하는 부분으로 사용자 정보에서 가져왔다. 마지막으로 80번 라인에서는 스프링에서 사용할 수 있도록 사용자 정보와 권한 정보 등으로 Authentication 구현체를 반한다.



사용자 정보는 UserDetailsService를 구현한 SecurityUserDetailsService로 하였다. UserDetailsService의 필수 조건은 사용자의 권한 정보를 포함한 사용자 정보이다. 하지만 SecurityUserDetailsService는 메뉴 정보까지 포함하여 받을 수 있도록 수정했다.


20번 라인21번 라인은 UserDetailsService 기능을 수행할 수 있도록 @Service 추가와 UserDetailsService 인터페이스를 추가하였다. 26번 라인의 loadUserByUserName() 메소드는 AuthenticationProvider 구현체에서 호출하는 부분으로 반드시 구현되어야 하는 부분이다. 이곳에서 사용자 정보 조회와, 사용자의 권한 그룹 조회, 사용자에게 할당된 메뉴 정보를 조회한다. 이 부분은 28번 라인, 35번 라인, 38번 라인에서 구현되어 있으며, 특히 38번 라인의 사용자 메뉴 목록 조회 부분에서는 내부에서 메소드(56번 라인)를 구현하여 각 메뉴의 부모와 자식 관계를 연결하였다. 

이렇게 한 이유는 나중에 화면을 구현할 때 각 화면별 화면 정보 조회와 화면 경로 표현을 위해 미리 만들어 둔 것이다.

40번 라인은 SecurityUser Object를 생성하는 부분으로 우리의 필요 용도에 맞게 UserDetails를 수정하여 구현 하였다.


마지막은 UserDetails를 구현한 SecurityUser다. SecurityUsers는 Spring Security에 사용자 정보를 제공하는 용도와 더불어 개인화된 화면을 제공하기 위한 부가 정보로 구성했다.

SecurityUser는 UserDetails의 구현체이므로 29라인 처럼 UserDetails을 추가해야 하며, Serializable도 추가해야 한다. 41번 라인은 생성자로써 사용자 정보과 권한 정보, 메뉴 목록 등을 받아 인스턴스 내에 저장하는 역할을 한다. 특히 50번 라인에서는 최상위 메뉴를 topMenuMaps에서 저장하며, 54번 라인에서는 사용자에게 할당된 메뉴를 authMenuUrlMap라는 인스턴스 변수에 url + menu 정보로 저장하여 둔다. 71번 라인84번 라인은 메뉴 목록을 출력하는 용도와 화면 정보를 표시하는 용도로 사용된다. 자세한 것은 interceptor(Logging 파트)를 다룰 때 설명할 것이다. 89번 라인과 103번 라인, 112번 라인은 UserDetails 인터페이스가 요구하는 항목으로 각각 사용자의 권한 정보와 패스워드, 로그인 아이디를 반환한다.

117번 라인, 126번 라인, 131번 라인, 149번 라인은 UserDetails 인터페이스가 요구하는 구현 항목으로 각각 계정의 만료 여부, (일정 수의 로그인 처리 오류의 경우 등으로)계정 잠김, 패스워드 만료, 유효 계정 여부 등을 묻는 기능으로 원칙적으로는 구현하는 것이 올바르지만, 이런 기능들을 사용하지 않을 true를 반환해도 된다. 





3. 인증과 처리에 대한 각종 핸들러

SpringSecurityConfig 설정에는 총 6종의 핸들러가 선언되어 있다. 이중에 SecurityAuthenticationSuccessHandler만 설명하고 나머지는 Logging 파트에서 설명할 것이다. SecurityAuthenticationSuccessHandler의 역할은 로그인 처리가 완료되었을 경우 로그인한 사용자가 보게될 첫번째 화면으로 안내하는 것이다.

67번 라인의 onAuthenticationSuccess() 메소드에서 사용자에게 보여질 첫번째 화면을 지정해 준다. 그러나 우선적으로 처리해 주어야할 일이 있다. 70번 라인에 호출된 clearAuthenticationAttributes() 메소드는 로그인 시 발생했던 세션에 저장된 오류를 지워주는 역할을 수행한다. 그리고 72번 라인의 decideRedirectStrategy() 메소드를 통해 어떤 화면으로 이동할 것인지를 지정한다. 판단 기준은 targetUrlParameter 값이 있을 경우 그것을 1순위로 하여  리다이렉션하며, 1순위 URL이 없을 경우 Spring Security가 세션에 저장한 URL을 2순위로 한다. 2순위 URL이 없을 경우 Request의 REFERER을 3순위로 하여 그 REFERER URL로 이동하며,  3순위도 없을 경우 Default URL을 4순위로 한다. (SecurityAuthenticationSuccessHandler의 소스 내용은 [http://zgundam.tistory.com/52]를 차용하였다. 보다 상세한 내용도 이곳에서 확인할 수 있다.)



지금까지의 코드로는 결과를 확인하기는 좀 어렵다. 다음 포스트에서 Apache Tiles의 사용과 더불어 결과를 확인해 보도록 하겠다.


demo.zip

 

05. flyway 설정과 DAO 테스트

본인은 Dao에 대한 테스트 코드를 만드는 것에 부정적이다. Dao 레벨의 테스트는 의미를 찾기가 어렵기 때문이다. 어떤 기능을 한다는 것은 Dao 레벨에서 만들어지는 단순한 기능이 아니라 여러가지 단순한 기능들을 엮어 하나의 의미있는 동작을 만들어야 하기 때문이다. 따라서 테스트의 단위는 의미있는 기능이 만들어지는 서비스 레벨에서 부터 이루어져야 하지 않을까? 더구나 Spring Data가 만들어내는 자동화 코드를 테스트할 의미가 있을까 싶다. 그러나 여기서는 Dao를 테스트 하므로써 Spring Data가 만들어내는 코드와 QueryDSL이 실제 동작하는지 살펴보고, flyway가 어떻게 개발환경 구축에 도움을 주는지 살펴볼 것이다.



1. flyway 설정과 테스트 데이터 입력


flyway는 기초 데이터를 넣어주는 유틸리티라고 생각하면 된다. 프로그램의 구동이 시작될 때 주어진 스크립트 파일을 하나씩 순서대로 실행해 준다. 여기서는 첫번째 파일에 DDL 스크립트를 넣고, 두번째 파일에는 기초데이터를 넣었다. 이 기능을 위해 대상 DB에 스크립트의 버전을 관리하는 테이블을 flyway가 스스로 만들어 둔다. 아래는 실행 스크립트를 만드는 방법이다.


flyway에 실핼 시킬 스크립트는 일반적으로 /resources/db/migration 아래 넣는 것이 기본 설정 사항이지만, 여기서는 따로 설정할 수 있도록 filesystem에 지정하였다. 50번 라인이 그것이다.

스크립트는 DDL 부분과 기초 데이터 insert 부분을 각각 따로 만들었다. 파일명은 규칙에 따라 V1__demo-initial-schema.sql 과 V2__demo-initial-data.sql으로 나눴다. ddl 스크립트는 hibernate 구동 시 생성되는 스크립트를 로그에서 복사하여 붙였으며, insert 문장은 ddl 문장에 맞춰서 작성 하였다.

V1__demo-initial-schema.sql 

V2__demo-initial-data.sql



2.DAO 테스트

아래 테스트하는 코드는 SpringSecurity에 사용할 메소드들이다. 더 추가되거나 변경될 수 있지만, 일단 생각나는 대로 작성했다. 테스트 코드를 보면 매우 엉성해 보일지도 모르겠다. 그러나 테스트 코드는 이정도 수준으로 작성하는 것이 옳다는 생각을 한다. 더 정교하고 완벽한 테스트 코드는 많은 시간과 노력이 들어가기 때문에 잘못하면 배보다 배꼽이 더 커지는 현상이 있다. 테스트에서 해야할 것은 원하는 동작을 오류 없이 수행 가능한 것인지를 확인하는 것 뿐이다.


테스트 코드를 작성할 때 주의할 점이 있다. 테스트 메소드는 다른 메소드와 연동되어 수행되면 안되고, 반드시 그 자체로 완결되어야 한다. 그렇지 않으면 작업이 진행되면서 계속 변화하고 추가되면 기능들로 인하여 빈번히 테스트를 수정해야 하는 상황이 발생한다. 그런 상황들은 테스트 코드를 만드는 것을 지치게 한다.


24번/25번 라인은 SpringBoot 환경에서 테스트할 환경을 조성해 주는 어노테이션이다. 26번 라인은 Lombok에서 제공하는 어노테이션으로 간편하게 로그를 찍을 수 있게 도와준다. 27번 라인의 @Transactional 어노테이션은 테스트를 수행하면서 더러워진(?) 데이터를 처음 상태로 만들어준다. 즉, 수행된 쿼리를 롤백 시켜준다. @Transactional 어노테이션을 지우면 테스트로 인해 변경된 데이터가 그대로 남게 된다. 28번 라인의 @FixMethodOrder 어노테이션은 테스트 메소드를 이름 순서대로 실행 시켜준다. 이것이 없을 경우는 어떤 테스트가 먼저 실행될 것인지 알 수 없다. 37번 라인의 @Test 어노테이션은 테스트 대상 메소드라는 것을 알린다. 

38번 라인에 선언된 메소드를 주의깊게 봐줬으면 한다. 앞 부분은 테스트 번호를 붙인 것이지만 뒤에는 한글로 테스트 명을 입력하였다. 이렇게 한글로 메소드명을 입력하면 따로 주석을 달지 않아도 직관적으로 무슨 테스트인지 확인할 수 있기 때문에 편리하다. 

40번과 65번 라인은 수행된 결과를 확인하는 문장이다. 이런 Assert 메소드는 여러가지 종류가 있지만, 본인을 이 두 메소드 외에는 별로 사용하지 않는다. 이유는 앞에서도 설명 했듯이 테스트 코드에 많은 시간과 노력을 들이고 싶지 않기 때문이다. 처한 여건이나 상황에 따라 다르겠지만, 적당한 테스트 수준을 유지하는 것이 테스트 코드의 진짜 핵심이지 않을까 싶다.



첨부파일은 본 페이지를 만들기 위한 소스다.

demo.zip



04. JPA를 이용한 DAO 개발

1. DAO 와 JPA

Data Access Object를 줄여서 Dao라고 부른다. Dao는 말 그대로 데이터베이스에 접근해서 필요한 작업을 하는 Object를 뜻하며 데이터베이스를 사용하는 프로젝트의 많은 비중이 Dao를 만드는 것에 치중되어 있다. 


Spring Data JPA는 매우 적은 코드로 Dao를 구현할 수 있도록 해준다. 인터페이스를 만드는 것 만으로 Entity 클래스에 대한 Insert,Update, Delete, Select 를 실행할 수 있게 해준다. 뿐만 아니라 인터페이스에 메소드를 선언하는 것 만으로 라이트한 쿼리를 수행하는 코드를 만드는 것과 동등한 작업를 수행한다.


Spring Data JPA가 만들 수 있는 코드는 매우 가볍고 쉬운 쿼리를 대체하는 것이라 무시하는 개발자가 더러 있지만, CRUD를 포함한 단순 쿼리가 차지하는 비율은 대부분의 프로젝트에서 40~50%를 차지한다. 더구나 이러한 단순 쿼리는 필수적인 작업이며, 모아 놓고 보면 그 양이 어마어마 하다. 


Spring Data JPA는 데이터베이스 작업에 큰 도움이 되지만 아쉬운 부분있다. 앞에서도 언급했듯이 비교적 간단한 쿼리작업을 대체할 수 있을 뿐이다. 복잡도가 조금이라도 높아지면 사용하기가 매우 어려워 진다. 이럴 때는 QueryDSL을 사용해서 해결해야 한다. QueryDSL은 Jpa Entity를 바탕으로 작업을 진행하며, JPQL이나 Criteria 같은 것을 사용하지 않고 다이나믹 쿼리를 수행할 수 있도록 지원한다. 보통의 프로젝트에서 QueryDSL을 이용하여 작업하는 쿼리는 전체 프로젝트에 20~30%정도를 차지한다.


QueryDSL도 한계가 있기는 마찬가지다. Entity를 Object로 취급한다는 근본적인 문제가 있어 FROM절의 서브쿼리나 Union 계열의 쿼리를 수행할 수 없는 한계가 있다. 또한 통계성 쿼리가 수행하는 작업은 포기하는 것이 답일 수도 있다. 그럴 경우는 Native 쿼리를 사용하는 것이 좋다.


전체적으로 보면 하나의 기술로 완벽한 작업을 수행할 수 없는 것이 JPA의 한계이다. JPA와 QueryDSL를 사용해서 구현 할 수 있는 Dao의 비율은 70~80% 정도로 보는 것이 적당하다. 그러나 이정도 만으로도 Dao 작업에 소요되는 작업의 양은 절반 이하로 줄어든다. 이유는 코딩의 절대적인 양이 절반이하고 줄어 들고, 모델의 변화에 민감하게 작용하기 때문에 프로그램 수정과 디버깅에 소요되는 작업 시간이 급격히 줄어들기 때문이다. 여기서 예를 든 것은 일반적인 OLTP환경에 대한 이야기다. 프로젝트의 성격이 OLTP 성능에 치중되지 않은 배치성 프로젝트라면 JPA는 지양해야할 기술일 수도 있다.



2. JpaRepository

JPA Repository 인터페이스를 만드는 것은 매우 간단하다. 본인은 Entity 이름 뒤에 Repository라는 이름의 인터페이스를 만든다. 그리고 extends JpaRepository를 붙이는 것을 끝낸다.


아래는 ComUserMngt에 대한 Repository이다. 인터페이스로 선언 했으며, 7번 라인에 extends로 JpaRepository를 추가 했다. 그리고 여기서 사용할 Entity가 ComUserMngt 클래스임을 선언하고, PK 타입이 Long으로 선언되어 있다. 즉, ComUserMngtRepository 인터페이스는 어떤 테이블을 관리할 수 있고, Primary Key는 어떤 유형인지를 알려주는 것이다.


9번 라인에는 findByLoginId라는 메소드가 선언되어 있다. ComUserMngt는 공통 사용자 관리 테이블로 LoginId라는 컬럼으로 사용자의 로그인 정보를 확인하기 때문에 사용자가 로그인 되었을 경우 사용자 정보는 찾게 하기 위해 만드는 메소드이다. 그리고, 이것으로 데이터 베이스에 접근하여 사용자 정보는 찾는 작업은 끝났다. 

뭔가 아쉬울 수도 있지만, 쿼리라던가 구현체를 만들거나 하는 일은 없다.



또 다른 ComAuthGrpMenuMapRepository를 보자. ComAuthGrpMenuMap은 복합키로 이루어져 있다. 그래서 9번 라인의 뒷부분이 ComUserMngtRepository와는 다르다. 즉, 여기서 사용할 Entity가 ComAuthGrpMenuMap 클래스임을 알리지만,  Primary Key 유형에는 IdClass가 들어간다.




아래는 ComUserMngtRepository의 메소드 목록이다. 우리가가 작성한 findByLoginId() 이외에도 많은 메소드들이 기본으로 제공되고 있다. 물론 아래의 메소드 목록은 기본적인 CRUD에 대한 것이다. 좀더 복잡한 작업을 하는 메소드를 원한다면 아래의 Supported keywords inside method names 표를 참고하면 될 것이다.


Supported keywords inside method names

 Keyword

 Sample

 JPQL snippet

 And

 findByLastnameAndFirstname

 … where x.lastname = ?1 and x.firstname = ?2

 Or

 findByLastnameOrFirstname

 … where x.lastname = ?1 or x.firstname = ?2

 Is,Equals

 findByFirstname,

 findByFirstnameIs,

 findByFirstnameEquals

 … where x.firstname = 1?

 Between

 findByStartDateBetween

 … where x.startDate between 1? and ?2

 LessThan

 findByAgeLessThan

 … where x.age < ?1

 LessThanEqual

 findByAgeLessThanEqual

 … where x.age <= ?1

 GreaterThan

 findByAgeGreaterThan

 … where x.age > ?1

 GreaterThanEqual

 findByAgeGreaterThanEqual

 … where x.age >= ?1

 After

 findByStartDateAfter

 … where x.startDate > ?1

 Before

 findByStartDateBefore

 … where x.startDate < ?1

 IsNull

 findByAgeIsNull

 … where x.age is null

 IsNotNull,NotNull

 findByAge(Is)NotNull

 … where x.age not null

 Like

 findByFirstnameLike

 … where x.firstname like ?1

 NotLike

 findByFirstnameNotLike

 … where x.firstname not like ?1

 StartingWith

 findByFirstnameStartingWith

 … where x.firstname like ?1 

      (parameter bound with appended %)

 EndingWith

 findByFirstnameEndingWith

 … where x.firstname like ?1 

      (parameter bound with prepended %)

 Containing

 findByFirstnameContaining

 … where x.firstname like ?1 

      (parameter bound wrapped in %)

 OrderBy

 findByAgeOrderByLastnameDesc

 … where x.age = ?1 order by x.lastname desc

 Not

 findByLastnameNot

 … where x.lastname <> ?1

 In

 findByAgeIn(Collection<Age> ages)

 … where x.age in ?1

 NotIn

 findByAgeNotIn(Collection<Age> age)

 … where x.age not in ?1

 True

 findByActiveTrue()

 … where x.active = true

 False

 findByActiveFalse()

 … where x.active = false

 IgnoreCase

 findByFirstnameIgnoreCase

 … where UPPER(x.firstame) = UPPER(?1)


3. QueryDSL

앞에서도 이야기 했듯이 Spring Data JPA는 한계가 있다. 복잡한 쿼리를 요구하는 작업이 필요할 경우 JPQL이나 Criteria를 사용해야 하지만 만만한 작업이 아니다. 일반 쿼리툴 처림 눈으로 보고 결과를 확인할 수 있는 툴도 없고, SQL과 비슷하지만 뭔가 다른 것이 내 마음대로 되지 않기 때문이다. 이럴 때 QueryDSL은 JPA 본연의 특성을 잃지 않고도 SQL을 사용하는 것과 같은 편안함을 준다.


QueryDSL을 사용하기 위해서는 우선 pom.xml에 추가할 항목이 있다. 아래의 32라인에서 44라인 처럼 QueryDSL 라이브러리를 추가한다. 53라인에서 68라인에 추가한 플러그인은 Q-Object를 생성하기 위한 것으로 QueryDSL은 Jpa Entity 를 보고 Q-Object라는 새로운 코드를 자동으로 생성하기 위해서 추가하는 플러그인이다.


이렇게 해서 끝나면 좋겠지만 [You need to run build with JDK or have tools.jar on the classpath.... ]라는 메시지를 보게 될 수도 있다. 이럴 경우eclipse.ini (Spring Tool Suite의 경우 sts.ini)에 -vm옵션을 추가 하면 된다. 본인은 맨 마지막 줄에 -vm 옵션을 설정했다. JDK 위치는 본인의 환경에 맞게 설정하면 된다. 



이제 QueryDSL을 작성하기 위한 준비가 끝났다. 우선 ComAuthGrpMngtRepositoryQuerydsl을 작성해 보자. 이름을 이렇게 만든 이유는 ComAuthGrpMngtRepository에 덧붙이기 위해서이다. 즉 ComAuthGrpMngtRepository에 사용할 Querydsl이란 뜻이다. 10번 라인은 사용자 관리 오브젝트로 사용자에게 할당된 권한 목록을 조회하는 메소드의 명세이다.


ComAuthGrpMngtRepositoryQuerydsl 작성이 되었으면 ComAuthGrpMngtRepositoryImpl을 작성한다. 15번 라인과 같이 @Repository 어노테이션을 붙여 퍼시스턴트에서 동작하는 클래스임을 알린다. 16번 라인의 클래스 선언부엔 extends를 추가하고 QueryDslRepositorySupport를 상속받아 QueryDSL을 처리할 수 있도록 하며, implements에는 방금 작성한 ComAuthGrpMngtRepositoryQuerydsl을 넣는다, 

findByComUserMngt()메소드 내에  24번라인25번라인이 Q-Object이다. QueryDSL은 이것으로 Object 쿼리를 작성할 수 있도록 한다. 이것은 우리가 작성한 적이 없지만, QueryDsl이 Entity 클래스를 보고 자동으로 만들어 주는 클래스이다. 이것의 존재를 확인하고자 한다면 소스폴더 중 target/generated-sources/java 에서 확인할 수 있다.

27번 라인28번 라인이 쿼리 문장을 만드는 부분이다. 마치 select절이 없는 쿼리처럼 보인다. List<ComAuthGrpMngt>를 반환할 것이기 때문에 굳이 select절을 만들지 않았다. 28번 라인의 where절에는 조건을 입력하여 쿼리를 완성했다. 이것이 QueryDSL로 만드는 가장 간단한 소스 중 하나라고 보면 된다. 


다음은 좀더 복잡한 ComMenuMngtRepositoryQuerydsl과 ComMenuMngtRepositoryImpl을 보자. 11번 라인에 사용자 정보와 그룹 정보를 받아 메뉴 목록을 조회하는 메소드를 선언했다. 13번 라인은 모든 메뉴를 조회하는 메소드를 선언했다. 이 메소드들은 ComAuthGrpMngtRepositoryImpl에 구현된 소스보다 조금 더 복잡할 것이다.


ComAuthGrpMngtRepositoryImpl의 소스의 36번 라인을 보면 BooleanBuilder가 선언되어 있는 것을 볼 수 있다. ComAuthGrpMngtRepositoryImpl에서는 이 부분을 where() 메소드로 처리했는데 이렇게 하면 상황에 따른 조건절 처리가 불가능하다. 이것을 BooleanBuilder로 하면 if-else처리가 가능해 진다. 42/43라인은 if문을 사용하진 않았지만 변수를 받는 부분이다. return 문이 있는 45번 라인에서는 BooleanBuilder로 모은 조건을 where절에 넣어 쿼리를 완성해 준다.

findAllComMenuMngtAndAuthGrpMngt() 메소드에서 볼 부분은 55/56번 라인이다. 이부분은 innerJoin을 하는 부분인데 맨 마지막에 .fetchJoin()이 추가된 것을 볼 수 있다. .fetchJoin()이 하는 역할은 ComMenuMngt목록을 반환할 때 ComAuthGrpMngt와 QComAuthGrpMenuMap를 포함시켜 가져오도록 지시하는 부분이다.

이 부분이 없다면 ComMenuMngt에 선언되어 있는 QComAuthGrpMenuMap를 가져오는 부분이 FetchType.LAZY로 되어 있기 때문에 쿼리를 여러번 수행하게 된다. 즉, 한번의 쿼리로 가져올 수 있는 부분을 나눠서 가져오는 결과가 발생하기 때문에 .fetchJoin()를 추가하므로써 원래 의도대로 한번에 가져오도록 하는 것이다.


이렇게 두개의 QueryDsl소스가 만들어졌다. 코딩이 완료 되었으면 이것을 Repository에 붙여 사용한다. 물론 QueryDsl만 단독으로 사용할 수도 있지만 기존에 만들어진 Repository에 붙여 사용하면 호출 클래스를 하나로 할 수 있기 때문에 약간의 간편함이 있다. 기존 Repository와 합치기 위해서는 아래의 소스 8번 라인처럼 extends에 QueryDsl 인터페이스를 넣으면 된다. QueryDsl의 구현체의 이름이 기존 Repository에 impl을 붙인 형태인 이유는 이렇게 통합하기 위해서 이다.


다음은 위의 소스들을 테스트 하기 위해 flyway를 설정하여 기본 데이터를 넣고, Junit으로 테스트 할 것이다.

03. JPA Entity Class와 패키지 구조


본격적인 개발에 앞서 다음과 같이 패키지 구조를 잡았다.


dao
DAO에 해당하는 클래스를 모아 놓을 것이다. 예를 들어 JpaRepository 나 QueryDSL 관련 클래스는 물론 myBatis Mapper 등이 해당한다.

entity
JPA Entity들이 여기에 해당하며, 본인은 영역을 나누기 위해 com(공통)이라는 패키지 밑에 Entity를 몰아 놓았다. domain이라는 패키지는 enum type의 클래스를 모아두기 위한 것으로 코드성 데이터라고 생각하면 된다. 

security
Spring Security 관련 클래스가 여기 위치 한다.

service
dao가 최소 실행 단위라고 하면, service는 의미 있는 최소 수행 단위라고 해야할 것이다. service에서 제공하는 Method는 dao에서 제공하는 Method를 묶어 의미 있는 서비스를 만들며, 이 서비스는 하나의 트랜잭션으로 묶어 일관성 있는 서비스를 제공해야 한다.

utils : 각종 유틸리티 클래스가 위치할 것이다.




1. ComUserMngt 만들기

6,7번 라인의 @Getter, @Setter는 Lombok 어노테이션을 선언한 것이다. Lombok은 getter/setter를 직접 코딩하지 않아도 생성해주는 도구로 코드를 깔끔하게 유지시켜 준다.


8번 라인의 @ToString 또한 Lombok에서 지원하는 어노테이션으로 멤버 변수 모두를 toString() 메소드에 참여 시켜주는 편리한 어노테이션이다. 그러나 어떤 멤버 변수는 toString() 메소드에 참여시키면 안되는 것도 있다. 특히 JPA의 Entity 클래스와 같은 경우 왜래키 관계를 멤버변수로 표현하는데, toString()으로 풀릴 경우 무한 재귀되는 현상이 있어, 해당 변수는 exclude 항목에 넣어 toString() 메소드에서 제외했다.


9번 라인의 @Entity 어노테이션은 JPA Entity로 참여한 다는 것을 알린다. 테이블에 행당한다고 생각하면 된다.


10번 라인의 @Table은 DB에 테이블로 맵핑되며, 테이블명은 COM_USER_MNGT라는 것을 명시적으론 선언했다.


11번 라인의 @UniqueConstraint은 유니크 인덱스이며, 인덱스 이름은 UK_COM_USER_MNGT_LOGIN_ID이고, 인텍스 컬럼은 LOGIN_ID라는 것을 알린다.


12번 라인의 @SequenceGenerator는 Sequence를 사용한다는 구문으로 17번 라인과 연결된다.


16번 라인의 @ID는 해당 멤버변수가 PK로 참여한다는 것을 알리는 것이며, 17번 라인에서는 해당 PK가 인조키이며, 인조키 사용 방식은 시퀀스 Object를 이용한다는 뜻이다. 이 인조키는 12번에 선언된 SEQGEN_COM_USER_MNGT를 사용한다. JPA에서 인조키는 만드는 방법은 시퀀스를 사용하는 방법 외에도 3가지가 더 있다. 


 인조키 생성 방법

 설명

 적용 DB

 IDENTITY

DB에 의존적이며, 기본키 자동 증가 생성 기능이 있는 DB에서 사용

 MS SQL, MySQL, DB2, PostgresSQL

 SEQUENCE

DB에 의존적이며, DB의 Sequence Object를 사용

 Oracle, H2, PostgreSQL

 TABLE

채번 테이블을 사용해서 사용

 모든 DB

 AUTO

 IDENTITY, SEQUENCE, TABLE 방법 중 하나를 자동 선택

 모든 DB


18번 라인의 @Column 어노테이션은 테이블의 컬럼을 뜻하는 것으로 name속성을 이용해 컬럼명을 명시적으로 지정했다. length속성은 문자 타입의 경우 길이를 지정하는 것으로 LOGIN_ID속성의 길이 제한은 30자이며, nullable=false을 통해 null값이 들어갈 수 없고, unique=true로 지정하므로써 유니크 인덱스를 사용한다는 것을 표시했다. unique 속성은 11번 라인의 @UniqueConstraint 어노테이션과 함께 사용할 수는 없다. 즉, unique=true로 설정하면 11번 라인이 무시되므로 여기서는 삭제하는 것이 마땅하지만, 예제로 넣었다.


36번 라인의 @Column은 아무런 속성이 없는 데 이것에 해당하는멤버 변수가 Date 타입이기 때문에 사용할 수 있는 옵션이 없는 것이다. 대신 37번 라인의 @Temporal(TemporalType.TIMESTAMP)와 같이 처리 함으로써 Date을 값을 저장할 수 있도록 처리 했다.


44번 라인의 @Enumerated(EnumType.STRING)는 enum 타입 클래스를 사용한다는 것이다. @Enumerated는 두가지의 속성을 가지고 있는데, enum 타입의 문자열 값을 사용하던지 멤버 변수의 순번를 사용하던지 할 수 있으며, EnumType.STRING 속성의 순번이 아닌, 문자를 사용한다는 뜻이다. @Enumerated에 들어가 class는 entity.domain 패키지에서 위치시켰으며, 이것은 마치 코드성 데이터와 같다. enum 타입 클래스의 멤버 변수에 해당하는 값만을 넣을 수 있도록 만든다.


54/57번 라인의 @Column의 속성을 보면 updatable=false 속성이 있는데, 이것은 데이터의 생성 시에만 값을 사용하고 수정될 경우는 사용하지 않는 다는 뜻이다. 즉, insert 될 경우만 값이 유효하며, update 문을 사용할 때는 무시된다는 뜻이다. 같은 경우로 61/64번 라인의 경우는 insertable=false 속성을 달고 있으며, 이것은 insert될 때는 무시되고, update될 경우만 값이 입력 된다는 뜻이다.


58번 라인의 @CreationTimestamp의 경우는 입력될 때 creDt 멤버 변수에 값을 넣지 않아도 시간 값을 자동으로 넣어 주는 기능이다. 65번 라인의 @UpdateTimestamp도 같은 역할을 한다. 단, 이것은 생성될 때가 아닌 수정될 때 시간 값을 넣어 준다.





2. ComAuthGrpMngt 만들기

44번 라인의 @OneToMany는 ComAuthGrpMngt객체를 왜래키(Foreign Key) 관계로 바라보는 연관객체가 있다는 뜻이며, 해당 객체는 ComUserAuthGrpMap이라는 것을 알린다. mappedBy="comAuthGrpMngt" 속성의 의미는 ComUserAuthGrpMap에서는 comAuthGrpMngt 멤버변수와 연결되어 있다는 뜻이다.

fetch=FetchType.LAZY속성은 ComAuthGrpMngt에서 특정 값을 조회할 경우 FK관계에 있는 comUserAuthGrpMaps 객체를 자동으로 가져오지 않다는 것을 뜻 한다. 이 값을 FetchType.EAGER 값으로 변경하면, ComAuthGrpMngt를 조회 시 comUserAuthGrpMaps 값을 자동으로 조회되어서 편리할 수도 있다. 하지만 원하지 않는 값을 조회하는 효과를 가질 수 있기 때문에 주의해야 한다. @OneToMany에서는 기본 값이 FetchType.LAZY이지만, 본인은 명시적인 것이 좋아 선언했다.

@OneToMany의 속성 중 cascade = CascadeType.ALL는 연관객체간의 동작을 설명하는 것이다.


 CascadeType 종류

 설명

 CascadeType.RESIST

엔티티를 생성하고, 연관 엔티티를 추가하였을 때 persist() 를 수행하면 연관 엔티티도 함께 persist()가 수행된다.  만약 연관 엔티티가 DB에 등록된 키값을 가지고 있다면 detached entity passed to persist Exception이 발생한다.

 CascadeType.MERGE

트랜잭션이 종료되고 detach 상태에서 연관 엔티티를 추가하거나 변경된 이후에 부모 엔티티가 merge()를 수행하게 되면 변경사항이 적용된다.(연관 엔티티의 추가 및 수정 모두 반영됨)

 CascadeType.REMOVE

삭제 시 연관된 엔티티도 같이 삭제됨

 CascadeType.DETACH

부모 엔티티가 detach()를 수행하게 되면, 연관된 엔티티도 detach() 상태가 되어 변경사항이 반영되지 않는다.

 CascadeType.ALL

모든 Cascade 적용


45번 라인의 @JsonIgnore는 
ComAuthGrpMngt 객체가 json으로 전송될 때 해당 항목은 json으로 변환하지 않도록 한다. 이것이 json으로 풀릴 경우 @ToString과 같이 왜래키 관계에 있는 하위 속성들이 풀리면서 원하지 않는 데이터를 json으로 풀어버릴 수도 있기 때문이다. 




3. ComUserAuthGrpMap 만들기

ComUserAuthGrpMap 클래스는 다른 클래스에 비해 깔끔하다. ComUserAuthGrpMap은 다대다 관계를 유연하게 풀어주는 역할을 하므로, 관계에 의해서만 존재한다. 따라서 ID는 ComUserMngt와 ComAuthGrpMngt를 받는다. 


9번 라인의 @IdClass@ID가 하나가 아니기 때문에 ID를 구성하는 객체를 만들어야 제공해야 한다. 여기서는 ComUserAuthGrpMapId.class라는 클래스를 만들어 제공하며, ComUserAuthGrpMapId.class는 조금 뒤에 설명한다.


13/18번 라인의 @ManyToOne는 @OneToMany와 비슷하지만 다른 의미를 가진다. ComUserAuthGrpMap의 부모에 해당하는 ComUserMngt와 ComAuthGrpMngt에 자식 객체로 묶여 있다는 것을 뜻한다. fetch=FetchType.LAZY 속성은 @OneToMany와 마찬가지로 부모에 해당하는 객체를 자동으로 가져 오지 않는 다는 것을 뜻한다. @ManyToOne의 fetch 타입은 FetchType.EAGER가 기본이 값이다. 그러나 자동 조회하지 못하도록 한 이유는 성능상의 문제가 될 수도 있으며, 원할 경우 자동으로 가져 오는 방법이 있기 때문다.


15/21번 라인의 @JoinColumn은 이것이 일반적인 컬럼이 아니라 왜래키(Foreign Key) 관계에 의해 만들어 진다는 것을 의미한다. 컬럼 이름은 name 속성을 사용하여 부모와 다른 독자적인 컬럼명을 사용할 수 있으나, 속성 타입은 부모의 것을 따르게 된다. 실제 멤버 변수도 부모에 해당하는 ComUserMngt와 ComAuthGrpMngt 객체를 그대로 사용하였다.



9번 라인의 @DataLombok에서 지원하는 어노테이션으로 getter/setter와 hashCode(), equals() 메소드를 자동으로 만들어 준다. @IdClass의 기본 조건 중 상당 부분을 어노테이션 하나로 충족 시켜 준다.

10/11번 라인은 기본 생성자(@NoArgsConstructor)와 모든 멤버 변수를 넣어야 하는 생성자(@AllArgsConstructor)를 만들어 준다.

12번 라이에서 주목해야 하는 부분은 implements Serializable로 @IdClass의 조건중 하나이다.


@IdClass의 조건을 보면 복합키를 가지는 Entity의 경우 빠른 비교와 검색을 위해 hashCode(), equals()를 구현해야 하는 것을 알 수 있다. 단일키 Entity의 경우는 키가 하나이기 때문에 이럴 필요가 없는 것이다.




4. ComUserPwd 만들기

ComUserPwd Entity의 경우 소스 코드 상으로는 특별히 이야기할 만한 것은 없다. 이미 이전 Entity에서 보았던 구성 요소만 있고 새로운 것은 없다. 하지만 ComUserPwd Entity의 경우는 ERD 모델을 생각해 봐야 한다. 


우선, ComUserPwd는 Primary Key는 부모의 키를 상속받지 않는 독자적인 구조이다. 이것을 모델링 이론으로 보면 이상한 구조일 수 밖에 없다. 하위의 자식이 없고, 부모에 종속되는 Entity는 식별관계를 가져야 하기 때문이다. 즉, 부모키와 자신의 키를 합쳐서 복합키로 사용해야 하나 ComUserPwd는 부모의 키는 일반속성으로 낮추고, 자기의 독자적인 키만으로 구성하는 비식별관계를 만든 것이다.


이런 비식별 관계를 만든 이유는 단순히 편리성 때문이다. 복합키를 사용하는 ComUserAuthGrpMap과 같은 구조는 IdClass를 만들어 사용해야 하는 불편함이 존재하기 때문에 아무 이득도 없는 식별관계를 만들어 불편함을 가중할 이유가 없다.




5. application.yml 설정하기

1~16번 라인까지의 속성은 일반적인 설정에 관한 것들이다. 웹서버 포트는 8080을 사용하고 세션은 30분 동안 유지 시키며, 어플리케이션 이름은 demo로 설정하였다. 관심을 가지고 봐야할 부분은 9~14번 라인의 로깅 부분이며, 현재 개발하고 있는 상태에 따라 적절히 추가 변경하면 될 것이다.


18~26번 라인은 데이터 소스에 관한 것이다. 여기서는 단순 테스트용이기 때문 h2 데이터베이스를 사용하고 있으며, 그에 대한 설정이다. h2 데이터베이스에 접속하고자 한다면 h2 데이터베이스 프로그램을 설치하고, H2 Console로 접속해 보면 된다.


28~43번 라인은 JPA 설정에 관한 것이다. 32번 라인의 show-sql은 실행 시 쿼리를 보이라는 것이며, generate-ddl은 DDL문장을 생성하는 기능이다. ddl-auto는 아무것도 하지 않는 none, 스키마를 생성하는 기능이 있는 create와 create-drop, 추가된 속성이 있으면 DDL문장을 갱신해 주는 update, 현재 스키마와 Entity 클래스의 정의가 동일한지 확인하는 validate 등이 있다.




6. 실행하기

이 프로그램을 실행시키기 위해서는 우선 H2 Database를 실행시켜줘야 한다. H2의 실행은 H2 Console를 실행 시킴으로써 가능한다. 실행시 접속 JDBC URL은 application.yml에 입력한 것과 동일한 것을 사용하면 된다. 

예) jdbc:h2:tcp://localhost/~/demodb





H2 Console를 실행 시켰으면, STS에서 Boot Dashboard를 찾아 demo 프로그램을 실행 시키면 된다.

실행은 (Re)start 또는 (Re)debug 중 아무거나 실행시키자. 





STS의 Console의 로그를 보면 아래와 같이 DDL문장이 만들어지고 실행되었음을 보인다.




마지막으로 H2 Console에 접속하면 해당 테이블이 만들어 졌음을 확인할 수 있다.





아래는 지금까지의 소스를 압축한 파일이다.

정말 잘되는지 궁금하면 실행해 보는 것도 좋을 것이다.


demo.zip


02. 사용자 권한 및 메뉴 관리 모델링

[스프링 부트 개발 설정]을 하면서 Spring Security를 사용할 수 있도록 했다. 그렇다는 것은 로그인을 할 수 있도록 사용자 정보가 있어야 하며, 사용자에게 부여된 권한과 권한에 부여된 메뉴가 있어야 한다는 말이다. 우리는 DB를 이용하여 로그인을 하고, 사용자에 부여된 권한을 인가하고, 사용자가 사용할 수 있는 메뉴를 보일 것이다.



Spring Security에 대한 개발을 하기 전에 사용자/권한/메뉴에 대한 일반적인 모델링을 해야 한다. 모델링된 테이블은 JPA로 관리할  것이다. 아래의 ERD는 가장 일반적인 모습의 사용자/권한그룹/메뉴에 대한 모델링이다.




1. 사용자 관리

일반적인 형태의 사용자 관리 테이블이다. 이메일과 전화번호를 이용해서 메일과 SMS와 같은 것을 이용할 수 있으며, 계정의 상태와 계정 만료일, 로그인 실패 수 등의 항목이 있다. 계정 상태와 만료일, 로그인 실패 수는 Spring Security에서 사용하는 UserDetails 인터페이스와 관련이 있다. UserDetails를 implement하면 isAccountNonExpired(), isAccountNonLocked(), isCredentialsNonExpired(), isEnabled() 등을 구현해야 하는데, isAccountNonExpired()는 계전 만료일을, isAccountNonLocked()는 로그인 연속 실패 수로 조절할 수 있으며, isCredentialsNonExpired()은 패스워드의 만료일, isEnabled() 계정의 상태 등을 감시하고 이에 대한 처리를 할 수 있다.


사용자 패스워드는 일정기간마다 변경해야 하고, 같은 패스워드는 일정 회차가 지나기 전에는 사용 할 수 없는 규칙을 정하는 경우도 많다. 따라서 패스워드의 이력을 관리해야 한다. 보안 강화의 한 방법으로 특정 IP에서만 로그인 할 수 있도록 사용자 별 IP를 맵핑하기도 한다. 



2. 권한 그룹 관리


사용자에게 부여되는 권한 그룹은 하나일 필요는 없다. 특정 사용자에게는 관리자 권한과 회계 권한을 부여할 수도 있고, 어떤 사용자에게는 회계 권한과 정산 권한, 영업직원 권한 같은 것을 부여할 수도 있으며, 어떤 사용자는 단 하나의 권한만을 사용할 수도 있다. 그러나 권한 부여 받지 못한 사용자는 서비스를 제공받을 수 없어야 한다.


이런 접근 제어 방식은 일반 기업에서 가장 많이 사용하는 역할기반 접근 제어 방식(RBAC : Role Based Access Control)이다. Spring Security에서도 이러한 접근 제어 방식을 사용하도록 되어 있다. 



3. 메뉴 관리

메뉴는 메뉴로서의 기능과 카테고리로서의 기능 두가지 소화한다. 메뉴가 자식 메뉴를 가지게되면 최종 서비스를 담당하는 메뉴가 아니라, 카테고리 같은 역할을 한다는 의미이며, 자식이 없는 메뉴만이 URL를 부여 받고 서비스를 수행할 수있다. 메뉴는 하나 이상의 권한 그룹에 소속 될 수 있으며, 권한 그룹 또한 하나 이상의 메뉴를 소유할 수 있다. 


최종적으로 보면 한 사용자는 복수의 권한 그룹에 소속될 수 있고, 권한 그룹들은 같은 메뉴를 각각 보유할 수 있다. 결국 한 사용자가 하나 이상의 같은 메뉴를 소유할 수도 있다. 이런 문제는 쿼리나 프로그램으로 처리할 수 밖에 없다.



4. 물리 모델링

물리 모델링은 논리 모델링과 유사하며, 왜래키(Foreign Key)에는 각각 이름을 주었다. 문제가 생겼을 때 발견하기 쉽게 하기 위해서이다. 또한 각 왜래키에는 Index가 주어지며, 특별히 사용자 관리 테이블의 로그인 아이디(LOGIN_ID)에는 유니크 인덱스를 추가하여 로그인 아이디의 유일성도 확보할 것이다. 

그러나, 우리는 JPA를 사용하고, 지금은 개발 초기 단계이므로 ERD에서 테이블 생성하기 보다는 JPA의 Entity 오브젝트를 만드는 것으로 생성을 완료할 것이다.




아래의 예제 파일은 ER-WIN 7.3 버전에서 생성한 것이다.


example.erwin

example.nsm


01. 스프링 부트 개발 설정


본인의 생각에는 Spring Boot란 좀더 간편한 어플리케이션 관리 체계라는 생각이 든다. 


Maven이 나오기 이전의 라이브러리 관리는 필요한 라이브러리를 찾아서 /WEB-INF/lib 폴더에 때려 넣는 것이었다. Maven을 사용하기 시작하면서는 하나씩 찾아서 넣던 라이브러리들을 pom.xml에 적어 넣기만 하면 자동으로 찾아서 넣어주니 많이 간편해 지기는 했다. 그러나 몇 백라인을 넘어가는 pom.xml의 관리도 쉬운 일은 아니다.


Spring Boot를 사용하면서도 여전히 Maven을 사용하고 pom.xml 파일을 관리하지만, pom.xml 파일의 크기는 절반 이하로 줄어든다. 이유는 스프링 부트의 모듈을 지정하면 그것에 해당하는 라이브러리가 자동으로 딸려오기 때문이다. 스프링 부트의 여러 기능 중에서도 이 기능이 제일 마음에 든다. 


스프링 부트의 또 다른 기능을 꼽자면 Javaconfig 기능이다.

물론, 스프링 부트가 아니어도 할 수 있지만 기본적으로 제공해 준다는 것과 전통적인 xml방식 보다 깔끔하고 아름답다.


부가적으로 임베디드된 웹서버라든지 서버의 상태를 확인할 수 있는 액추에이터 기능들을 한방에 구현해 준다는 것 또한 편리한 기능이라 할 수 있다.



1. 스프링 부트 프로젝트 생성

File >New > Spring Starter Project 선택면 새로운 프로젝트를 시작할 준비를 할 수 있다.




2. 기본 설정

프로젝트 이름을 정의하고, 사용할 Java 버전과 패키징 형태를 설정한다. 아마도 본격적으로 프로젝트를 시작하고자 하면 Group과 Package 항목은 자신의 프로젝트에 맞게 설정해야 할 것이다. 본인은 Packaging 항목을 War로 바꾸었다. 

이유는 내장 tomcat의 사용과 다른 WAS의 배포를 염두해 둔것이다.





3. 스프링 부트 모듈 선택

본인은 DevTools, Security, Lombok, Configuration Processor, Mail, Actuator, JPA, H2, JDBC, MyBatis, Flyway, Web 등을 선택했다.



DevTools     : 소스가 변경되면 알아서 재 시작하는 기능

Security       : Spring Security  사용한다ㅏ.

Lombok       : Lombok을 사용한 한다.

Configuration Processor : 커스텀 프로퍼티의 사용을 돕는다.

Actuator       : 실행 화경 및  상태등을 모니터링 할 수 있도록 한다.

JPA            : JPA로 개발할 수 있도록 한다.

H2             : H2 Database를 사용할 수 있도록 한다. (소개및 설치)

JDBC          : jdbcTemplate을 사용할 수 있도록 한다.

MyBatis       : MyBatis와 Spring과 MyBatis을 통합할 수 있다.

Flyway        : DB 마이그래이션 툴로 초기 실행환경을 구성하는데 유용한다.

Web          : 스프링 기반의 웹 프로젝트 라이브러리를 추가한다.



4. 설정 완료

이제 설정이 완료되었으므로 Finish 버튼을 클릭하여 실행할 준비를 마친다.



5. 실행

아직 아무것도 하지 않았지만 설정한 어플리케이션을 실행할 수는 있다.

실행은 Boot Dashboard 를 이용해서 실행시키면 된다.



아무것도 손대지 않은 상태에서 실행이 되면 좋겠지만, 아쉽게도 flyway 라이브러리로 인해 오류가 발생하므로 pom.xml 파일에서 주석처리 한다. flyway는 나중에 다시 활성화 시킬 것이다.



문제 없이 실행이 완료되었으면 http://localhost:8080/health URL을 입력하여 아래와 같이 서버의 상태를 확인할 수 잇다.  보다 상세한 서버의 상태를 보고자 하면 로그인을 해야만 확인할 수 있다. 



우리가 만든 스프링부트 어플리케이션은 액추에이터를 설정해 놓았기 때문에 기본적인 서비스가 /health 외에 다수(/mappings, /loggers, /beans, , /heapdump, /trace, /dump, /auditevents, /info, /configprops, /autoconfig, /env, /metrics)가 존재하며, 이 URI를 호출해도 로그인창이 뜨며, 로그인 후 요청한 화면으로 이동된다. 


로그인 창이 뜬 이유는 SpringSecurity가 동작하고 있다는 증거이며, 로그인 관련하여 아무런 작업도 하지 않았기 때문에 로그인 페이지가 없는 상태이다. 따라서 로그인 후 사용가능한 화면(거의 모든 화면)을 호출할 경우 아래와 같은 로그인 창이 뜨게된다.



기본 사용자 이름 'user'이며 패스워드는 로그에서 [Using default security password]를 찾아 값을 입력한다.




로그인 후  http://localhost:8080/health 를 호출하면 아래와 같이 보다 상세한 상태정보를 확인할 수 있다.



참고로 액추에이터에서 기본으로 생성한 서비스는 application.yml에서 비활성화 할 수 있다.


endopints:

enabled: false ## 전체 액추에이터 비활성화

metrics:

enabled: true  ## 매트릭스 활성화 


아래는 액추에이터 목록이다.

ID Description Sensitive
Default
actuator Provides a hypermedia-based “discovery page” for the other endpoints. Requires Spring HATEOAS to be on the classpath. TRUE
auditevents Exposes audit events information for the current application. TRUE
autoconfig Displays an auto-configuration report showing all auto-configuration candidates and the reason why they ‘were’ or ‘were not’ applied. TRUE
beans Displays a complete list of all the Spring beans in your application. TRUE
configprops Displays a collated list of all @ConfigurationProperties. TRUE
dump Performs a thread dump. TRUE
env Exposes properties from Spring’s ConfigurableEnvironment. TRUE
flyway Shows any Flyway database migrations that have been applied. TRUE
health Shows application health information (when the application is secure, a simple ‘status’ when accessed over an unauthenticated connection or full message details when authenticated). FALSE
info Displays arbitrary application info. FALSE
loggers Shows and modifies the configuration of loggers in the application. TRUE
liquibase Shows any Liquibase database migrations that have been applied. TRUE
metrics Shows ‘metrics’ information for the current application. TRUE
mappings Displays a collated list of all @RequestMapping paths. TRUE
shutdown Allows the application to be gracefully shutdown (not enabled by default). TRUE
trace Displays trace information (by default the last 100 HTTP requests). TRUE


H2 Database 설치

개발과 유닛테스트를 위한 경량 데이터베이스 엔진 입니다

Console이 포함되어 있기 때문에 별다른 툴 설치 없이도 사용 및 데이터 확인이 가능합니다


http://www.h2database.com/html/main.html 에서 다운로드 받을 수 있습니다.



설치 메뉴에서 H2 Console 실행하면 콘솔화면에 접속하여 테이블과 데이터를 확인할 있습니다.


Features를 간단히 요약하며, 가볍고 안되는 기능이 없다고 나와 있습니다.

믿거나 말거나...



03.이클립스 플러그인 설치

Properties Editor 설치

STS에서 properties 확장자를 가진 파일을 열었을 경우 영어 이외의 문자는 아래와 같이 2바이트 16진수 유니코드로 표기가 되어 알아보기 힘든 문제가 있기 때문에 Editor를 설치 해야 합니다. (다국어 코드를 프로퍼티에서 관리 하기 때문에 영어 이외에 한글, 한문 등의 문자가 들어 갈 수 있습니다.)




Help > Eclipse Marketplace 클릭


 

Find 입력란> Properties Editor 검색 > Install 버튼 클릭 > Confirm 버튼 클릭 > Accept 선택 > Finish 버튼 클릭(Security Warning 무시 - OK버튼 클릭)



위와과 같은 과정을 거치면 STS가 재시작 되고 Properties Editor가 설치됩니다.

설치가 잘 되었다면 위에서 출력되지 않던 한글 문자가 잘 출력되는 것을 볼 수 있습니다.



 

AnyEdit Tool 설치 

이클립스에서 카멜케이스, 대문자, 소문자 변경 등을 간편하게 해주는 플러그인 입니다. 

- To Lower Case :  Ctrl+Alt+L

- To Upper Case :  Ctrl+Alt+U

- Camel <-> Underscores : Ctrl+Alt+K

Help > Eclipse Marketplace 클릭, Install 버튼을 클릭하여 설치하면 됩니다.



 

FindBugs 설치

FindBugs란 Java Code 정적 분석 Tool입니다. 작성된 코드의 잠재적인 결함 가능성을 확인하여 결함 원인과 해결 방법을 제시해 줄 뿐 아니라, 결함의 심각도 및 코드 결함의 정량적 분석도 가능한 Tool입니다. 


Help > Eclipse Marketplace 클릭


Find 입력란> Properties Editor 검색 > Install 버튼 클릭 > Confirm 버튼 클릭 > Accept 선택 > Finish 버튼 클릭(Security Warning 무시 - OK버튼 클릭)



 

위와과 같은 과정을 거치면 STS가 재시작 되고 FindBugs가 설치됩니다.




PMD 설치

PMD는 정적분석도구로 자바소스코드를 스캔하여 잠재적인 문제를 찾도록 도와주는 Tool입니다. 

(※ findBugs, Check Style, PMD 중 기업에서는 PMD를 가장 선호합니다.)


Help > Install new software 클릭


Work with 입력 란에 http://sourceforge.net/projects/pmd/files/pmd-eclipse/update-site/ 주소를 입력하고 검색하면 PMD for Eclipse2, 3, 4의 버전이 검색됩니다. 4버전을 check 하고 Next 버튼을 클릭 (Spring Dashboard를 통해 받을 수도 있습니다.)



다시 Next 버튼 클릭


Accept 선택 후 Finish 버튼 클릭(설치 중 Security Warning 메시지 OK 클릭)



위와과 같은 과정을 거치면 STS가 재시작 되고 PMD가 설치됩니다.



Check Style 설치

Checkstyle는 Java의 소스코드(.java파일)의 기술형식 코딩규약에 준하고 있는지를 체크하는 오픈소스의 정적해석 툴입니다.

( Javadoc커멘트, 명령규약, import문, 스페이스등 많은 항목에 대해 체크를 할 수가 있습니다.)


Help > Install new software 클릭


Work With 입력 란에 http://eclipse-cs.sf.net/update/ 검색 > CheckStyle 체크 > Next 버튼 클릭

(Spring Dashboard를 통해 받을 수도 있습니다.)




 


Accept 선택 > finish 버튼 클릭(설치 중 Security Warning 메시지 OK 클릭)


위와과 같은 과정을 거치면 STS가 재시작 되고 checkStyle이 설치됩니다.




형상관리 (SVN) 프로그램 설치

표준 형상관리 프로그램으로는 SVN을 사용합니다.  STS에서의 설치 방법은 아래와 같습니다. Help > Eclipse Marketplace 에서 svn을 검색하여 Install> Confirm 버튼 클릭 > Accept 선택 > Finish 버튼 클릭

                


 

위과 같은 과정을 거치면 STS가 재시작되고 SVN가 설치됩니다. 

자동으로 STS가 재시작 되면 Window > Perferences > Team > SVN 을 클릭하여 SVN Connector를 설치 합니다.
(자동 설치가 안될 경우 SVN Connector 탭의 Get Connectors… 버튼을 클릭하여 설치를 할 수 있습니다.)
SVN Kit를 check 하고 Finish 버튼을 클릭 하면 설치가 됩니다.



 

설치 이후 재시작이 되면 Window > Preferences > Team > SVN > SVN Connector 에서 설치가 제대로 되었는지 확인을 할 수 있습니다.



 




 

Lombok 설치

Lombok은 멤버변수에 대한 Getter/Setter 및 생성자 등을 어노테이션으로 생성할 수 있도록 하는 라이브러리 입니다.  

라이브러리는 https://projectlombok.org/download에서 다운로드 받을 수 있으며, 설치는 약간 독특한 방법을 통해 이루어 집니다.

Lombok.jar을 다운로드 받아 더블 클릭하여 아래와 같은 화면이 뜨는 것을 확인합니다.  화면이 뜨면 Specify location … 에서 sts.exe(or eclipse.exe) 경로를 입력하고, install 버튼을 클릭한 후 재시작 하면 됩니다.

(만약 더블클릭으로 실행이 되지 않을 경우 콘솔창(cmd)에서 java –jar lombok.jar 입력하여 실행하면 됩니다. )

 


lombok에서 사용할 수 있는 어노테이션은 @Getter/Setter, @EqualsAndHashCode, @ToString, @Log, @Data, @AllArgsConstructor, @NoArgsConstructor 등입니다. lombok은 코드를 짧고 깔끔하게 관리할 수 있도록 도와줍니다.


 어노테이션

 설명

 @Getter/Setter

 getter와 setter를 생성해 준다

 @EqualsAndHashCode

 equals() 와 hashcode() 메소드를 생성해 준다.

 @ToString

 toString() 메소드를 생성해 준다. 속성으로 exclude 가 있으며 제외할 멤버를 선택할 수 있다.

 @AllArgsConstructor

 클래스에 선언하면 모든 멤버변수를 생성자에 참여 시킨다.

 @NoArgsConstructor

 클래스에 선언하면 기본 생성자(인자 없는 생성자)를 만들어 준다.

 @Data

 @Getter/Setter, @EqualsAndHashCode, @ToString 을 합친 기능을 한다.

 @Log

 log 필드를 자동으로 만들어 주고, 해당 클래스의 이름으로 객체를 생성하여 할당해 준다

 @Builder

 클래스에 선언하면 자동으로 해당 클래스에 빌더를 추가해 줌

 @Singular

 컬렉션으로 된 멤버변수에 선언하면 모든 원소를 한 번에 넘기지 않고 원소를 하나씩 추가할 수 있다.


02. STS 설치 및 환경설정

개발 도구는 순수 Eclipse 보다는 springsource-tool-suite(이하 STS)를 사용할 것을 권장합니다. http://spring.io/tools/sts/all 에 접속하면 OS에 따라 적절한 STS를 다운로드 받을 수 있습니다. 설치할 때 주의할 점은 JDK 아키텍처가 32bit/64bit용인지 확인하는 것입니다. 그에 따라 다운로드 받아야할 STS도 달라집니다.


개발 환경에 따라 1.6 또는 1.7 버전의 JDK를 사용해야 하는 경우 STS도 그에 맞는 버전을 사용해야 합니다. 이전 버전의 STS는 https://spring.io/tools/sts/legacy 에서 다운로드 받을 수 있습니다.



 

다운받은 압축된 설치파일은 C:\java\springsource 위치에 풀면 실행파일을 통해 STS를 사용 할 수 있습니다. 

workspace는 D:\workspace를 사용할 것을 권장합니다.




기본 인코딩 설정

상단의 메뉴에서 Window > Preference를 통하여 환경설정에 들어간 후, General > Workspace > Text file encoding 에서 Other항목을 UTF-8로 설정해 주면 자바파일들에 대한 기본 인코딩이 변경 되게 됩니다.

 



Java파일을 제외한 class, html, jsp, XML 등에 대한 인코딩 변경은 General > Content Types

에서 Default encoding 을 UTF-8로 설정하면 변경됩니다.

 


 

JDK 설정

상단의 메뉴에서 Window > Preference를 통하여 환경설정에 들어간 후, Java> Installed JREs 를 선택합니다. 

 


Add버튼 > Standard VM > Next

 


JRE home 에 앞에서 설치한 JDK경로를 지정하면 JDK가 추가됩니다. 추가한 JDK를 선택하고 저장합니다.

 

 


이후 환경설정 > Java > Installed JREs > Execution Environments 에서  방금 선택한  JDK버전을 선택한 후  확인하면  JDK설정이 완료 됩니다.




폰트 설정

표준 폰트로는 [나눔 고딕 코딩]을 사용합니다.  사용 이유는 글자와 공백의 크기가 일정하여 코딩에 유용한 환경을 제공하기 때문입니다.  나눔폰트가 없을 경우 다음의 주소에서 다운받아 설치한 후 작업을 진행합니다. 



https://developers.naver.com/opensource/tools/editor/



STS에서의 사용법은 window > Preferences > General > Appearance > Colors and Fonts > Basic > Text Font 를 선택한 후 Edit 버튼을 클릭하여 폰트를 설정합니다.





 

Code Templates 설정

Code Templates 설정은 사전에 STS가 설치된 경로(C:\java\springsource)의 설정파일인 STS.ini( 이클립스의 경우는 eclipse.ini)에 다음의 내용을 추가 해야 합니다.


-Duser.name="사용하고자하는 유저명"



위의 문장을 추가하면 밑에서 사용될 ${user} 이 “사용하고자하는 유저명”으로 출력 됩니다. 



Code Templates은 파일 맨위에 들어가는 저작권 표시나 메소드 설명을 하기위한 공간을 자동으로 만들어 주는 설정입니다.

이제 Code Templates을 설정해 보도록 하겠습니다. Window > Perferences > Java > Code Style > Code Template > Comments > Files 에는 아래의 문장을 입력합니다.  입력은 Edit 버튼을 통해 입력하면 됩니다.


라이선스 표시 스타일의 주석

/*

 * Copyright (c) 2016 example.co.kr CO.,LTD. All rights reserved.

 *

 * This software is the confidential and proprietary information of example.co.kr CO.,LTD.

 * You shall not disclose such Confidential Information and shall use it

 * only in accordance with the terms of the license agreement you entered into

 * with example.co.kr CO.,LTD.

 */


SI 프로젝트 스타일의 주석

/************************************************************

 * 시스템 명 : 

 * 업무명 :

 * 프로그램명(ID) :

 * 프로그램 설명 :

 * 

 * 작성일 : ${date}

 * 작성자 : ${user}

 *

 * 수정자     수정일자     수정내역

 * ------    ----------    ---------------------------------

 * ${user}    ${date}    최초 생성

 *

 ************************************************************/



Window > Perferences > Java > Code Style > Code Template > Comments > Types


/**

 * ${file_name} 

 *

 * @author ${user}

 * @see

 */




Window > Perferences > Java > Code Style > Code Template > Comments > Methods


/**

 * Enter the description of ${enclosing_method}.

 *

 * ${tags}

 * @author ${user} 

 * @see

 */




Debug Filtering 설정


다음과 같은 과정을 통해 Debug Filter를 설정할 수 있습니다.

Window > Preferences > Java > Debug > Step Filtering


 


Use Step Filters 체크 > Select All 버튼 > Apply 버튼 > OK 버튼


01. JAVA 설치 및 환경 설정


먼저 오라클 홈페이지(http://www.oracle.com/)로 이동 후 Download Java For Developers 메뉴로 접근합니다.

(http://www.oracle.com/technetwork/java/javase로 바로 접근할 수도 있습니다.)

JDK는 최신 버전 또는 이전 버전을 다운로드하여 설치하면 됩니다.


JDK8 다운로드 URL : http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html

JDK7 다운로드 URL http://www.oracle.com/technetwork/java/javase/downloads/java-archive-downloads-javase7-521261.html 





설치 시 JAVA 설치 폴더는 C:\Program Files로 설정되는 것이 기본이지만, C:\java 로 변경해 줍니다.

C:\java로 변경하는 이유는 java관련 개발툴의 설치 위치를 C:\java로 설정하기 위함입니다. 

JDK 설치 후 JRE의 설치 화면이 뜨는데 JRE는 설치하지 않아도 됩니다.



설치가 완료되었으면 자바 환경 변수 설정을 해야합니다. 내 컴퓨터의 속성에서 [고급 시스템 설정]을 클릭합니다.



고급 탭의 환경변수에 들어가서 하단의 새로 만들기를 통해 새 시스템 변수를 다음과 같이 입력 합니다

첫번째는 변수 이름에 JAVA_HOME이며, 변수 값은 설치한 JDK의 설치 경로를 넣으면 됩니다. 

두번째 [시스템 변수]에서 이미 등록되어 있는 Path 를 찾아 변수값 뒤에 ;%JAVA_HOME%\bin; 을 붙여줍니다.



Window 10의 경우에는 JAVA_HOME의 설정은 동일하지만, Path 변수는 문자열 맨 끝에 추가하는 것이 아니, 목록에서 추가하는 것으로 설정이 보다 깔끔합니다.



환경변수 설정이 끝났습니다. 정상적으로 설치가 되었는지 확인을 해보겠습니다.

cmd 에 들어가서 java –version 명령어로 현재 설치한 버전이 맞는지 확인합니다.