스프링 부트와 AWS로 혼자 구현하는 웹 서비스
: 인텔리제이, JPA, JUnit 테스트, 그레이들, 소셜 로그인, AWS 인프라로 무중단 배포까지
이동욱 저
Table of Content
스프링 시큐리티와 스프링 시큐리티 Oauth2 클라이언트
왜 많은 서비스에서 소셜 로그인을 사용할까? 직접 구현할 경우 배보다 배꼽이 커지는 경우가 많다.
•
로그인 시 보안
•
회원가입 시 이메일 또는 전화번호 인증
•
비밀번호 찾기
•
비밀번호 변경
•
회원정보 변경
위와 같은 기능을 부가적으로 구현해야 하는데 이런 기능은 구글, 페이스북, 네이버 등 맡기면 되기 때문에 서비스 개발에 집중 할 수 있다.
스프링 부트 1.5 vs 스프링 부트 2.0 내용에서 버전에 대한 정리
본 교재에서는 스프링 부트 1.5, 2.0에 대한 비교를 주로 다루며 그 중 2.0을 기준으로 작성된 경우가 많다. 하지만 나는 3.2 버전을 사용하고 있으며 JDK는 17버전을 사용하고 있다. 항상 애매하게 궁금했던 부분인데 Java 버전과 스프링 버전, 스프링 부트 버전의 선택 이유에 대한 것이었다.
그것은 서로 호환되는 버전의 차이가 주요 내용이었다. 결과적으로 프레임워크가 최신버전일수록 JDK17 버전을 필요로 하는 것이 보여졌다.
SpringBoot의 버전 개별 도큐먼트에서도 System Requirements에서 Java의 버전, Spring Framework의 버전을 확인 할 수 있다.
JDK 버전에 대한 내용??
Google 로그인 구현 준비
Google 클라우드 플랫폼(개발자) 등록
ClientId와 ClientSecret를 통해서 SNS 로그인 기능을 사용 할 수 있기 때문에 앱을 등록하고자 한다.
1. 프로젝트 생성
2. 사용자 동의화면 만들기
3. OAuth 클라이언트 ID 만들기
프로젝트에서의 기본 설정
필요한 clientId, clientSecret 코드를 프로젝트에서 사용하도록 설정해야 한다.
1. application-oauth.properties 생성
spring.security.oauth2.client.registration.google.client-id=[Client ID]
spring.security.oauth2.client.registration.google.client-secret=[Client Secret]
spring.security.oauth2.client.registration.google.scope=profile,email
Java
복사
clientId와 secret은 github을 통해서 외부 노출되면 안되기 때문에 .gitignore 를 통해서 해당 파일의 git 파일 추적을 제외시킨다.
2. application.properties 코드 추가
스프링 부트의 기본 설정 파일인 application.properties 에서 application-oauth.properties 를 포함하도록 코드를 추가
spring.profiles.include=oauth
Java
복사
이제 이 설정값을 사용 할 수 있다.
구글 로그인 연동하기
구글의 로그인 인증 정보를 발급 받았으니 프로젝트에서의 실제 구현을 시작하자.
User.java 클래스 생성
package com.citefred.ldwspring.domain.user;
import com.citefred.ldwspring.domain.BaseTimeEntity;
import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
@Entity
public class User extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String email;
@Column
private String picture;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private Role role;
@Builder
public User(String name, String email, String picture, Role role) {
this.name = name;
this.email = email;
this.picture = picture;
this.role = role;
}
public User update(String name, String picture) {
this.name = name;
this.picture = picture;
return this;
}
public String getRoleKey() {
return this.role.getKey();
}
}
Java
복사
@Enumberated(EnumType.STRING)
•
JPA로 데이터베이스를 저장 할 때 Enum 값을 어떤 형태로 저장 할 지(String으로 지정함)
•
기본값은 int로 된 숫자가 저장 됨
•
숫자는 그 값이 무슨 유형, 값인지 알 수 없으니 문자열로 저장 될 수 있도록 하는것이 좋음
Role.java Enum 클래스 생성
package com.citefred.ldwspring.domain.user;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum Role {
GUEST("ROLE_GUEST", "손님"),
USER("ROLE_USER", "일반 사용자");
private final String key;
private final String title;
}
Java
복사
•
스프링 시큐리티에서는 항상 ROLE_이 앞에 있어야만 한다.
UserRepository.java 인터페이스 생성
User의 CRUD를 위한 Repository 생성
package com.citefred.ldwspring.domain.user;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
}
Java
복사
Optional<User> findByEmail(String email);
•
소설 로그인으로 반환되는 값 중에 email을 통해 이미 생성된 사용자인지 최초 가입 사용자인지 확인하기 위한 메소드
스프링 시큐리티 설정
build.gradle 의존성 추가
...
dependencies {
...
// OAuth 2.0
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
}
Java
복사
버전에 대한 이해도 부족
여기서부터 버전간의 호환에 대한 이해도 부족이 발생
JDK 8, 11, 17
SpringBoot 2.x, 3.x
이 버전 차이에 따라 생각보다 많은 코드의 변화들이 있지만, 많은 것들을 다뤄보지 않은 신입의 입장으로썬 최신을 선택해야 되지 않을까라는 막연한 생각이 많았다. 또한 프로젝트를 진행해오면서도 각종 레퍼런스들을 찾아내도 구현에 어려움이 있던 문제는 대부분의 레퍼런스들은 Boot 2.x로 작성된 경우가 많아서 이것을 스스로 마이그레이션 해야 하는 경우가 많았는데, 이해도가 부족한 상황에서는 이게 맞나? 라는 생각과 정상적으로 작동되는 것이 맞는지 스스로 신뢰도가 부족했다.
이런 버전간 문제에 대해서 고민을 하게 되었고 과연 “배우는 입장”에서 최신버전을 찾아가는 단순한 생각이 맞을까를 다시 한번 살펴보고 선배들의 선택의 근거들을 다시 한번 찾아보기 시작했다.
결론적으로는 JDK 17에 대한 프로젝트(레퍼런스)가 부족한 상황이 많았다. 때문에 빠른 개발에 유용한 JDK 8, 11에 레퍼런스가 많은 것은 나같은 초보 개발자에게 필요한 선택이었다. 이에 맞는 Spring Framework, Boot 버전을 사용한 것이고 이에 따라 Security 등 다른 의존성 또한 자동으로 그 버전에 맞는 레퍼런스가 압도적으로 많았다.
그럼, 왜? 최신버전을 사용하지 않을까?
그럼 왜 그 버전들에 압도적으로 많은 사용량을 보여줄까? 한국은 전자정부 표준프레임워크의 기준도 하나의 역할을 하지 않았을까? 아직 Boot 3.x이상(JDK17버전 이상 필요)에 대한 요구사항이 업데이트 되지 않은 상황이다.
이런 점들을 고려해서 SpringBoot2.x 버전을 사용 할 수 있는 JDK11버전이 적합해보이는 것으로 보여졌다.
다양한 질문들에서도 선배들의 조언은 다음과 같았다.
가끔 질문글 보니까 스프링에 자바 1.8 깔았는데 안된다는 질문들 보니...
알고보니 스프링 버전이 6, 혹은 스프링 부트 3.0을 깔아서 발생한 문제죠.
스프링 6, 스프링 부트 3은 JDK 17부터 지원합니다.
한국 업계에서 앞으로 15년간(출시 2021년 + 자바버전 17 = 2038년까지) 자바 17 버전 쓸 일은 없으니
혹시 이 글을 보고 있는 입문 개발자라면, 스프링 5 혹은 스프링 부트 2 를 쓰시기 바랍니다.
또한 17버전을 사용하는 선배의 조언 또한 다음과 같다.
머리로는 이해가 된다. 앞을 바라보고 마이그레이션의 최소화, 기술 선도, 신규 버전을 위한 대비, 다음 세대 플랫폼 호환 준비 하지만 당장 코린이의 입장에선 어떤 기준을 잡아야 할까?
중요한 부분은 가장 앞에 닥친 문제들을 보고 판단해야 할 것 같다.
Spring Boot 2의 지원은 2023년 말
Spring Boot 2 -> JDK 8, 11, 17 상관없음,
Spring Boot 3 -> JDK 17 이상으로
여기서 JDK17을 사용해야 하는 이유가 나타나긴한다. 당장 JDK17버전을 사용하는 것에 대한 문제는 없어보인다. 하지만 스프링 부트의 버전은 고려할 부분이다. 왜 항상 Boot 3.x버전이었을까 생각한 결과 프로젝트 생성 위저드에서 https://start.spring.io 에서의 추천 목록에 항상 3.x 버전이 나타났기 때문이었다. 이에 따라 JDK는 17버전 이상을 강제해왔던 것이다.
여기서 또 고려해야 할 것이 있다.
JDK별 Gradle의 최소 버전이 정해지기도 한다.
Boot 2.6.1 버전으로 변경 후 재시작 준비
전체적으로 Boot 버전의 2.x, 3.x의 문제로 교재의 내용을 순수하게 따라 갈 수 없던 문제인 것으로 확인했고, 이에 따라 2.x버전으로 다운그레이드하여 여러 레퍼런스를 그대로 활용 할 수 있는데 도움이 될 것으로 생각되었다. JDK는 내가 사용하는 수준에서는 호환성의 문제는 없기 때문에 그대로 17 버전을 사용하기로 결정했다.
이후 다른 의존성들은 Boot 버전에 맞추어 모두 재설정되기 때문에 편리하다(이것이 부트의 장점이기도 하다.)
WebSecurityConfigurerAdapter 클래스를 못찾던 부트 3.2 에서의 6.x Security의존성과는 다르게 다운그레이드하여 Securiy 5.6 버전대로 들어오니 WebSecurityConfigurerAdapter 를 그대로 사용 하여 해당 레퍼런스를 그대로 사용 할 수 있었다. 이 부분을 새로운 버전으로 마이그레이션하려 다양한 시도를 했었지만 이해도 부족으로 괜히 복잡한 것을 더 복잡하게 삽질하는 결과가 있었다. 하지만 이제 이대로 흐름을 읽는데 집중하고자 한다.
ps. 프로젝트에서도 계속 antMatchers메소드를 사용하는 레퍼런스가 자주 보였지만 버전 문제로 requestMatchers메소드로 리팩토링 했어야 하는 것이 의문이긴 했었다. 하지만 이 문제가 버전 맞춤으로 깔끔하게 해결되어 다양한 레퍼런스들을 그대로 참고 할 수 있게 되었다. 분명 윗 버전에서 보다 깔끔하게 HttpSecurity의 체인 메소딩을 보여준것 같았는데 아래처럼 무분별한 메소드 연결 부분들이 다시 보이긴한다.
package com.citefred.ldwspring.config;
import com.citefred.ldwspring.config.oauth2.service.CustomOAuth2UserService;
import com.citefred.ldwspring.domain.user.Role;
import lombok.RequiredArgsConstructor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final CustomOAuth2UserService customOAuth2UserService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.headers().frameOptions().disable()
.and()
.authorizeRequests()
.antMatchers("/", "/css/**", "/images/**", "/js/**", "/h2-console/**", "/profile").permitAll()
.antMatchers("/api/v1/**").hasRole(Role.USER.name())
.anyRequest().authenticated()
.and()
.logout()
.logoutSuccessUrl("/")
.and()
.oauth2Login()
.userInfoEndpoint()
.userService(customOAuth2UserService);
}
}
Java
복사
Related Posts
Search