diff --git a/src/main/java/sopt/org/HMH/HealthCheckApiController.java b/src/main/java/sopt/org/HMH/HealthCheckApiController.java index 84dd34c0..1fa2dc97 100644 --- a/src/main/java/sopt/org/HMH/HealthCheckApiController.java +++ b/src/main/java/sopt/org/HMH/HealthCheckApiController.java @@ -3,7 +3,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -@RequestMapping("/") +@RequestMapping("/health") @RestController public class HealthCheckApiController { @GetMapping diff --git a/src/main/java/sopt/org/HMH/HmhApplication.java b/src/main/java/sopt/org/HMH/HmhApplication.java index 69ef6139..dfdd9036 100644 --- a/src/main/java/sopt/org/HMH/HmhApplication.java +++ b/src/main/java/sopt/org/HMH/HmhApplication.java @@ -2,9 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration; +import org.springframework.cloud.openfeign.EnableFeignClients; -@SpringBootApplication(exclude = UserDetailsServiceAutoConfiguration.class) +@SpringBootApplication +@EnableFeignClients public class HmhApplication { public static void main(String[] args) { diff --git a/src/main/java/sopt/org/HMH/domain/user/controller/UserController.java b/src/main/java/sopt/org/HMH/domain/user/controller/UserController.java index 7a56db47..1195d68d 100644 --- a/src/main/java/sopt/org/HMH/domain/user/controller/UserController.java +++ b/src/main/java/sopt/org/HMH/domain/user/controller/UserController.java @@ -1,19 +1,19 @@ package sopt.org.HMH.domain.user.controller; +import java.security.Principal; import lombok.RequiredArgsConstructor; - -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.security.Principal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RestController; import sopt.org.HMH.domain.user.domain.exception.UserSuccess; import sopt.org.HMH.domain.user.dto.request.SocialLoginRequestDto; import sopt.org.HMH.domain.user.dto.response.LoginResponseDto; import sopt.org.HMH.domain.user.service.UserService; import sopt.org.HMH.global.auth.jwt.JwtProvider; import sopt.org.HMH.global.auth.jwt.TokenDto; -import sopt.org.HMH.global.auth.social.kakao.fegin.KakaoLoginService; import sopt.org.HMH.global.common.response.ApiResponse; @RestController @@ -21,13 +21,12 @@ public class UserController { private final UserService userService; - private final KakaoLoginService kakaoLoginService; @PostMapping("/login") public ResponseEntity> login( @RequestHeader("Authorization") String socialAccessToken, @RequestBody SocialLoginRequestDto request - ){ + ) { return ResponseEntity .status(UserSuccess.LOGIN_SUCCESS.getHttpStatus()) .body(ApiResponse.success(UserSuccess.LOGIN_SUCCESS, userService.login(socialAccessToken, request))); @@ -36,18 +35,18 @@ public ResponseEntity> login( @GetMapping("/reissue") public ResponseEntity> reissue( @RequestHeader("Authorization") String refreshToken - ){ + ) { return ResponseEntity .status(UserSuccess.REISSUE_SUCCESS.getHttpStatus()) .body(ApiResponse.success(UserSuccess.REISSUE_SUCCESS, userService.reissueToken(refreshToken))); } @PostMapping("/log-out") - @ResponseStatus(HttpStatus.OK) - public ApiResponse logout(Principal principal) { - + public ResponseEntity> logout(Principal principal) { userService.logout(JwtProvider.getUserFromPrincipal(principal)); - return ApiResponse.success(UserSuccess.LOGOUT_SUCCESS); + return ResponseEntity + .status(UserSuccess.LOGOUT_SUCCESS.getHttpStatus()) + .body(ApiResponse.success(UserSuccess.LOGOUT_SUCCESS)); } } \ No newline at end of file diff --git a/src/main/java/sopt/org/HMH/domain/user/service/UserService.java b/src/main/java/sopt/org/HMH/domain/user/service/UserService.java index 5dd2dce0..e92b826c 100644 --- a/src/main/java/sopt/org/HMH/domain/user/service/UserService.java +++ b/src/main/java/sopt/org/HMH/domain/user/service/UserService.java @@ -28,12 +28,9 @@ public class UserService { @Transactional public LoginResponseDto login(String socialAccessToken, SocialLoginRequestDto request) { - socialAccessToken = parseTokenString(socialAccessToken); - SocialPlatform socialPlatform = request.getSocialPlatform(); Long socialId = getUserIdBySocialAccessToken(socialPlatform, socialAccessToken); - // 유저를 찾지 못하면 404 Error를 던져 클라이언트에게 회원가입 api를 요구한다. User loginUser = getUserBySocialAndSocialId(socialPlatform, socialId); diff --git a/src/main/java/sopt/org/HMH/global/auth/jwt/JwtProvider.java b/src/main/java/sopt/org/HMH/global/auth/jwt/JwtProvider.java index 665c9755..c2d808ad 100644 --- a/src/main/java/sopt/org/HMH/global/auth/jwt/JwtProvider.java +++ b/src/main/java/sopt/org/HMH/global/auth/jwt/JwtProvider.java @@ -136,7 +136,7 @@ public void deleteRefreshToken(Long userId) { if (tokenRepository.existsById(userId)) { tokenRepository.deleteById(userId); } else { - throw new JwtException(JwtError.NOT_FOUND_REFRESH_TOKEN_ERROR); + throw new JwtException(JwtError.INVALID_REFRESH_TOKEN); } } @@ -164,10 +164,8 @@ private Claims getClaim(final String token) { * JWT의 서명을 위한 Secret Key를 가져오는 함수 */ private SecretKey getSigningKey() { - // 시크릿 키 문자열을 바이트 배열로 변환 & Base64로 인코딩 String encodedKey = Base64.getEncoder().encodeToString(JWT_SECRET.getBytes()); - // HMAC SHA 알고리즘을 사용하는 Secret Key 생성 return Keys.hmacShaKeyFor(encodedKey.getBytes()); } diff --git a/src/main/java/sopt/org/HMH/global/auth/jwt/exception/JwtError.java b/src/main/java/sopt/org/HMH/global/auth/jwt/exception/JwtError.java index e328e354..385cab04 100644 --- a/src/main/java/sopt/org/HMH/global/auth/jwt/exception/JwtError.java +++ b/src/main/java/sopt/org/HMH/global/auth/jwt/exception/JwtError.java @@ -6,15 +6,17 @@ @AllArgsConstructor public enum JwtError implements ErrorBase { + // 400 BAD REQUEST EMPTY_PRINCIPLE_EXCEPTION(HttpStatus.BAD_REQUEST, "Principle 객체가 없습니다."), INVALID_TOKEN_HEADER(HttpStatus.BAD_REQUEST, "토큰 헤더 값의 형식이 잘못되었습니다."), + // 401 UNAUTHORIZED INVALID_SOCIAL_ACCESS_TOKEN(HttpStatus.UNAUTHORIZED, "유효하지 않은 소셜 엑세스 토큰입니다."), INVALID_ACCESS_TOKEN(HttpStatus.UNAUTHORIZED, "유효하지 않은 액세스 토큰입니다. 액세스 토큰을 재발급 받아주세요."), + INVALID_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "유효하지 않은 리프레시 토큰입니다. 다시 로그인 해주세요."), // 403 FORBIDDEN - INVALID_REFRESH_TOKEN(HttpStatus.FORBIDDEN, "유효하지 않은 리프레시 토큰입니다. 다시 로그인 해주세요."), // 404 NOT FOUND NOT_FOUND_REFRESH_TOKEN_ERROR(HttpStatus.NOT_FOUND, "존재하지 않는 리프레시 토큰입니다.") diff --git a/src/main/java/sopt/org/HMH/global/auth/security/CustomJwtAuthenticationEntryPoint.java b/src/main/java/sopt/org/HMH/global/auth/security/CustomJwtAuthenticationEntryPoint.java index 462c04f0..fc14f333 100644 --- a/src/main/java/sopt/org/HMH/global/auth/security/CustomJwtAuthenticationEntryPoint.java +++ b/src/main/java/sopt/org/HMH/global/auth/security/CustomJwtAuthenticationEntryPoint.java @@ -29,9 +29,9 @@ public void commence(HttpServletRequest request, HttpServletResponse response, A private void setResponse(HttpServletResponse response) throws IOException { response.setCharacterEncoding("UTF-8"); response.setContentType(MediaType.APPLICATION_JSON_VALUE); - response.setStatus(HttpServletResponse.SC_FORBIDDEN); // 응답의 상태코드를 403으로 정의 + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 응답의 상태코드를 401으로 정의 - // 응답 상태코드 403으로 정의 + // 응답 상태코드 401으로 정의 response.getWriter().println(objectMapper.writeValueAsString(ApiResponse.error(JwtError.INVALID_ACCESS_TOKEN))); } } diff --git a/src/main/java/sopt/org/HMH/global/auth/security/JwtAuthenticationFilter.java b/src/main/java/sopt/org/HMH/global/auth/security/JwtAuthenticationFilter.java index f6d6414c..6c3636c6 100644 --- a/src/main/java/sopt/org/HMH/global/auth/security/JwtAuthenticationFilter.java +++ b/src/main/java/sopt/org/HMH/global/auth/security/JwtAuthenticationFilter.java @@ -35,16 +35,12 @@ protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull Ht // 추출한 토큰이 존재하고, 토큰이 유효한 경우 if (StringUtils.hasText(token) && jwtProvider.validateAccessToken(token) == JwtValidationType.VALID_JWT) { - // JWT 토큰에서 사용자 정보 추출 Long userId = jwtProvider.getUserFromJwt(token); - // 사용자 정보로 Spring Security의 사용자 인증 객체 생성 UserAuthentication authentication = new UserAuthentication(userId, null, null); - // 사용자 인증 객체에 요청의 세부 정보 추가 authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - // Spring Security의 SecurityContextHolder에 사용자 인증 객체 설정 SecurityContextHolder.getContext().setAuthentication(authentication); } diff --git a/src/main/java/sopt/org/HMH/global/auth/social/kakao/fegin/KakaoLoginService.java b/src/main/java/sopt/org/HMH/global/auth/social/kakao/fegin/KakaoLoginService.java index 2025e044..55fff612 100644 --- a/src/main/java/sopt/org/HMH/global/auth/social/kakao/fegin/KakaoLoginService.java +++ b/src/main/java/sopt/org/HMH/global/auth/social/kakao/fegin/KakaoLoginService.java @@ -1,6 +1,7 @@ package sopt.org.HMH.global.auth.social.kakao.fegin; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import sopt.org.HMH.domain.user.domain.User; @@ -11,8 +12,8 @@ @RequiredArgsConstructor public class KakaoLoginService { - private KakaoApiClient kakaoApiClient; - private static final String TOKEN_TYPE = "Bearer: "; + private final KakaoApiClient kakaoApiClient; + private static final String TOKEN_TYPE = "Bearer "; /** * 카카오 Acess Token으로 유저 Id 불러오는 함수 @@ -20,7 +21,7 @@ public class KakaoLoginService { public Long getUserIdByKakao(String socialAccessToken) { KakaoUserResponse userResponse = kakaoApiClient.getUserInformation(TOKEN_TYPE + socialAccessToken); - + System.out.println("userResponse : " + userResponse); return userResponse.getId(); } @@ -28,9 +29,7 @@ public Long getUserIdByKakao(String socialAccessToken) { * 카카오 Access Token으로 유저 정보 업데이트 */ public void updateUserInfoByKakao(User loginUser, String socialAccessToken) { - KakaoUserResponse userResponse = kakaoApiClient.getUserInformation(TOKEN_TYPE + socialAccessToken); - loginUser.updateSocialInfo(userResponse.getKakaoAccount().getProfile().getNickname(), userResponse.getKakaoAccount().getProfile().getProfileImageUrl()); } diff --git a/src/main/java/sopt/org/HMH/global/config/FeignClientConfig.java b/src/main/java/sopt/org/HMH/global/config/FeignClientConfig.java deleted file mode 100644 index 6444fa90..00000000 --- a/src/main/java/sopt/org/HMH/global/config/FeignClientConfig.java +++ /dev/null @@ -1,9 +0,0 @@ -package sopt.org.HMH.global.config; - -import org.springframework.cloud.openfeign.EnableFeignClients; -import org.springframework.context.annotation.Configuration; - -@Configuration -@EnableFeignClients -public class FeignClientConfig { -} diff --git a/src/main/java/sopt/org/HMH/global/config/RedisConfig.java b/src/main/java/sopt/org/HMH/global/config/RedisConfig.java index d2a92e12..1a4f61fc 100644 --- a/src/main/java/sopt/org/HMH/global/config/RedisConfig.java +++ b/src/main/java/sopt/org/HMH/global/config/RedisConfig.java @@ -7,8 +7,8 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; -@EnableRedisRepositories @Configuration +@EnableRedisRepositories public class RedisConfig { @Value("${spring.data.redis.host}") diff --git a/src/main/java/sopt/org/HMH/global/config/SecurityConfig.java b/src/main/java/sopt/org/HMH/global/config/SecurityConfig.java index 997c735a..98adcf50 100644 --- a/src/main/java/sopt/org/HMH/global/config/SecurityConfig.java +++ b/src/main/java/sopt/org/HMH/global/config/SecurityConfig.java @@ -9,6 +9,7 @@ import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import sopt.org.HMH.global.auth.security.CustomAccessDeniedHandler; import sopt.org.HMH.global.auth.security.CustomJwtAuthenticationEntryPoint; import sopt.org.HMH.global.auth.security.JwtAuthenticationFilter; @@ -21,16 +22,25 @@ public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; // JWT에 대한 인증 예외 처리를 담당하는 인증 진입점 private final CustomJwtAuthenticationEntryPoint customJwtAuthenticationEntryPoint; + private final CustomAccessDeniedHandler customAccessDeniedHandler; private static final String[] AUTH_WHITELIST = { - "/api/kakao/**", "/loading", "/error", "/api/login", "/api/reissue", - "/api/test/**", "/health", "/actuator/health", - "/api/images/**", "/", "/swagger-ui/**", "/swagger-resources/**", "/api-docs/**" + "/", "/error", "/health", + + // Swagger + "/swagger-ui/**", + "/swagger-resources/**", + "/api-docs/**", + + // Authentication + "/login", + "/reissue", + }; @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - return http + public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { + return httpSecurity .formLogin(AbstractHttpConfigurer::disable) // Form Login 사용 X .httpBasic(AbstractHttpConfigurer::disable) // HTTP Basic 사용 X .csrf(AbstractHttpConfigurer::disable) // 쿠키 기반이 아닌 JWT 기반이므로 사용 X diff --git a/src/main/java/sopt/org/HMH/global/config/WebConfig.java b/src/main/java/sopt/org/HMH/global/config/WebConfig.java index 2a6057de..b5bea4ce 100644 --- a/src/main/java/sopt/org/HMH/global/config/WebConfig.java +++ b/src/main/java/sopt/org/HMH/global/config/WebConfig.java @@ -11,7 +11,7 @@ public class WebConfig implements WebMvcConfigurer { public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("**") - .allowedMethods("GET", "POST", "PUT", "DELETE") + .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE") .allowCredentials(true) .maxAge(3000); }