diff --git a/src/main/java/com/acon/server/global/auth/filter/CustomJwtAuthenticationEntryPoint.java b/src/main/java/com/acon/server/global/auth/filter/CustomJwtAuthenticationEntryPoint.java index 334cb387..1dfeddd7 100644 --- a/src/main/java/com/acon/server/global/auth/filter/CustomJwtAuthenticationEntryPoint.java +++ b/src/main/java/com/acon/server/global/auth/filter/CustomJwtAuthenticationEntryPoint.java @@ -22,6 +22,6 @@ public void commence( } private void setResponse(HttpServletResponse response) { - response.setStatus(HttpServletResponse.SC_FORBIDDEN); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); } } diff --git a/src/main/java/com/acon/server/global/auth/filter/JwtAuthenticationFilter.java b/src/main/java/com/acon/server/global/auth/filter/JwtAuthenticationFilter.java index d43744d9..2a4549a0 100644 --- a/src/main/java/com/acon/server/global/auth/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/acon/server/global/auth/filter/JwtAuthenticationFilter.java @@ -3,8 +3,9 @@ import static com.acon.server.global.auth.jwt.JwtValidationType.VALID_JWT; import com.acon.server.global.auth.MemberAuthentication; +import com.acon.server.global.auth.jwt.JwtAuthenticationException; import com.acon.server.global.auth.jwt.JwtTokenProvider; -import com.acon.server.global.exception.BusinessException; +import com.acon.server.global.auth.jwt.JwtValidationType; import com.acon.server.global.exception.ErrorType; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -26,6 +27,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenProvider jwtTokenProvider; + private final CustomJwtAuthenticationEntryPoint authenticationEntryPoint; // 각 HTTP 요청에 대해 토큰이 유효한지 확인하고, 유효하다면 해당 사용자를 인증 설정하는 필터링 로직 @Override @@ -33,39 +35,61 @@ protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException { try { - final String token = getJwtFromRequest(request); + final String token = resolveToken(request); - if (jwtTokenProvider.validateToken(token) == VALID_JWT) { + if (token == null) { + filterChain.doFilter(request, response); + + return; + } + + JwtValidationType result = jwtTokenProvider.validateToken(token); + + if (result == VALID_JWT) { Long memberId = jwtTokenProvider.getMemberIdFromJwt(token); - // authentication 객체 생성 -> principal에 유저정보를 담는다. MemberAuthentication authentication = new MemberAuthentication(memberId.toString(), null, null); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); + + filterChain.doFilter(request, response); + + return; } - } catch (Exception exception) { - // SecurityConfig에서 permitAll을 적용해도, Spring Security의 필터 체인을 거치므로 - // 여기서 바로 Exception throw를 하게 되면 permitAll과 상관 없이 ExceptionTranslationFilter로 처리가 넘어간다. - // 따라서 예외를 직접 throw로 던져주는 것이 아닌, 발생시키기만 하고 다음 필터 호출로 이어지게끔 해야 하고, (doFilter) - // 이렇게 하면 API의 permitAll 적용 여부에 따라 ExceptionTranslationFilter를 거칠지 판단하게 된다. - log.error("JwtAuthentication Authentication Exception Occurs! - {}", exception.getMessage()); + + throw mapToAuthException(result); + + } catch (JwtAuthenticationException ex) { + authenticationEntryPoint.commence(request, response, ex); } - // 다음 필터로 요청 전달 (호출) - filterChain.doFilter(request, response); } - // Authorization 헤더에서 JWT 토큰을 추출 - private String getJwtFromRequest(HttpServletRequest request) { - String bearerToken = request.getHeader("Authorization"); + private String resolveToken(HttpServletRequest request) { + String bearer = request.getHeader("Authorization"); - if (!StringUtils.hasText(bearerToken)) { -// throw new BusinessException(ErrorType.UN_LOGIN_ERROR); - } else if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { - return bearerToken.substring("Bearer ".length()); - } else if (StringUtils.hasText(bearerToken) && !bearerToken.startsWith("Bearer ")) { - throw new BusinessException(ErrorType.BEARER_LOST_ERROR); + if (!StringUtils.hasText(bearer)) { + return null; } - return null; + if (!bearer.startsWith("Bearer ")) { + throw new JwtAuthenticationException(ErrorType.BEARER_LOST_ERROR); + } + + String token = bearer.substring("Bearer ".length()).trim(); + + if (!StringUtils.hasText(token)) { + throw new JwtAuthenticationException(ErrorType.INVALID_ACCESS_TOKEN_ERROR); + } + + return token; + } + + private JwtAuthenticationException mapToAuthException(JwtValidationType v) { + return switch (v) { + case EXPIRED_JWT_TOKEN -> new JwtAuthenticationException(ErrorType.EXPIRED_ACCESS_TOKEN_ERROR); + case INVALID_JWT_SIGNATURE, INVALID_JWT_TOKEN, UNSUPPORTED_JWT_TOKEN, EMPTY_JWT -> + new JwtAuthenticationException(ErrorType.INVALID_ACCESS_TOKEN_ERROR); + case VALID_JWT -> throw new IllegalStateException("VALID_JWT should not reach mapToAuthException"); + }; } } diff --git a/src/main/java/com/acon/server/global/auth/jwt/JwtAuthenticationException.java b/src/main/java/com/acon/server/global/auth/jwt/JwtAuthenticationException.java new file mode 100644 index 00000000..12852bee --- /dev/null +++ b/src/main/java/com/acon/server/global/auth/jwt/JwtAuthenticationException.java @@ -0,0 +1,21 @@ +package com.acon.server.global.auth.jwt; + +import com.acon.server.global.exception.ErrorType; +import lombok.Getter; +import org.springframework.security.core.AuthenticationException; + +@Getter +public class JwtAuthenticationException extends AuthenticationException { + + private final ErrorType errorType; + + public JwtAuthenticationException(ErrorType errorType) { + super(errorType.getMessage()); + this.errorType = errorType; + } + + public JwtAuthenticationException(ErrorType errorType, Throwable cause) { + super(errorType.getMessage(), cause); + this.errorType = errorType; + } +} diff --git a/src/main/java/com/acon/server/global/auth/security/SecurityConfig.java b/src/main/java/com/acon/server/global/auth/security/SecurityConfig.java index 481ef836..9ac206fb 100644 --- a/src/main/java/com/acon/server/global/auth/security/SecurityConfig.java +++ b/src/main/java/com/acon/server/global/auth/security/SecurityConfig.java @@ -23,8 +23,8 @@ public class SecurityConfig { private final CustomAccessDeniedHandler customAccessDeniedHandler; private static final String[] AUTH_WHITE_LIST = { - "/api/v1/**", - "/api/v1/auth/**" + "/api/v1/auth/**", + "/api/v2/auth/**" }; @Bean @@ -32,18 +32,15 @@ SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf(AbstractHttpConfigurer::disable) .formLogin(AbstractHttpConfigurer::disable) .httpBasic(AbstractHttpConfigurer::disable) - .sessionManagement(session -> { - session.sessionCreationPolicy(SessionCreationPolicy.STATELESS); - }) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .exceptionHandling(exception -> { exception.authenticationEntryPoint(customJwtAuthenticationEntryPoint); exception.accessDeniedHandler(customAccessDeniedHandler); - }); - - http.authorizeHttpRequests(auth -> { - auth.requestMatchers(AUTH_WHITE_LIST).permitAll(); - auth.anyRequest().authenticated(); }) + .authorizeHttpRequests(auth -> auth + .requestMatchers(AUTH_WHITE_LIST).permitAll() + .anyRequest().authenticated() + ) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build();