diff --git a/src/main/java/me/thinkcat/opic/practice/config/security/JwtAuthenticationEntryPoint.java b/src/main/java/me/thinkcat/opic/practice/config/security/JwtAuthenticationEntryPoint.java index acdb21d..bc5b5c6 100644 --- a/src/main/java/me/thinkcat/opic/practice/config/security/JwtAuthenticationEntryPoint.java +++ b/src/main/java/me/thinkcat/opic/practice/config/security/JwtAuthenticationEntryPoint.java @@ -5,6 +5,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import me.thinkcat.opic.practice.dto.ErrorResponse; import org.springframework.http.MediaType; import org.springframework.security.core.AuthenticationException; @@ -13,6 +14,7 @@ import java.io.IOException; +@Slf4j @Component @RequiredArgsConstructor public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { @@ -35,6 +37,8 @@ public void commence(HttpServletRequest request, default -> "Unauthorized access"; }; + log.warn("event=unauthorized | code={} | path={}", errorCode, request.getRequestURI()); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setCharacterEncoding("UTF-8"); diff --git a/src/main/java/me/thinkcat/opic/practice/config/security/JwtAuthenticationFilter.java b/src/main/java/me/thinkcat/opic/practice/config/security/JwtAuthenticationFilter.java index 1df875d..16a16ff 100644 --- a/src/main/java/me/thinkcat/opic/practice/config/security/JwtAuthenticationFilter.java +++ b/src/main/java/me/thinkcat/opic/practice/config/security/JwtAuthenticationFilter.java @@ -5,6 +5,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.User; @@ -17,6 +18,7 @@ import java.io.IOException; import java.util.Collections; +@Slf4j @Component @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { @@ -41,8 +43,14 @@ protected void doFilterInternal(HttpServletRequest request, authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } - case EXPIRED -> request.setAttribute("auth-error", "ACCESS_TOKEN_EXPIRED"); - case INVALID -> request.setAttribute("auth-error", "INVALID_TOKEN"); + case EXPIRED -> { + log.debug("event=access_token_expired | path={}", request.getRequestURI()); + request.setAttribute("auth-error", "ACCESS_TOKEN_EXPIRED"); + } + case INVALID -> { + log.warn("event=access_token_invalid | path={}", request.getRequestURI()); + request.setAttribute("auth-error", "INVALID_TOKEN"); + } } } diff --git a/src/main/java/me/thinkcat/opic/practice/config/security/SecurityConfig.java b/src/main/java/me/thinkcat/opic/practice/config/security/SecurityConfig.java index 1faa088..fbf69c7 100644 --- a/src/main/java/me/thinkcat/opic/practice/config/security/SecurityConfig.java +++ b/src/main/java/me/thinkcat/opic/practice/config/security/SecurityConfig.java @@ -28,7 +28,10 @@ public class SecurityConfig { private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; private static final String[] PUBLIC_URLS = { - "/api/v1/auth/**", + "/api/v1/auth/register", + "/api/v1/auth/login", + "/api/v1/auth/refresh", + "/api/v1/auth/logout", "/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html", diff --git a/src/main/java/me/thinkcat/opic/practice/controller/AuthController.java b/src/main/java/me/thinkcat/opic/practice/controller/AuthController.java index 9cd4542..13b08c7 100644 --- a/src/main/java/me/thinkcat/opic/practice/controller/AuthController.java +++ b/src/main/java/me/thinkcat/opic/practice/controller/AuthController.java @@ -1,5 +1,6 @@ package me.thinkcat.opic.practice.controller; +import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import me.thinkcat.opic.practice.dto.request.AccessTokenRefreshRequest; @@ -38,7 +39,9 @@ public ResponseEntity> register(@Valid @RequestBody } @PostMapping("/login") - public ResponseEntity> login(@Valid @RequestBody LoginRequest request) { + public ResponseEntity> login(@Valid @RequestBody LoginRequest request, + HttpServletRequest httpRequest) { + httpRequest.setAttribute("auth-username", request.getUsername()); TokenResponse tokenResponse = userService.login(request); CommonResponse response = CommonResponse.builder() diff --git a/src/main/java/me/thinkcat/opic/practice/exception/GlobalExceptionHandler.java b/src/main/java/me/thinkcat/opic/practice/exception/GlobalExceptionHandler.java index 19cf29a..165e3f5 100644 --- a/src/main/java/me/thinkcat/opic/practice/exception/GlobalExceptionHandler.java +++ b/src/main/java/me/thinkcat/opic/practice/exception/GlobalExceptionHandler.java @@ -1,5 +1,6 @@ package me.thinkcat.opic.practice.exception; +import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import me.thinkcat.opic.practice.dto.ErrorResponse; import org.springframework.http.HttpStatus; @@ -58,8 +59,11 @@ public ResponseEntity handleUnauthorized(UnauthorizedException ex } @ExceptionHandler(BadCredentialsException.class) - public ResponseEntity handleBadCredentials(BadCredentialsException ex) { - log.warn("Bad credentials attempt: {}", ex.getMessage()); + public ResponseEntity handleBadCredentials(BadCredentialsException ex, + HttpServletRequest request) { + String username = (String) request.getAttribute("auth-username"); + log.warn("event=login_fail | who={} | reason=bad_credentials", + username != null ? username : "unknown"); ErrorResponse errorResponse = ErrorResponse.builder() .errorCode("BAD_CREDENTIALS") .message("Invalid username or password") diff --git a/src/main/java/me/thinkcat/opic/practice/service/RefreshTokenService.java b/src/main/java/me/thinkcat/opic/practice/service/RefreshTokenService.java index 1c821c2..1a459be 100644 --- a/src/main/java/me/thinkcat/opic/practice/service/RefreshTokenService.java +++ b/src/main/java/me/thinkcat/opic/practice/service/RefreshTokenService.java @@ -1,6 +1,7 @@ package me.thinkcat.opic.practice.service; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import me.thinkcat.opic.practice.dto.response.TokenResponse; import me.thinkcat.opic.practice.entity.RefreshToken; import me.thinkcat.opic.practice.entity.User; @@ -14,6 +15,7 @@ import java.time.Duration; import java.time.LocalDateTime; +@Slf4j @Service @RequiredArgsConstructor public class RefreshTokenService { @@ -37,10 +39,15 @@ public RefreshToken createRefreshToken(User user) { @Transactional public TokenResponse refreshTokens(String refreshTokenValue) { RefreshToken refreshToken = refreshTokenRepository.findByToken(refreshTokenValue) - .orElseThrow(() -> new UnauthorizedException("Invalid refresh token")); + .orElseThrow(() -> { + log.warn("event=token_rotation_fail | reason=invalid_token"); + return new UnauthorizedException("Invalid refresh token"); + }); if (refreshToken.isExpired()) { refreshTokenRepository.delete(refreshToken); + log.warn("event=token_rotation_fail | who={} | reason=expired", + refreshToken.getUser().getUsername()); throw new TokenExpiredException("Refresh token expired"); } @@ -52,6 +59,7 @@ public TokenResponse refreshTokens(String refreshTokenValue) { String newAccessToken = jwtTokenProvider.generateAccessToken(user.getUsername(), user.getId(), user.getUserRole()); RefreshToken newRefreshToken = createRefreshToken(user); + log.info("event=token_rotation_success | who={}", user.getUsername()); return TokenResponse.builder() .accessToken(newAccessToken) .refreshToken(newRefreshToken.getToken()) @@ -63,11 +71,15 @@ public TokenResponse refreshTokens(String refreshTokenValue) { @Transactional public void revokeRefreshToken(String refreshTokenValue) { refreshTokenRepository.findByToken(refreshTokenValue) - .ifPresent(refreshTokenRepository::delete); + .ifPresent(token -> { + log.info("event=token_revoke | who={}", token.getUser().getUsername()); + refreshTokenRepository.delete(token); + }); } @Transactional public void revokeAllByUser(User user) { + log.info("event=token_revoke_all | who={}", user.getUsername()); refreshTokenRepository.deleteByUserId(user.getId()); } } diff --git a/src/main/java/me/thinkcat/opic/practice/service/UserService.java b/src/main/java/me/thinkcat/opic/practice/service/UserService.java index b17651d..a27d088 100644 --- a/src/main/java/me/thinkcat/opic/practice/service/UserService.java +++ b/src/main/java/me/thinkcat/opic/practice/service/UserService.java @@ -1,6 +1,7 @@ package me.thinkcat.opic.practice.service; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import me.thinkcat.opic.practice.dto.mapper.UserMapper; import me.thinkcat.opic.practice.dto.request.LoginRequest; import me.thinkcat.opic.practice.dto.request.UserRegisterRequest; @@ -19,6 +20,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +@Slf4j @Service @RequiredArgsConstructor public class UserService { @@ -48,6 +50,7 @@ public UserResponse register(UserRegisterRequest request) { User savedUser = userRepository.save(user); + log.info("event=register | who={}", request.getUsername()); return UserMapper.toResponse(savedUser); } @@ -63,6 +66,7 @@ public TokenResponse login(LoginRequest request) { String accessToken = jwtTokenProvider.generateAccessToken(user.getUsername(), user.getId(), user.getUserRole()); RefreshToken refreshToken = refreshTokenService.createRefreshToken(user); + log.info("event=login_success | who={}", request.getUsername()); return TokenResponse.builder() .accessToken(accessToken) .refreshToken(refreshToken.getToken()) @@ -97,6 +101,7 @@ public void withdraw(String username) { refreshTokenService.revokeAllByUser(user); user.softDelete(); userRepository.save(user); + log.warn("event=withdraw | who={}", username); } private void validatePassword(String password) {