1. 오늘 학습 키워드
- OAuth 개념
- 카카오 로그인 API 연동 및 추가 권한(이메일) 설정
- 테스트 코드의 종류 (단위 테스트 & 통합 테스트)
- Controller 테스트 방법
- Spring AOP란 무엇인가?
2. 오늘 학습한 내용 정리
- 나만의 언어 정의: 사용자가 번거로운 회원가입 절차(아이디, 비밀번호 입력) 없이, 이미 가입된 대형 플랫폼(Kakao, Naver, Google 등)의 인증을 빌려와서 간편하게 로그인할 수 있게 해주는 기술.
- 핵심 동작 원리 & 흐름:
- 앱 생성 및 키 발급: Kakao Developers 같은 플랫폼에서 애플리케이션을 생성하고 고유의 Client Key들을 발급받는다.
- 인가 코드(Authorization Code) 수신: 사용자가 카카오 로그인을 클릭하면 플랫폼(Kakao)이 인증 후 설정된 Redirect URI로 인가 코드를 보내준다.
- 토큰 교환 및 정보 조회: 이 인가 코드를 카카오 서버에 보내 최종적으로 카카오 액세스 토큰(Access Token)을 발급받고, 이를 통해 사용자의 프로필 정보(kakaoId, nickname, email)를 안전하게 가져온다.
- 우리 서비스만의 로그인 유지: 조회한 정보로 회원가입/로그인 처리가 끝나면, 우리 서버 자체의 JWT를 발급하여 쿠키나 헤더에 저장하고 로그인 상태를 유지한다.
★ 중요 짚고 넘어가기: 카카오 토큰 vs 우리 서비스 JWT
- 카카오 액세스 토큰: 카카오가 제공하는 API(사용자 정보 가져오기 등)를 호출하기 위해 카카오 측에 보여주는 통행증.
- 우리 서비스 JWT: 사용자가 로그인한 후, '우리 서버' 안에서 인증을 유지하고 권한을 확인하기 위해 사용하는 자체 통행증. 둘은 목적과 사용처가 완전히 다르다!
- 단위 테스트 (Unit Test):
- 나만의 언어 정의: 하나의 모듈이나 클래스에 대해 세밀한 부분까지 테스트가 가능하지만 모듈 간 상호 작용을 검증할 순 없다.
- 특징: 외부 의존성(DB, 외부 API 등)을 완전히 차단하기 위해 Mock(가짜 객체)을 활용한다. (예: Repository를 Mock으로 만들어 주입). 주로 Given(준비) - When(실행) - Then(검증) 순서로 테스트 스크립트를 작성하며, 속도가 매우 빠르다.
- 통합 테스트 (Integration Test):
- 나만의 언어 정의: 두 개 이상의 모듈이 연결된 상태를 테스트할 수 있고, 모듈 간의 연결에서 발생하는 에러 검증이 가능하다.
- 특징: Spring IoC/DI 기능을 활용하여 실제 Repository와 DB를 다 연결한다. DB에 진짜 데이터도 넣어보고, API 호출도 날려보면서 모듈 간의 상호작용이 제대로 이루어지는지 광범위하게 검사한다. 진짜 환경과 유사하지만 단위 테스트보다 무겁고 시간이 걸린다.
Controller 테스트 방법 (@WebMvcTest)
- 개념: Controller 레이어는 웹 요청과 응답을 담당하므로, 전체 스프링 컨텍스트를 띄우지 않고 웹 관련 컴포넌트만 레이어별로 분리해서 테스트하기 위해 @WebMvcTest를 사용한다.
- Spring Security 관련 에러 우회 및 설정:
- 프로젝트에 WebSecurity가 적용되어 있으면, 컨트롤러 테스트 시 인증 필터 때문에 테스트가 막히거나 무조건 401/403 에러가 터진다.
- 이를 해결하기 위해 실제 Security 환경 구성 파일(WebSecurityConfig)을 컴포넌트 스캔 대상에서 제외(excludeFilters)하고, 가짜 가상 시큐리티 필터(MockSpringSecurityFilter)를 적용한 MockMvc 환경을 구축하여 테스트를 진행한다.
@WebMvcTest(
controllers = {UserController.class, ProductController.class},
excludeFilters = {
@ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
classes = WebSecurityConfig.class
)
}
)
@BeforeEach
public void setup() {
mvc = MockMvcBuilders.webAppContextSetup(context)
.apply(springSecurity(new MockSpringSecurityFilter()))
.build();
}
Spring AOP이란? 핵심 기능에 부가기능을 더해주기 위한 기술을 의미함.
- 어드바이스: 부가기능을 통해 핵심 기능을 언제 수행할 것인지 정하는 것
- @Around(핵심 기능 수행 전, 후), @Before(핵심 기능 호출 전), @After(언제나 동작), @AfterReturning(핵심기능 호출 성공 시), @AfterThrowing(핵심 기능 호출 실패 시. 즉, 예외가 발생한 경우만 동작)
- 포인트컷: 부가기능을 만들고, 어디에 수행할지 정하는 것
- @Around("execution(public * com.sparta.myselectshop.controller..*(..))")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable { ... }
- @Around("execution(public * com.sparta.myselectshop.controller..*(..))")
@Slf4j(topic = "UseTimeAop") // 로그찍기위함 @SlF4j
@Aspect
@Component //Bean
@RequiredArgsConstructor
public class UseTimeAop {
private final ApiUseTimeRepository apiUseTimeRepository;
// ProductController 전부 * 이면서 모든 반환 타입(..)
@Pointcut("execution(* com.sparta.myselectshop.controller.ProductController.*(..))")
private void product() {}
3. 학습하며 겪었던 문제점 & 에러
- 상황: 카카오 로그인 연동 중, 이전 버전의 강의를 참고하여 구현하다 보니 카카오 측으로부터 사용자 이메일 정보(추가 권한)를 받아오지 못하는 문제가 발생함.
- 결과: 이메일이 없어서 소셜 로그인과 기존 일반 회원가입 계정을 연동하지 못하고, 이메일을 제외한 채 반쪽짜리 코드로만 구현해 둔 상태였음.
🛠️ 내가 한 시도
- 최신 버전의 강의 영상을 다시 찾아보며 카카오 개발자 센터(Kakao Developers)의 설정 및 동의 항목을 검토함.
- 카카오 로그인 시 필수/선택 동의 항목에서 이메일(email) 추가 권한을 받아오도록 설정을 변경함.
✅ 해결 방법 (상세 회원가입 및 계정 통합 흐름)
- 기존 카카오 회원인지 확인: DB에서 kakaoId가 이미 존재하는지 먼저 조회한다. 존재한다면 이미 가입된 회원이므로 별도의 처리 없이 로그인 절차(우리 서비스 JWT 발급)로 넘어간다.
- 이메일 대조를 통한 계정 통합: kakaoId가 없다면, 카카오 이메일과 동일한 이메일을 가진 기존 자체 회원이 있는지 확인한다(sameEmailUser != null). 동일한 이메일 유저가 있다면 기존 계정에 카카오 ID를 업데이트(kakaoIdUpdate)하여 하나의 계정으로 연동한다.
- 신규 소셜 회원가입: 같은 이메일도 없다면 완전히 새로운 회원으로 판단하여 신규 가입을 진행한다.
Java
if (kakaoUser == null) {
// 카카오 사용자 email 동일한 email 가진 회원이 있는지 확인
String kakaoEmail = kakaoUserInfo.getEmail();
User sameEmailUser = userRepository.findByEmail(kakaoEmail).orElse(null);
if (sameEmailUser != null) {
kakaoUser = sameEmailUser;
// 기존 회원정보에 카카오 Id 추가 (계정 통합)
kakaoUser = kakaoUser.kakaoIdUpdate(kakaoId);
} else {
// 신규 회원가입
// password: random UUID
String password = UUID.randomUUID().toString();
String encodedPassword = passwordEncoder.encode(password);
// email: kakao email
String email = kakaoUserInfo.getEmail();
kakaoUser = new User(kakaoUserInfo.getNickname(), encodedPassword, email, UserRoleEnum.USER, kakaoId);
}
userRepository.save(kakaoUser);
}
여기서 잠깐! 신규 가입 시 'UUID 비밀번호'를 사용하는 이유
소셜 로그인 사용자는 일반 폼 로그인처럼 직접 비밀번호를 입력하고 가입하지 않는다. 하지만 우리 DB 구조(User 테이블)상 password 컬럼은 필수값이므로, 임의의 문자열인 랜덤 UUID를 생성하고 암호화해서 저장해둔다.
이렇게 하면 소셜 가입 사용자가 일반 로그인 화면에서 아이디와 무작위 비밀번호를 입력해 억지로 로그인하려는 시도를 원천 차단하는 보안 효과도 얻을 수 있다.
- 소셜 로그인 도입 시 기존 회원들과의 데이터 정합성(이메일 대조를 통한 중복 방지 및 계정 연동)을 어떻게 처리해야 하는지 구체적인 비즈니스 로직 설계 방법을 배움.
- 우리 서비스의 보안과 DB 구조 유지를 위해 소셜 로그인 유저에게 무작위 UUID 패스워드를 부여하는 방식의 필요성을 깨달음.
이 문제&에러를 다시 만나게 되었다면?
- 네이버나 구글 등 다른 소셜 로그인을 추가할 때도, 무작정 가입시키기 전에 이메일 같은 고유 식별자를 먼저 DB에서 조회하여 기존 회원과의 통합 프로세스를 먼저 설계하겠다!
새롭게 알게된 점
- 카카오로부터 정상적으로 사용자 정보(kakaoId, nickname, email)를 받아온 뒤, 다음과 같이 단계별로 데이터 정합성을 맞추도록 분기 로직을 구현함.
- OAuth란 무엇인지
- 단위 테스트 vs 통합 테스트
- // ProductController 전부 * 이면서 모든 반환 타입(..) @Pointcut("execution(* com.sparta.myselectshop.controller.ProductController.*(..))") private void product() {}
- long startTime = System.currentTimeMillis(); // 시간을 밀리초(ms, 1000분의 1초) 단위의 숫자로 반환해 주는 함수
- @Around: 수행 전과 후 둘다 결과 제공
4. 내일 학습 할 것
- MSA(마이크로서비스 아키텍처) 강의 수강
- 코드카타풀기
- TIL 작성
