From 465a1d1adf42f540fac1c4618f68c8e3def9dcd9 Mon Sep 17 00:00:00 2001 From: shinae1023 Date: Fri, 31 Jul 2026 20:08:20 +0900 Subject: [PATCH] =?UTF-8?q?[Feat]=20mau=20=EA=B0=90=EC=82=AC=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/service/AuthService.java | 21 ++++++ .../domain/auth/service/AuthServiceTest.java | 72 ++++++++++++++++++- 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/jobdri/jobdri_api/domain/auth/service/AuthService.java b/src/main/java/com/jobdri/jobdri_api/domain/auth/service/AuthService.java index 0ea72e08..32602ebb 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/auth/service/AuthService.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/auth/service/AuthService.java @@ -1,5 +1,6 @@ package com.jobdri.jobdri_api.domain.auth.service; +import com.jobdri.jobdri_api.domain.audit.service.AuditLogService; import com.jobdri.jobdri_api.domain.auth.dto.request.LoginRequest; import com.jobdri.jobdri_api.domain.auth.dto.request.LogoutRequest; import com.jobdri.jobdri_api.domain.auth.dto.request.PasswordResetConfirmationRequest; @@ -17,6 +18,7 @@ import com.jobdri.jobdri_api.global.logging.LoggingMdcKeys; import io.jsonwebtoken.Claims; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.slf4j.MDC; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.redis.core.StringRedisTemplate; @@ -30,9 +32,11 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Base64; +import java.util.Map; import java.util.concurrent.TimeUnit; @Service +@Slf4j @RequiredArgsConstructor public class AuthService { @@ -54,6 +58,7 @@ public class AuthService { private final EmailService emailService; private final AsyncEmailSender asyncEmailSender; private final StringRedisTemplate redisTemplate; + private final AuditLogService auditLogService; @Transactional public void signup(SignupRequest request) { @@ -98,6 +103,7 @@ public LoginResponse issueTokens(User user) { String refreshTokenValue = jwtUtil.createRefreshToken(user.getEmail()); saveRefreshToken(user.getId(), refreshTokenValue); + recordLoginSuccess(user); return LoginResponse.builder() .accessToken(accessToken) @@ -261,6 +267,21 @@ private void saveRefreshToken(Long userId, String refreshTokenValue) { ); } + private void recordLoginSuccess(User user) { + try { + auditLogService.record( + user, + "LOGIN_SUCCESS", + "USER", + user.getId(), + null, + Map.of("loginMethod", user.getSocialType().name()) + ); + } catch (RuntimeException e) { + log.warn("Login success audit log recording failed. userId={}", user.getId(), e); + } + } + private String getRefreshTokenKey(Long userId) { return REFRESH_TOKEN_PREFIX + userId; } diff --git a/src/test/java/com/jobdri/jobdri_api/domain/auth/service/AuthServiceTest.java b/src/test/java/com/jobdri/jobdri_api/domain/auth/service/AuthServiceTest.java index f8f71a4b..d3dd74a1 100644 --- a/src/test/java/com/jobdri/jobdri_api/domain/auth/service/AuthServiceTest.java +++ b/src/test/java/com/jobdri/jobdri_api/domain/auth/service/AuthServiceTest.java @@ -1,8 +1,11 @@ package com.jobdri.jobdri_api.domain.auth.service; +import com.jobdri.jobdri_api.domain.audit.service.AuditLogService; +import com.jobdri.jobdri_api.domain.auth.dto.request.LoginRequest; import com.jobdri.jobdri_api.domain.auth.dto.request.PasswordResetConfirmationRequest; import com.jobdri.jobdri_api.domain.auth.dto.request.PasswordResetEmailRequest; import com.jobdri.jobdri_api.domain.auth.dto.request.ReissueTokenRequest; +import com.jobdri.jobdri_api.domain.auth.dto.response.LoginResponse; import com.jobdri.jobdri_api.domain.user.entity.SocialType; import com.jobdri.jobdri_api.domain.user.entity.User; import com.jobdri.jobdri_api.domain.user.repository.UserRepository; @@ -25,12 +28,14 @@ import org.springframework.test.util.ReflectionTestUtils; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -61,6 +66,9 @@ class AuthServiceTest { @Mock private StringRedisTemplate redisTemplate; + @Mock + private AuditLogService auditLogService; + @Mock private ValueOperations valueOperations; @@ -75,7 +83,69 @@ void setUp() { jwtUtil, emailService, asyncEmailSender, - redisTemplate + redisTemplate, + auditLogService + ); + } + + @Test + @DisplayName("로컬 로그인 성공 시 refresh token 저장 후 로그인 성공 audit 로그를 남긴다") + void loginStoresRefreshTokenAndRecordsAuditLog() { + User user = localUser(1L, "local@example.com"); + when(userRepository.findByEmail("local@example.com")).thenReturn(Optional.of(user)); + when(passwordEncoder.matches("password123", "encoded-password")).thenReturn(true); + when(jwtUtil.createAccessToken(user.getEmail(), user.getId(), user.getRole())).thenReturn("access-token"); + when(jwtUtil.createRefreshToken(user.getEmail())).thenReturn("refresh-token"); + when(jwtUtil.getRefreshTokenTime()).thenReturn(604_800_000L); + when(redisTemplate.opsForValue()).thenReturn(valueOperations); + + LoginResponse response = authService.login(new LoginRequest("local@example.com", "password123")); + + assertThat(response.accessToken()).isEqualTo("access-token"); + assertThat(response.refreshToken()).isEqualTo("refresh-token"); + verify(valueOperations).set( + "RefreshToken:1", + "refresh-token", + 604_800_000L, + TimeUnit.MILLISECONDS + ); + verify(auditLogService).record( + eq(user), + eq("LOGIN_SUCCESS"), + eq("USER"), + eq(1L), + isNull(), + eq(Map.of("loginMethod", "LOCAL")) + ); + } + + @Test + @DisplayName("소셜 로그인 토큰 발급도 Google 로그인 성공 audit 로그를 남긴다") + void issueTokensRecordsGoogleLoginAuditLog() { + User user = User.createSocialUser( + "구글 사용자", + "google@example.com", + "encoded-password", + SocialType.GOOGLE, + "google-id" + ); + ReflectionTestUtils.setField(user, "id", 2L); + when(jwtUtil.createAccessToken(user.getEmail(), user.getId(), user.getRole())).thenReturn("google-access-token"); + when(jwtUtil.createRefreshToken(user.getEmail())).thenReturn("google-refresh-token"); + when(jwtUtil.getRefreshTokenTime()).thenReturn(604_800_000L); + when(redisTemplate.opsForValue()).thenReturn(valueOperations); + + LoginResponse response = authService.issueTokens(user); + + assertThat(response.accessToken()).isEqualTo("google-access-token"); + assertThat(response.refreshToken()).isEqualTo("google-refresh-token"); + verify(auditLogService).record( + eq(user), + eq("LOGIN_SUCCESS"), + eq("USER"), + eq(2L), + isNull(), + eq(Map.of("loginMethod", "GOOGLE")) ); }