diff --git a/backend/src/main/java/com/project/capstone/auth/exception/AuthException.java b/backend/src/main/java/com/project/capstone/auth/exception/AuthException.java new file mode 100644 index 0000000000..5df4f6945b --- /dev/null +++ b/backend/src/main/java/com/project/capstone/auth/exception/AuthException.java @@ -0,0 +1,10 @@ +package com.project.capstone.auth.exception; + +import com.project.capstone.common.exception.BaseException; +import com.project.capstone.common.exception.ExceptionType; + +public class AuthException extends BaseException { + public AuthException(ExceptionType exceptionType) { + super(exceptionType); + } +} diff --git a/backend/src/main/java/com/project/capstone/auth/exception/AuthExceptionType.java b/backend/src/main/java/com/project/capstone/auth/exception/AuthExceptionType.java new file mode 100644 index 0000000000..d0bf894520 --- /dev/null +++ b/backend/src/main/java/com/project/capstone/auth/exception/AuthExceptionType.java @@ -0,0 +1,39 @@ +package com.project.capstone.auth.exception; + +import com.project.capstone.common.exception.ExceptionType; +import lombok.AllArgsConstructor; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.*; + +@AllArgsConstructor +public enum AuthExceptionType implements ExceptionType { + ALREADY_EMAIL_EXIST(BAD_REQUEST, 1000, "이메일이 이미 존재합니다."), + EMAIL_NOT_FOUND(NOT_FOUND, 1001, "이메일을 찾을 수 없습니다."), + SIGNATURE_NOT_FOUND(UNAUTHORIZED, 1002, "서명을 확인하지 못했습니다"), + SIGNATURE_INVALID(UNAUTHORIZED, 1003, "서명이 올바르지 않습니다."), + MALFORMED_TOKEN(UNAUTHORIZED, 1004, "토큰의 길이 및 형식이 올바르지 않습니다"), + EXPIRED_TOKEN(UNAUTHORIZED, 1005, "이미 만료된 토큰입니다"), + UNSUPPORTED_TOKEN(UNAUTHORIZED, 1006, "지원되지 않는 토큰입니다"), + INVALID_TOKEN(UNAUTHORIZED, 1007, "토큰이 유효하지 않습니다"), + ; + + private final HttpStatus status; + private final int exceptionCode; + private final String message; + + @Override + public HttpStatus httpStatus() { + return status; + } + + @Override + public int exceptionCode() { + return exceptionCode; + } + + @Override + public String message() { + return message; + } +} diff --git a/backend/src/main/java/com/project/capstone/auth/jwt/JwtAuthenticationEntryPoint.java b/backend/src/main/java/com/project/capstone/auth/jwt/JwtAuthenticationEntryPoint.java new file mode 100644 index 0000000000..edf7fa2d19 --- /dev/null +++ b/backend/src/main/java/com/project/capstone/auth/jwt/JwtAuthenticationEntryPoint.java @@ -0,0 +1,29 @@ +package com.project.capstone.auth.jwt; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerExceptionResolver; + +import java.io.IOException; + +@Slf4j +@Component +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { + + private final HandlerExceptionResolver resolver; + + public JwtAuthenticationEntryPoint(@Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) { + this.resolver = resolver; + } + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { + resolver.resolveException(request, response, null, (Exception) request.getAttribute("exception")); + } +} diff --git a/backend/src/main/java/com/project/capstone/auth/jwt/JwtAuthenticationFilter.java b/backend/src/main/java/com/project/capstone/auth/jwt/JwtAuthenticationFilter.java index 99e9e0c140..e5e29b7330 100644 --- a/backend/src/main/java/com/project/capstone/auth/jwt/JwtAuthenticationFilter.java +++ b/backend/src/main/java/com/project/capstone/auth/jwt/JwtAuthenticationFilter.java @@ -1,6 +1,12 @@ package com.project.capstone.auth.jwt; +import com.project.capstone.auth.exception.AuthException; +import com.project.capstone.member.exception.MemberException; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.UnsupportedJwtException; +import io.jsonwebtoken.security.SignatureException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -9,11 +15,15 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; +import static com.project.capstone.auth.exception.AuthExceptionType.*; +import static com.project.capstone.member.exception.MemberExceptionType.*; + @RequiredArgsConstructor @Component @Slf4j @@ -29,8 +39,22 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse Authentication authentication = jwtProvider.createAuthentication(id); log.info(authentication.getName()); SecurityContextHolder.getContext().setAuthentication(authentication); + } catch (SecurityException e) { + request.setAttribute("exception", new AuthException(SIGNATURE_NOT_FOUND)); + } catch (SignatureException e) { + request.setAttribute("exception", new AuthException(SIGNATURE_INVALID)); + } catch (MalformedJwtException e) { + request.setAttribute("exception", new AuthException(MALFORMED_TOKEN)); + } catch (ExpiredJwtException e) { + request.setAttribute("exception", new AuthException(EXPIRED_TOKEN)); + } catch (UnsupportedJwtException e) { + request.setAttribute("exception", new AuthException(UNSUPPORTED_TOKEN)); + } catch (IllegalArgumentException e) { + request.setAttribute("exception", new AuthException(INVALID_TOKEN)); + } catch (UsernameNotFoundException e) { + request.setAttribute("exception", new MemberException(MEMBER_NOT_FOUND)); } catch (Exception e) { - log.warn(e.getMessage()); + request.setAttribute("exception", new Exception()); } filterChain.doFilter(request, response); diff --git a/backend/src/main/java/com/project/capstone/auth/service/AuthService.java b/backend/src/main/java/com/project/capstone/auth/service/AuthService.java index cd48022f04..3dcfd0cf1c 100644 --- a/backend/src/main/java/com/project/capstone/auth/service/AuthService.java +++ b/backend/src/main/java/com/project/capstone/auth/service/AuthService.java @@ -2,6 +2,7 @@ import com.project.capstone.auth.controller.dto.SignupRequest; import com.project.capstone.auth.controller.dto.TokenResponse; +import com.project.capstone.auth.exception.AuthException; import com.project.capstone.auth.jwt.JwtProvider; import com.project.capstone.member.domain.Member; import com.project.capstone.member.domain.MemberRepository; @@ -9,6 +10,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import static com.project.capstone.auth.exception.AuthExceptionType.*; + @RequiredArgsConstructor @Service public class AuthService { @@ -17,14 +20,14 @@ public class AuthService { private final JwtProvider jwtProvider; public void signup(SignupRequest request) { if (memberRepository.findMemberByEmail(request.email()).isPresent()) { - throw new RuntimeException("이메일이 이미 존재합니다."); + throw new AuthException(ALREADY_EMAIL_EXIST); } memberRepository.save(new Member(request)); } @Transactional public TokenResponse login(String email) { Member member = memberRepository.findMemberByEmail(email) - .orElseThrow(() -> new RuntimeException("이메일이 존재하지 않습니다.")); + .orElseThrow(() -> new AuthException(EMAIL_NOT_FOUND)); return new TokenResponse(generateToken(member)); } diff --git a/backend/src/main/java/com/project/capstone/config/SecurityConfig.java b/backend/src/main/java/com/project/capstone/config/SecurityConfig.java index 68bbec8aa1..42cbd9cc8e 100644 --- a/backend/src/main/java/com/project/capstone/config/SecurityConfig.java +++ b/backend/src/main/java/com/project/capstone/config/SecurityConfig.java @@ -1,5 +1,6 @@ package com.project.capstone.config; +import com.project.capstone.auth.jwt.JwtAuthenticationEntryPoint; import com.project.capstone.auth.jwt.JwtAuthenticationFilter; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; @@ -18,6 +19,7 @@ public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; + private final JwtAuthenticationEntryPoint entryPoint; @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @@ -29,7 +31,8 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .httpBasic(AbstractHttpConfigurer::disable) .httpBasic(AbstractHttpConfigurer::disable) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) + .exceptionHandling(handler -> handler.authenticationEntryPoint(entryPoint)); return http.build(); } diff --git a/backend/src/main/java/com/project/capstone/member/exception/MemberExceptionType.java b/backend/src/main/java/com/project/capstone/member/exception/MemberExceptionType.java index e5f7847053..01137b7c53 100644 --- a/backend/src/main/java/com/project/capstone/member/exception/MemberExceptionType.java +++ b/backend/src/main/java/com/project/capstone/member/exception/MemberExceptionType.java @@ -6,7 +6,6 @@ import static org.springframework.http.HttpStatus.*; - @AllArgsConstructor public enum MemberExceptionType implements ExceptionType { MEMBER_NOT_FOUND(NOT_FOUND, 201, "해당 멤버를 찾을 수 없습니다.")