diff --git a/src/main/java/igoMoney/BE/common/config/JwtAuthenticationFilter.java b/src/main/java/igoMoney/BE/common/config/JwtAuthenticationFilter.java index 1281468..7b8a247 100644 --- a/src/main/java/igoMoney/BE/common/config/JwtAuthenticationFilter.java +++ b/src/main/java/igoMoney/BE/common/config/JwtAuthenticationFilter.java @@ -1,9 +1,11 @@ package igoMoney.BE.common.config; +import com.auth0.jwt.exceptions.TokenExpiredException; import com.auth0.jwt.interfaces.Claim; import igoMoney.BE.common.jwt.JwtUtils; import igoMoney.BE.domain.User; import igoMoney.BE.repository.UserRepository; +import io.jsonwebtoken.JwtException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -42,14 +44,20 @@ protected void doFilterInternal( final String authHeader = request.getHeader("Authorization"); final String jwt; final String userEmail; + Long userId; + Map claims; if (authHeader == null ||!authHeader.startsWith("Bearer ")) { filterChain.doFilter(request, response); return; } jwt = authHeader.substring(7); - Long userId = jwtUtils.getUserIdFromToken(jwt); - Map claims = jwtUtils.getClaimsFromToken(jwt); - + try { + userId = jwtUtils.getUserIdFromToken(jwt); + claims = jwtUtils.getClaimsFromToken(jwt); + } catch (TokenExpiredException e){ + logger.warn("Token is expired and not valid anymore", e); + throw new JwtException("토큰 기한 만료"); + } if (userId != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(String.valueOf(userId)); diff --git a/src/main/java/igoMoney/BE/common/config/JwtExceptionFilter.java b/src/main/java/igoMoney/BE/common/config/JwtExceptionFilter.java new file mode 100644 index 0000000..fd92ae5 --- /dev/null +++ b/src/main/java/igoMoney/BE/common/config/JwtExceptionFilter.java @@ -0,0 +1,40 @@ +package igoMoney.BE.common.config; + +import igoMoney.BE.common.exception.ErrorResponse; +import io.jsonwebtoken.JwtException; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Component +public class JwtExceptionFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws ServletException, IOException { + try { + chain.doFilter(req, res); // go to 'JwtAuthFilter' + } catch (JwtException ex) { + setErrorResponse(HttpStatus.UNAUTHORIZED, res, ex); + } + } + + public void setErrorResponse(HttpStatus status, HttpServletResponse res, Throwable ex) throws IOException { + res.setStatus(status.value()); + res.setContentType("application/json; charset=UTF-8"); + + ErrorResponse jwtExceptionResponse = ErrorResponse.builder() + .status(HttpStatus.UNAUTHORIZED.value()) + .error(HttpStatus.UNAUTHORIZED.name()) + .code("TOKEN") + .detail(ex.getMessage()) + .build(); + jwtExceptionResponse.setDateNull(); + res.getWriter().write(jwtExceptionResponse.convertToJson()); + } +} diff --git a/src/main/java/igoMoney/BE/common/config/WebSecurityConfig.java b/src/main/java/igoMoney/BE/common/config/WebSecurityConfig.java index 63c1e1f..78157ba 100644 --- a/src/main/java/igoMoney/BE/common/config/WebSecurityConfig.java +++ b/src/main/java/igoMoney/BE/common/config/WebSecurityConfig.java @@ -20,11 +20,9 @@ public class WebSecurityConfig { private static final String[] WHITE_LIST_URL = { "/auth/**" }; -// private final CorsConfig corsConfig; private final JwtAuthenticationFilter jwtAuthFilter; private final AuthenticationProvider authenticationProvider; -// private final LogoutHandler logoutHandler; - + private final JwtExceptionFilter jwtExceptionFilter; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @@ -36,10 +34,6 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // 세션을 사용하지 않기 때문에 STATELESS로 설정 .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - -// .exceptionHandling((exception)-> exception.authenticationEntryPoint(CustomAuthenticationEntryPoint -// .accessDeniedHandler(CustomAccessDeniedHandler))) - .authorizeHttpRequests(req -> req.requestMatchers(WHITE_LIST_URL) .permitAll() @@ -48,6 +42,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { ) .authenticationProvider(authenticationProvider) .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(jwtExceptionFilter, JwtAuthenticationFilter.class) // .logout(logout -> // logout.logoutUrl("/auth/logout") // .addLogoutHandler(logoutHandler) @@ -57,37 +52,10 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { } -// private final AuthenticationEntryPoint unauthorizedEntryPoint = -// (request, response, authException) -> { -// ErrorResponse fail = ...; // Custom error response. -// response.setStatus(HttpStatus.UNAUTHORIZED.value()); -// String json = objectMapper.writeValueAsString(fail); -// response.setContentType(MediaType.APPLICATION_JSON_VALUE); -// PrintWriter writer = response.getWriter(); -// writer.write(json); -// writer.flush(); -// }; -// // PasswordEncoder는 BCryptPasswordEncoder를 사용 -// @Bean -// public PasswordEncoder passwordEncoder() { -// return new BCryptPasswordEncoder(); -// } -// -// @Bean -// public AuthenticationManager authenticationManagerBean() throws Exception { -// return authenticationConfiguration.getAuthenticationManager(); -// } -// -// @Bean -// public JwtAuthorizationFilter jwtAuthorizationFilter() throws Exception { -// return new JwtAuthorizationFilter(authenticationManagerBean(), userRepository, jwtUtils, redisTemplate); -// } // // @Bean // public JwtExceptionFilter jwtExceptionFilter() { // return new JwtExceptionFilter(); // } - - } diff --git a/src/main/java/igoMoney/BE/common/exception/ErrorResponse.java b/src/main/java/igoMoney/BE/common/exception/ErrorResponse.java index e36f3e7..ec4af43 100644 --- a/src/main/java/igoMoney/BE/common/exception/ErrorResponse.java +++ b/src/main/java/igoMoney/BE/common/exception/ErrorResponse.java @@ -1,5 +1,7 @@ package igoMoney.BE.common.exception; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Builder; import lombok.Getter; import org.springframework.http.HttpStatus; @@ -12,7 +14,7 @@ @Builder public class ErrorResponse { - private final LocalDateTime timestamp = LocalDateTime.now(); + private LocalDateTime timestamp = LocalDateTime.now(); private final int status; private final String error; private final String code; @@ -57,4 +59,11 @@ public static ResponseEntity toResponseEntity(HttpStatus httpStat .build() ); } + + public String convertToJson() throws JsonProcessingException { + ObjectMapper mapper = new ObjectMapper(); + return mapper.writeValueAsString(this); + } + + public void setDateNull(){ this.timestamp = null;} } diff --git a/src/main/java/igoMoney/BE/service/AuthService.java b/src/main/java/igoMoney/BE/service/AuthService.java index 6fab8eb..e5e7d30 100644 --- a/src/main/java/igoMoney/BE/service/AuthService.java +++ b/src/main/java/igoMoney/BE/service/AuthService.java @@ -255,13 +255,11 @@ public void kakaoSignOut(Long userId) { authHeader.forEach((s, o) -> System.out.println(s + " : " + o)); kakaoClient.signOut(authHeader, request); - // User 정보 삭제 deleteUser(userId); } // User 정보 삭제 private void deleteUser(Long userId){ -// User findUser = getUserOrThrow(userId); em.createNativeQuery("SET FOREIGN_KEY_CHECKS = 0").executeUpdate(); userRepository.deleteById(userId); em.createNativeQuery("SET FOREIGN_KEY_CHECKS = 1").executeUpdate();