
① 편에서는 Session Clustering을 위해 Session 저장소를 Spring 내부가 아닌 Key-Value 형태의 DB인 Redis에 저장하고 확인해봤습니다. 또한 ① 편에서 만든 스프링은 로컬 환경에 초점을 두고 있었습니다.
② 편에서는 개발 서버에 배포할 수 있도록 ① 편 스프링에 몇 가지 코드를 추가하도록 하겠습니다.
그럼 먼저 포스트에 사용되는 기술들의 버전 정보에 대해 나열하도록 하겠습니다. ① 편의 로컬 개발 환경과 배포할 Docker 환경의 다른 부분이 있으니 주의하세요.
- 개발 도구: IntellJ Ultimate
- Spring Boot: 2.5.9
- Gradle
- Docker
- CentOS 7 => Docker Container
- Nginx => CentOS 7에 설치
- MySQL 5.7 => CentOS 7에 설치
- Java 11 => CentOS 7에 설치
- Redis => CentOS 7에 설치
그럼 이제 ① 편의 스프링에 몇 가지 코드를 추가하도록 하겠습니다.
1. InitData
로그인할 수 있는 계정 하나를 더 등록하도록 하겠습니다.
기존 소스코드 InitData 클래스는 @Profile에 local을 줬기 때문에 스프링이 구동될 때 profile이 local일 경우에만 빈으로 등록되도록 설정했습니다. 그러나 이번 포스트에서 배포할 때 dev라는 profile의 환경 설정 파일을 분리해서 진행할 예정이기 때문에 dev로 실행했을 때도 InitData가 빈으로 등록되어 데이터가 초기에 들어가 있을 수 있도록 하기 위해 다음과 같이 추가하도록 하겠습니다.
@Profile({"local", "dev"}) // dev 추가
@Component
@RequiredArgsConstructor
public class InitData {
private final InitDataService initDataService;
@PostConstruct
public void init() {
initDataService.memberInit();
}
@Component
static class InitDataService {
@PersistenceContext
EntityManager em;
@Transactional
public void memberInit() {
Role role = Role.builder()
.roleCode("ROLE_ADMIN")
.roleDesc("관리자 권한")
.build();
em.persist(role);
Member member = Member.builder()
.email("wangtak@gmail.com")
.password(BCrypt.hashpw("1234", BCrypt.gensalt()))
.name("왕탁이")
.role(role)
.build();
Member admin = Member.builder()
.email("admin@gmail.com")
.password(BCrypt.hashpw("1234", BCrypt.gensalt()))
.name("관리자")
.role(role)
.build(); // 관리자 계정 추가
em.persist(member);
em.persist(admin); // 관리자 계정 등록
}
}
}
2. application-dev.yml
로컬에서 IDE를 사용하여 개발하고 테스트할 때 별다른 옵션을 설정하지 않는 이상 기본적으로 application.yml을 바라보고 환경 설정 정보를 읽어 옵니다. 그러나 로컬 환경과 개발 환경은 다를 수 있기 때문에 배포할 때마다 application.yml을 [local 개발 -> 테스트 -> dev용 수정 -> 개발 서버 배포 -> 테스트 -> local용 수정 -> 개발 -> 테스트] 이러한 작업을 반복하는 것은 말이 안 되고 귀찮기 때문에 실무에서는 환경 설정 파일을 분리하여 사용합니다.
이번에 저희도 개발 서버에 배포한다는 생각으로 개발 서버용 환경 설정 파일을 따로 추가하여 배포하도록 하겠습니다. 왜냐하면 Local에서 h2 DB를 사용했다면, 개발 서버인 Docker CentOS 7에서는 MySQL 8.0을 사용할 것이기 때문입니다.
spring:
datasource:
url: mysql://localhost:3306/wangtakDB?serverTimezone=UTC&characterEncoding=UTF-8
username: wangtak
password: 1234
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: create
properties:
hibernate:
format_sql: true
default_batch_fetch_size: 1000
session:
store-type: redis
timeout: 30m
3. build.gradle - MySQL용 JDBC Driver
Docker에서는 MySQL 8.0을 사용할 것이기 때문에 build.gradle에 다음 한 줄을 추가 해줍니다. 또한, .jar를 사용하여 배포를 진행할 예정이기 때문에 추가적으로 .jar의 이름을 설정하도록 합니다.
// ..
dependencies {
// ..
runtimeOnly 'com.h2database:h2' // Local에서 사용한 h2
runtimeOnly 'mysql:mysql-connector-java' // Dev에서 사용할 MySQL
// ..
}
bootJar { // bootJar 했을 때 나오는 .jar의 이름 설정
archiveFileName = 'session.jar'
}
4. index.html에 현재 실행된 port 번호 찍기
동일한 애플리케이션을 2개 이상의 인스턴스로 띄울 때 쉘 스크립트, 빌드 자동화를 통해서 포트만 다르게 해서 배포를 하는 것이 일반적입니다. 그러나 저희는 Nginx의 LB 기능을 사용하여 Session Clustering이 정상적으로 작동하는지 확인하고, 요청을 보냈을 때 응답을 하는 서비스가 누구인지 가시적으로 확인할 필요가 있습니다. 그렇기 때문에 로그인했을 때 기본적으로 redirect 되는 위치인 '/'에 현재 서비스 중인 포트 번호를 출력하도록 하고 새로고침을 반복하여 port가 변경되는 것을 확인할 것이며, 포트가 변경됐을 때 로그인이 풀리지 않고 지속적으로 유지되는 것 또한 확인할 것입니다.
위와 같은 확인을 위해서는 resources > templates > index.html => 이 친구에 코드를 추가해야 합니다.
Thymeleaf는 Spring과 찰떡궁합이기 때문에 정말 Spring과 엄청난 연동성과 편리한 기능을 많이 제공합니다. 그리고 그중 하나는 바로 이 코드인 것 같습니다.
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width,
user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Wangtak</title>
</head>
<body>
<h1>Main Page</h1>
<h1 th:text="${#request.getLocalPort()}"></h1> <!-- 추가 된 코드 -->
</body>
</html>
저렇게 #request를 사용하면 저희가 Java 코드에서 사용한 HttpServletRequest를 사용할 수 있습니다. 엄청난 편의 제공이라 생각합니다.
TMI
가령 Java에서는 인증 객체를 SecurityContextHolder.getContext().getAuthentication()를 사용하여 접근할 수 있습니다. 그러나, Thymeleaf에서는 #authentication을 하게 되면 끝입니다. 그래서 현재 로그인 한 회원의 이름을 확인하고 싶다면 #authentication. getPrincipal(). getUsername()을 하게 되면 현재 로그인 한 유저의 이름을 쉽게 얻을 수 있습니다.
Q. 지금 진행 중인 스프링에는 "/"에 대한 매핑 정보를 (ex. @GetMapping("/") return "index.html") 등록하지 않았는데 로그인 이후에 404 error가 뜨지 않고 index.html을 찾아가는가??
A. 스프링은 "/"가 등록되어 있지 않다면 자동으로 index.html을 불러옵니다. 비슷한 예시로 4xx.html, 5xx.html을 errors 디렉터리에 만들어 두고 사용한다면 400번대 에러가 발생하면 4xx.html을, 500번대 에러가 발생하면 5xx.html을 불러옵니다.
그러면 ①, ② 편에서는 Session Clustering을 위해 스프링 애플리케이션을 만들었습니다. ③ 편에서는 Redis 설치, Nginx LB 설정, 스프링 배포 및 결과 확인을 하도록 하겠습니다.
'Spring > Spring Security' 카테고리의 다른 글
Spring Security - Nginx LB + 세션 클러스터링(Session Clustering) ③ (0) | 2022.02.21 |
---|---|
Spring Security - Nginx LB + 세션 클러스터링(Session Clustering) ① (1) | 2022.02.09 |
Spring Security - Form 인증 로그인 (0) | 2022.02.06 |