Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -13,6 +14,7 @@

import java.io.IOException;

@Slf4j
@Component
@RequiredArgsConstructor
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
Expand All @@ -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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -17,6 +18,7 @@
import java.io.IOException;
import java.util.Collections;

@Slf4j
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
Expand All @@ -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");
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -38,7 +39,9 @@ public ResponseEntity<CommonResponse<UserResponse>> register(@Valid @RequestBody
}

@PostMapping("/login")
public ResponseEntity<CommonResponse<TokenResponse>> login(@Valid @RequestBody LoginRequest request) {
public ResponseEntity<CommonResponse<TokenResponse>> login(@Valid @RequestBody LoginRequest request,
HttpServletRequest httpRequest) {
httpRequest.setAttribute("auth-username", request.getUsername());
TokenResponse tokenResponse = userService.login(request);

CommonResponse<TokenResponse> response = CommonResponse.<TokenResponse>builder()
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -58,8 +59,11 @@ public ResponseEntity<ErrorResponse> handleUnauthorized(UnauthorizedException ex
}

@ExceptionHandler(BadCredentialsException.class)
public ResponseEntity<ErrorResponse> handleBadCredentials(BadCredentialsException ex) {
log.warn("Bad credentials attempt: {}", ex.getMessage());
public ResponseEntity<ErrorResponse> 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")
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -14,6 +15,7 @@
import java.time.Duration;
import java.time.LocalDateTime;

@Slf4j
@Service
@RequiredArgsConstructor
public class RefreshTokenService {
Expand All @@ -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");
}

Expand All @@ -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())
Expand All @@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -19,6 +20,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Service
@RequiredArgsConstructor
public class UserService {
Expand Down Expand Up @@ -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);
}

Expand All @@ -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())
Expand Down Expand Up @@ -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) {
Expand Down