철학과 학생의 개발자 도전기
[Spring] 스프링 부트와 AWS 스터디 - 2주차 본문
05장 스프링 시큐리티와 OAuth2.0으로 로그인 기능 구현하기
소셜 로그인의 장점
로그인 기능을 직접 구현하는 것보다 소셜 로그인을 활용하는 것이 좋다.
- 로그인 보안
- 회원가입 인증
- 비밀번호 찾기
- 비밀번호 및 회원정보 변경
위 4가지 기능을 직접 구현하지 않아도 되기 때문이다.
GCP 사용자 인증 정보
구글을 통한 소셜 로그인 기능을 구현해볼 것이다. 우선 GCP에서 인증 정보를 만들어보자.
GCP에 접속한 뒤 새 프로젝트를 생성한다.
그리고 방금 생성한 프로젝트를 선택한다.
이제 왼쪽 상단의 메뉴 탭을 열어서 사용자 인증 정보에 접속한다.
현재는 생성된 정보가 없다.
사용자 인증 정보 만들기를 클릭해서 필요한 정보들을 입력하면 다음과 같이 클라이언트 ID가 생성된다.
application-oauth.properties 생성
이제 스프링부트에 방금 만든 클라이언트 ID를 등록해주면 된다.
application.properties 파일이 있는 위치에 application-oauth.properties 파일을 생성하고 ID를 등록한다.
scope=profile,email
이메일과 프로필 정보만 받아오도록 만들어서 네이버, 카카오 등의 소셜로그인에도 범용성있게 적용할 수 있다.
application.properties 파일에 oauth를 등록해준다.
application-(이름).properties 파일을 생성하면 (이름)을 가진 profile이 생성된다.
여기서는 oauth라는 profile을 호출한다.
.gitignore에 application-oauth.properties 파일을 추가해서 시크릿 키가 노출되지 않도록 한다.
구글 로그인 연동
도메인에 User 클래스를 추가한다.
package org.example.springboot.domain.user;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.example.springboot.domain.BaseTimeEntity;
import javax.persistence.*;
@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();
}
}
- @Enumerated(EnumType.STRING): Enum 값을 DB에 저장할 때 숫자 대신 문자열로 저장되게 설정
사용자의 권한을 관리할 Enum 클래스 Role을 생성한다.
package org.example.springboot.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 Enum에 대한 자세한 내용은 우아한형제들 기술블로그에서 확인가능하다.
Java Enum 활용기 | 우아한형제들 기술블로그 (woowahan.com)
Java Enum 활용기 | 우아한형제들 기술블로그
{{item.name}} 안녕하세요? 우아한 형제들에서 결제/정산 시스템을 개발하고 있는 이동욱입니다. 이번 사내 블로그 포스팅 주제로 저는 Java Enum 활용 경험을 선택하였습니다. 이전에 개인 블로그에 E
techblog.woowahan.com
User의 CRUD를 담당할 UserRepository도 생성한다.
package org.example.springboot.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);
}
Optional을 null이 나올 수 있는 값에 사용해서 NPE(Null Pointer Exception)를 방지할 수 있다.
[Java] Optional이란? Optional 개념 및 사용법 - (1/2) - MangKyu's Diary (tistory.com)
[Java] Optional이란? Optional 개념 및 사용법 - (1/2)
이번에는 Java8부터 지원하는 Optional 클래스에 대해 알아보도록 하겠습니다. 1. Optional이란? Optional 개념 및 사용법 [ NPE(NullPointerException) 이란? ] 개발을 할 때 가장 많이 발생하는 예외 중 하나가 바
mangkyu.tistory.com
스프링 시큐리티 설정
build.gradle에 스프링시큐리티 관련 의존성을 추가한다.
compile('org.springframework.boot:spring-boot-starter-oauth2-client')
config.auth 패키지를 만든 뒤 SecurityConfig 클래스를 생성한다.
package org.example.springboot.config.auth;
import lombok.RequiredArgsConstructor;
import org.example.springboot.domain.user.Role;
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/**").permitAll()
.antMatchers("/api/v1/**").hasRole(Role.USER.name())
.anyRequest().authenticated()
.and()
.logout().logoutSuccessUrl("/")
.and()
.oauth2Login().userInfoEndpoint().userService(customOAuth2UserService);
}
}
- @EnableWebSecurity: Spring Security 설정 활성화
- csrf().disable().headers().frameOptions().disable(): h2-console 화면을 사용하기 위해 옵션 끄기
- authorizeRequests(): URL 별 권한 관리 설정을 시작
- antMatchers(): 권한 관리 대상을 지정 / pertmitAll로 모두에게 권한을 주거나 hasRole로 USER에게만 권한을 줌
- anyRequest(): 나머지 URL / authenticated로 인증된 사용자에게 권한을 줌
- logout().logoutSuccessUrl(): 로그인에 성공한 후 접속할 URL
- oauth2Login(): OAuth2 로그인 설정 시작
- userInfoEndpoint(): OAuth2 로그인 성공 이후 사용자 정보 가져올 때 설정
- userService(): 소셜 로그인 성공 시 동작들을 수행할 구현체 등록 / UserService 인터페이스를 구현한 것
CustomOAuth2UserService 클래스를 생성한다.
package org.example.springboot.config.auth;
import lombok.RequiredArgsConstructor;
import org.example.springboot.domain.user.User;
import org.example.springboot.domain.user.UserRepository;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpSession;
import java.util.Collections;
@RequiredArgsConstructor
@Service
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
private final UserRepository userRepository;
private final HttpSession httpSession;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
OAuth2User oAuth2User = delegate.loadUser(userRequest);
String registrationId = userRequest.getClientRegistration().getRegistrationId();
String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails()
.getUserInfoEndpoint().getUserNameAttributeName();
OAuthAttributes attributes = OAuthAttributes.of(registrationId, userNameAttributeName,
oAuth2User.getAttributes());
User user = saveOrUpdate(attributes);
httpSession.setAttribute("user", new SessionUser(user));
return new DefaultOAuth2User(
Collections.singleton(new
SimpleGrantedAuthority(user.getRoleKey())),
attributes.getAttributes(),
attributes.getNameAttributeKey());
}
private User saveOrUpdate(OAuthAttributes attributes) {
User user = userRepository.findByEmail(attributes.getEmail())
.map(entity -> entity.update(attributes.getName(), attributes.getPicture()))
.orElse(attributes.toEntity());
return userRepository.save(user);
}
}
- registrationId: 현재 진행중인 로그인 서비스의 구분자 / 구글인지 네이버인지
- userNameAttributeName: OAuth2 로그인 진행시 키가 되는 필드 값
- OAuthAttributes: OAuth2User의 attribute를 담을 클래스
- SessionUser: 세션에 사용자 정보를 저장하기 위한 Dto
구글 사용자 정보가 업데이트되면 User 또한 업데이트할 수 있게 기능을 구현했다.
config.auth.dto 패키지를 생성하고 OAuthAttributes 클래스를 만든다.
package org.example.springboot.config.auth.dto;
import lombok.Builder;
import lombok.Getter;
import org.example.springboot.domain.user.Role;
import org.example.springboot.domain.user.User;
import java.util.Map;
@Getter
public class OAuthAttributes {
private Map<String, Object> attributes;
private String nameAttributeKey;
private String name;
private String email;
private String picture;
@Builder
public OAuthAttributes(Map<String, Object> attributes, String nameAttributeKey,
String name, String email, String picture) {
this.attributes = attributes;
this.nameAttributeKey = nameAttributeKey;
this.name = name;
this.email = email;
this.picture = picture;
}
public static OAuthAttributes of(String registrationId, String userNameAttributeName,
Map<String, Object> attributes) {
return ofGoogle(userNameAttributeName, attributes);
}
private static OAuthAttributes ofGoogle(String userNameAttributeName,
Map<String, Object> attributes) {
return OAuthAttributes.builder()
.name((String) attributes.get("name"))
.email((String) attributes.get("email"))
.picture((String) attributes.get("picture"))
.attributes(attributes)
.nameAttributeKey(userNameAttributeName)
.build();
}
public User toEntity() {
return User.builder()
.name(name)
.email(email)
.picture(picture)
.role(Role.GUEST)
.build();
}
}
- of(): Map에서 데이터를 추출해서 변환
- toEntity(): 처음 가입 시 권한을 GUEST로 주고 엔터티 생성
SessionUser 클래스도 만들어준다.
package org.example.springboot.config.auth.dto;
import lombok.Getter;
import org.example.springboot.domain.user.User;
import java.io.Serializable;
@Getter
public class SessionUser implements Serializable {
private String name;
private String email;
private String picture;
public SessionUser(User user) {
this.name = user.getName();
this.email = user.getEmail();
this.picture = user.getPicture();
}
}
세션에 정보를 저장할 때는 직렬화를 해야하므로 SessionUser를 따로 만들어준다.
User 엔터티에 직렬화 코드를 넣지 않는 이유는 User 엔터티가 다른 엔터티와 관계를 맺으며 사이드 이펙트를 야기할 수 있기 때문이다.
어노테이션 기반으로 개선하기
반복되는 코드를 개선하기 위해 @LoginUser 어노테이션을 만들어보자.
package org.example.springboot.config.auth;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser {
}
- @Target(ElementType.PARAMETER): 메소드의 파라미터로 선언된 객체에서만 사용가능
- @interface: 이 파일을 어노테이션 클래스로 지정
LoginUserArgumentResolver 클래스도 생성한다.
HandlerMethodArgumentResolver 인터페이스를 구현한 클래스이다.
package org.example.springboot.config.auth;
import lombok.RequiredArgsConstructor;
import org.example.springboot.config.auth.dto.SessionUser;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import javax.servlet.http.HttpSession;
@RequiredArgsConstructor
@Component
public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {
private final HttpSession httpSession;
@Override
public boolean supportsParameter(MethodParameter parameter) {
boolean isLoginUserAnnotation = parameter.getParameterAnnotation(LoginUser.class) != null;
boolean isUserClass = SessionUser.class.equals(parameter.getParameterType());
return isLoginUserAnnotation && isUserClass;
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
return httpSession.getAttribute("user");
}
}
- supportsParameter(): @LoginUser가 붙어 있고 파라미터 클래스 타입이 SessionUser.class이면 true를 반환
- resolveArgument(): 파라미터에 전달한 객체 생성
@LoginUser를 사용하기 위한 환경은 구성했으니 스프링에서 인식될 수 있도록 WebConfig에 추가한다.
package org.example.springboot.config;
import lombok.RequiredArgsConstructor;
import org.example.springboot.config.auth.LoginUserArgumentResolver;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
@RequiredArgsConstructor
@Configuration
public class WebConfig implements WebMvcConfigurer {
private final LoginUserArgumentResolver loginUserArgumentResolver;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(loginUserArgumentResolver);
}
}
이제 @LoginUser를 이용해서 세션 정보를 가져올 수 있다.
세션 저장소로 데이터베이스 사용하기
현업에서 세션을 저장할 때 다음 3가지 중 한 가지를 선택한다.
1) 톰켓 세션을 사용: 기본 설정 값 / WAS에 세션이 저장되므로 2대 이상의 WAS 구동 시 세션 공유 필요
2) MySQL 등의 데이터베이스 사용: 공용 세션을 사용하는 가장 쉬운 방법 / DB IO로 인해 성능 이슈 발생
3) Redis 등의 메모리 DB 사용: B2C 서비스에서 많이 사용 / 외부 메모리 서버 필요
실습에서는 가장 쉬운 방법인 2번째 방법을 시도해볼 것이다.
우선 spring-session-jdbc를 등록한다.
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.projectlombok:lombok')
compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile('com.h2database:h2')
compile('org.springframework.boot:spring-boot-starter-oauth2-client')
compile('org.springframework.session:spring-session-jdbc')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
그리고 application.properties에 세션 저장소를 jdbc로 설정한다.
spring.jpa.show_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.h2.console.enabled=true
spring.profiles.include=oauth
spring.session.store-type=jdbc
네이버 로그인
네이버 오픈 API에서 소셜로그인을 위한 키를 발급받는다.
application-oauth.properties에 발급받은 키를 등록해준다.
spring.security.oauth2.client.registration.google.client-id=구글클라이언트ID
spring.security.oauth2.client.registration.google.client-secret=구글클라이언트시크릿
spring.security.oauth2.client.registration.google.scope=profile,email
# registration
spring.security.oauth2.client.registration.naver.client-id=네이버클라이언트ID
spring.security.oauth2.client.registration.naver.client-secret=네이버클라이언트시크릿
spring.security.oauth2.client.registration.naver.redirect-uri={baseUrl}/{action}/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.naver.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.naver.scope=name,email,profile_image
spring.security.oauth2.client.registration.naver.client-name=Naver
# provider
spring.security.oauth2.client.provider.naver.authorization-uri=https://nid.naver.com/oauth2.0/authorize
spring.security.oauth2.client.provider.naver.token-uri=https://nid.naver.com/oauth2.0/token
spring.security.oauth2.client.provider.naver.user-info-uri=https://openapi.naver.com/v1/nid/me
spring.security.oauth2.client.provider.naver.user-name-attribute=response
마지막으로 OAuthAttributes에 네이버인지 판단하는 코드와 네이버 생성자를 추가해주면 된다.
package org.example.springboot.config.auth.dto;
import lombok.Builder;
import lombok.Getter;
import org.example.springboot.domain.user.Role;
import org.example.springboot.domain.user.User;
import java.util.Map;
@Getter
public class OAuthAttributes {
private Map<String, Object> attributes;
private String nameAttributeKey;
private String name;
private String email;
private String picture;
@Builder
public OAuthAttributes(Map<String, Object> attributes, String nameAttributeKey,
String name, String email, String picture) {
this.attributes = attributes;
this.nameAttributeKey = nameAttributeKey;
this.name = name;
this.email = email;
this.picture = picture;
}
public static OAuthAttributes of(String registrationId, String userNameAttributeName,
Map<String, Object> attributes) {
if("naver".equals(registrationId)) {
return ofNaver("id", attributes);
}
return ofGoogle(userNameAttributeName, attributes);
}
private static OAuthAttributes ofGoogle(String userNameAttributeName,
Map<String, Object> attributes) {
return OAuthAttributes.builder()
.name((String) attributes.get("name"))
.email((String) attributes.get("email"))
.picture((String) attributes.get("picture"))
.attributes(attributes)
.nameAttributeKey(userNameAttributeName)
.build();
}
private static OAuthAttributes ofNaver(String userNameAttributeName, Map<String, Object> attributes) {
Map<String, Object> response = (Map<String, Object>) attributes.get("response");
return OAuthAttributes.builder()
.name((String) response.get("name"))
.email((String) response.get("email"))
.picture((String) response.get("profile_image"))
.attributes(response)
.nameAttributeKey(userNameAttributeName)
.build();
}
public User toEntity() {
return User.builder()
.name(name)
.email(email)
.picture(picture)
.role(Role.GUEST)
.build();
}
}
기존 테스트에 시큐리티 적용하기
전체 테스트를 실행하면 모두 실패하는 것을 볼 수 있다.
문제 1. CustomOAuth2UserService를 찾을 수 없음
main에서 application-oauth.properties에 설정값을 추가했지만 test 환경에서는 적용되지 않는다.
test에 application.properties가 없으면 main에 있는 같은 이름의 파일을 사용하기 때문에 oauth 설정은 사용되지 않는다.
그러므로 test 환경에서 사용할 application.properties을 만들어야한다.
spring.jpa.show_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.h2.console.enabled=true
spring.session.store-type=jdbc
spring.security.oauth2.client.registration.google.client-id=test
spring.security.oauth2.client.registration.google.client-secret=test
spring.security.oauth2.client.registration.google.scope=profile,email
문제 2. 302 Status Code
스프링 시큐리티 설정을 통해 인증되지 않은 사용자의 요청은 리다이렉트 시킨다.
임의로 인증된 사용자를 추가해서 API 테스트를 할 수 있도록 해야한다.
build.gradle에 spring-security-test를 추가한다.
testCompile('org.springframework.security:spring-security-test')
@WithMockUser(roles="User)를 이용해 임의 사용자 인증을 추가한다.
하지만 이것은 MockMvc에서만 작동하기 때문에 기존 Test가 MockMvc를 사용하도록 설정한다.
package org.example.springboot.web;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.example.springboot.domain.posts.Posts;
import org.example.springboot.domain.posts.PostsRepository;
import org.example.springboot.web.dto.PostsSaveRequestDto;
import org.example.springboot.web.dto.PostsUpdateRequestDto;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class PostsApiControllerTest {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private PostsRepository postsRepository;
@Autowired
private WebApplicationContext context;
private MockMvc mvc;
@Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
@After
public void tearDown() throws Exception {
postsRepository.deleteAll();
}
@Test
@WithMockUser(roles="USER")
public void Posts_등록된다() throws Exception {
//given
String title = "title";
String content = "content";
PostsSaveRequestDto requestDto = PostsSaveRequestDto.builder()
.title(title)
.content(content)
.author("author")
.build();
String url = "http://localhost:" + port + "/api/v1/posts";
//when
mvc.perform(post(url)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(new ObjectMapper().writeValueAsString(requestDto)))
.andExpect(status().isOk());
//then
List<Posts> all = postsRepository.findAll();
assertThat(all.get(0).getTitle()).isEqualTo(title);
assertThat(all.get(0).getContent()).isEqualTo(content);
}
@Test
@WithMockUser(roles="USER")
public void Posts_수정된다() throws Exception {
//given
Posts savedPosts = postsRepository.save(Posts.builder()
.title("title")
.content("content")
.author("author")
.build());
Long updateId = savedPosts.getId();
String expectedTitle = "title2";
String expectedContent = "content2";
PostsUpdateRequestDto requestDto = PostsUpdateRequestDto.builder()
.title(expectedTitle)
.content(expectedContent)
.build();
String url = "http://localhost:" + port + "/api/v1/posts/" + updateId;
//when
mvc.perform(put(url)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(new ObjectMapper().writeValueAsString(requestDto)))
.andExpect(status().isOk());
//then
List<Posts> all = postsRepository.findAll();
assertThat(all.get(0).getTitle()).isEqualTo(expectedTitle);
assertThat(all.get(0).getContent()).isEqualTo(expectedContent);
}
}
문제 3. @WebMvcTest에서 CustomOAuth2UserService를 찾을 수 없음
@WebMvcTest는 CustomOAuth2UserService를 스캔하지 않고 SecurityConfig만 스캔한다. 그러면 SecurityConfig에서 사용중인 CustomOAuth2UserService를 찾을 수 없어서 테스트에 실패하게 된다. 그러므로 SecurityConfig를 스캔하지 않도록 설정한다.
@WebMvcTest(controllers = HelloController.class, excludeFilters = {
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = SecurityConfig.class)
})
그리고 @WithMockUser를 사용해 임의 사용자 인증을 추가한다.
그 후에는 @EnableJpaAuditing을 사용으로 인한 오류를 해결해야한다. 이것을 사용하기 위해서 최소 하나의 엔터티가 필요한데 WebMvcTest에서는 엔터티가 없기 때문이다. @EnableJpaAuditing을 분리해서 문제를 해결한다.
Application.java에서 @EnableJpaAuditing을 제거하고 config 패키지에 JpaConfig 파일을 새로 만든다.
package org.example.springboot.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@Configuration
@EnableJpaAuditing
public class JpaConfig {
}
앞으로 할 일
이제 AWS에서 EC2 환경과 RDS 환경을 구성하면 된다.
원래는 같은 스터디 주차에 발표할 내용이지만 분량이 긴 관계로 다음 포스팅에 이어서 작성하겠다.
'Spring' 카테고리의 다른 글
[Spring] 스프링부트와 AWS 스터디 - 3주차 (1) | 2023.01.03 |
---|---|
[Spring] 스프링 부트와 AWS 스터디 - 2.5주차 (0) | 2022.12.02 |
[Spring] 스프링 부트와 AWS 스터디 - 1주차 (0) | 2022.11.19 |
[Spring] 스프링 입문 - 후기 (0) | 2022.11.05 |
[Spring] 스프링 입문 - 섹션 6. 스프링 DB 접근 기술 (0) | 2022.11.05 |