From 65b8073ea7b7c452864d7514f8414ac53cbe1a07 Mon Sep 17 00:00:00 2001 From: jy000n Date: Sun, 28 Jun 2026 16:15:23 +0900 Subject: [PATCH 01/49] =?UTF-8?q?chore:=20=ED=8C=8C=EC=9D=BC=EB=AA=85=20?= =?UTF-8?q?=EB=81=9D=20=EA=B3=B5=EB=B0=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .coderabbit.yaml => .coderabbit.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .coderabbit.yaml => .coderabbit.yaml (100%) diff --git a/.coderabbit.yaml b/.coderabbit.yaml similarity index 100% rename from .coderabbit.yaml rename to .coderabbit.yaml From f3443e235396d0cb1f5a7c5d85c14bfdef099f30 Mon Sep 17 00:00:00 2001 From: jy000n Date: Sun, 28 Jun 2026 19:12:07 +0900 Subject: [PATCH 02/49] =?UTF-8?q?chore:=20oauth2,=20redis,=20jwt=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/build.gradle b/build.gradle index 055cacb..acfb9e7 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,17 @@ dependencies { // Security implementation 'org.springframework.boot:spring-boot-starter-security' + // OAuth + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + + // Redis + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + + // Jwt + implementation 'io.jsonwebtoken:jjwt-api:0.12.6' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6' + // Validation implementation 'org.springframework.boot:spring-boot-starter-validation' From c16a213ea42429fe30b6ed30ef35fb18a92ea451 Mon Sep 17 00:00:00 2001 From: jy000n Date: Sun, 28 Jun 2026 20:20:24 +0900 Subject: [PATCH 03/49] =?UTF-8?q?git=20commit=20-m=20"feat:=20CORS=20?= =?UTF-8?q?=EB=B0=8F=20JWT=20=ED=95=84=ED=84=B0=EB=A5=BC=20=ED=8F=AC?= =?UTF-8?q?=ED=95=A8=ED=95=9C=20SecurityConfig=20=EC=B6=94=EA=B0=80"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Timo/global/config/SecurityConfig.java | 47 +++++++++++++++++-- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java b/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java index 0d03134..2b91f32 100644 --- a/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java +++ b/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java @@ -1,32 +1,71 @@ package com.Timo.Timo.global.config; +import java.util.List; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; @Configuration +@EnableWebSecurity +@RequiredArgsConstructor public class SecurityConfig { + private final CustomOAuth2UserService customOAuth2UserService; + private final OAuthSuccessHandler oAuthSuccessHandler; + private final OAuthFailureHandler oAuthFailureHandler; + private final JwtAuthenticationFilter jwtAuthenticationFilter; + @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(AbstractHttpConfigurer::disable) .formLogin(AbstractHttpConfigurer::disable) .httpBasic(AbstractHttpConfigurer::disable) + .cors(cors -> cors.configurationSource(corsConfigurationSource())) .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(authorize -> authorize .requestMatchers( "/swagger-ui/**", "/swagger-ui.html", - "/v3/api-docs/**" + "/v3/api-docs/**", + "/login/**", + "/oauth2/**", + "/api/auth/reissue" ).permitAll() - // TODO: JWT 인증 적용 시 보호가 필요한 API는 authenticated()로 변경 - .anyRequest().permitAll()); + .anyRequest().authenticated()) + .oauth2Login(oauth2 -> oauth2 + .userInfoEndpoint(userInfo -> + userInfo.userService(customOAuth2UserService) + ) + .successHandler(oAuthSuccessHandler) + .failureHandler(oAuthFailureHandler) + ) + .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); - return http.build(); + return http.build(); } + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowedOrigins(List.of("http://localhost:3000", "http://localhost:5173")); // TODO: 프론트 배포 시 도메인으로 변경 + config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); + config.setAllowedHeaders(List.of("*")); + config.setAllowCredentials(true); + config.setMaxAge(3600L); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + return source; + } } From 3e85bdeb1eb7a0fe80fc007ed6d67b4acfe7bc4a Mon Sep 17 00:00:00 2001 From: jy000n Date: Sun, 28 Jun 2026 20:25:15 +0900 Subject: [PATCH 04/49] =?UTF-8?q?git=20commit=20-m=20"feat:=20RedisConfig?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Timo/Timo/global/config/RedisConfig.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/main/java/com/Timo/Timo/global/config/RedisConfig.java diff --git a/src/main/java/com/Timo/Timo/global/config/RedisConfig.java b/src/main/java/com/Timo/Timo/global/config/RedisConfig.java new file mode 100644 index 0000000..8304655 --- /dev/null +++ b/src/main/java/com/Timo/Timo/global/config/RedisConfig.java @@ -0,0 +1,20 @@ +package com.Timo.Timo.global.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +public class RedisConfig { + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory factory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(factory); + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(new StringRedisSerializer()); + return template; + } +} \ No newline at end of file From 012c19e0818f38b09b21c0bea7aded14abe2e957 Mon Sep 17 00:00:00 2001 From: jy000n Date: Tue, 30 Jun 2026 00:38:50 +0900 Subject: [PATCH 05/49] =?UTF-8?q?feat:=20=EC=86=8C=EC=85=9C=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=EC=9D=84=20=EC=9C=84=ED=95=9C=20User=20?= =?UTF-8?q?=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Timo/Timo/domain/entity/user/User.java | 46 +++++++++++++++++++ .../Timo/Timo/global/config/RedisConfig.java | 3 ++ 2 files changed, 49 insertions(+) create mode 100644 src/main/java/com/Timo/Timo/domain/entity/user/User.java diff --git a/src/main/java/com/Timo/Timo/domain/entity/user/User.java b/src/main/java/com/Timo/Timo/domain/entity/user/User.java new file mode 100644 index 0000000..3f10eb9 --- /dev/null +++ b/src/main/java/com/Timo/Timo/domain/entity/user/User.java @@ -0,0 +1,46 @@ +package com.Timo.Timo.domain.entity.user; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "users") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Builder +@AllArgsConstructor +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, unique = true) + private String email; + + private String name; + private String picture; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private Provider provider; + + // TODO : 변경 가능한 건지 기획에게 확인 필요 (피그마에 관련 의견 남겨둠) + public void update(String name, String picture){ + this.name = name; + this.picture = picture; + } + + public enum Provider { GOOGLE } +} diff --git a/src/main/java/com/Timo/Timo/global/config/RedisConfig.java b/src/main/java/com/Timo/Timo/global/config/RedisConfig.java index 8304655..d97759c 100644 --- a/src/main/java/com/Timo/Timo/global/config/RedisConfig.java +++ b/src/main/java/com/Timo/Timo/global/config/RedisConfig.java @@ -11,10 +11,13 @@ public class RedisConfig { @Bean public RedisTemplate redisTemplate(RedisConnectionFactory factory) { + RedisTemplate template = new RedisTemplate<>(); template.setConnectionFactory(factory); + template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new StringRedisSerializer()); + return template; } } \ No newline at end of file From fdb720e07156496eab0826760ffb1145517e102b Mon Sep 17 00:00:00 2001 From: jy000n Date: Tue, 30 Jun 2026 00:45:59 +0900 Subject: [PATCH 06/49] =?UTF-8?q?chore:=20User=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=ED=8C=8C=EC=9D=BC=20=EC=9C=84=EC=B9=98=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/Timo/Timo/domain/{entity/user => user/entity}/User.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/main/java/com/Timo/Timo/domain/{entity/user => user/entity}/User.java (96%) diff --git a/src/main/java/com/Timo/Timo/domain/entity/user/User.java b/src/main/java/com/Timo/Timo/domain/user/entity/User.java similarity index 96% rename from src/main/java/com/Timo/Timo/domain/entity/user/User.java rename to src/main/java/com/Timo/Timo/domain/user/entity/User.java index 3f10eb9..c7c15f0 100644 --- a/src/main/java/com/Timo/Timo/domain/entity/user/User.java +++ b/src/main/java/com/Timo/Timo/domain/user/entity/User.java @@ -1,4 +1,4 @@ -package com.Timo.Timo.domain.entity.user; +package com.Timo.Timo.domain.user.entity; import jakarta.persistence.Column; import jakarta.persistence.Entity; From 2a6faf751f423f7656e1ce5d6e0248f979bc83a1 Mon Sep 17 00:00:00 2001 From: jy000n Date: Tue, 30 Jun 2026 00:51:36 +0900 Subject: [PATCH 07/49] =?UTF-8?q?feat:=20=EC=86=8C=EC=85=9C=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=EC=9D=84=20=EC=9C=84=ED=95=9C=20UserReposito?= =?UTF-8?q?ry=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Timo/Timo/domain/user/repository/UserRepository.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/com/Timo/Timo/domain/user/repository/UserRepository.java diff --git a/src/main/java/com/Timo/Timo/domain/user/repository/UserRepository.java b/src/main/java/com/Timo/Timo/domain/user/repository/UserRepository.java new file mode 100644 index 0000000..c5c9696 --- /dev/null +++ b/src/main/java/com/Timo/Timo/domain/user/repository/UserRepository.java @@ -0,0 +1,9 @@ +package com.Timo.Timo.domain.user.repository; + +import com.Timo.Timo.domain.user.entity.User; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserRepository extends JpaRepository { + Optional findByEmail(String email); +} From 3f2bda6855f5b149d91d587065bad3af92fa5cd8 Mon Sep 17 00:00:00 2001 From: jy000n Date: Tue, 30 Jun 2026 01:14:09 +0900 Subject: [PATCH 08/49] =?UTF-8?q?feat:=20CustomOAuth2UserService=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/service/CustomOAuth2UserService.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java diff --git a/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java b/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java new file mode 100644 index 0000000..c0786d0 --- /dev/null +++ b/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java @@ -0,0 +1,52 @@ +package com.Timo.Timo.global.auth.service; + +import com.Timo.Timo.domain.user.entity.User; +import com.Timo.Timo.domain.user.repository.UserRepository; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CustomOAuth2UserService implements OAuth2UserService { + + private final UserRepository userRepository; + + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + // 기본 구글 유저 정보 가져오기 + OAuth2UserService delegate = new DefaultOAuth2UserService(); + OAuth2User oAuth2User = delegate.loadUser(userRequest); + + // 구글이 준 원본 JSON 데이터 꺼내기 + Map attributes = oAuth2User.getAttributes(); + String email = (String) attributes.get("email"); + String name = (String) attributes.get("name"); + String picture = (String) attributes.get("picture"); + + // DB에서 유저 찾기 + 없으면 생성 + User user = userRepository.findByEmail(email) + .map(existing -> { + // 이름/사진 바뀌었을 수도 있으므로 최신 정보로 업데이트 + existing.update(name, picture); + return existing; + }) + // 없는 유저의 경우 첫 로그인 -> 자동 회원가입 + .orElseGet(() -> userRepository.save( + User.builder() + .email(email) + .name(name) + .picture(picture) + .provider(User.Provider.GOOGLE) + .build() + )); + + // 우리 서비스용 객체로 변환 + return new CustomerUserDetails(user, attributes); + } +} From 3e99a23b778a5ba89865061a7917d0b1ef13ac53 Mon Sep 17 00:00:00 2001 From: jy000n Date: Tue, 30 Jun 2026 01:57:10 +0900 Subject: [PATCH 09/49] =?UTF-8?q?feat:=20CustomUserDetails=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/auth/dto/CustomUserDetails.java | 49 +++++++++++++++++++ .../auth/service/CustomOAuth2UserService.java | 8 +-- 2 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/Timo/Timo/global/auth/dto/CustomUserDetails.java diff --git a/src/main/java/com/Timo/Timo/global/auth/dto/CustomUserDetails.java b/src/main/java/com/Timo/Timo/global/auth/dto/CustomUserDetails.java new file mode 100644 index 0000000..dc87799 --- /dev/null +++ b/src/main/java/com/Timo/Timo/global/auth/dto/CustomUserDetails.java @@ -0,0 +1,49 @@ +package com.Timo.Timo.global.auth.dto; + +import com.Timo.Timo.domain.user.entity.User; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import lombok.Getter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.oauth2.core.user.OAuth2User; + +@Getter +public class CustomUserDetails implements OAuth2User, UserDetails { + + private final User user; + private final Map attributes; + + public CustomUserDetails(User user, Map attributes){ + this.user = user; + this.attributes = attributes; + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public String getName() { + return user.getEmail(); + } + + @Override + public Collection getAuthorities() { + return List.of(new SimpleGrantedAuthority("ROLE_USER")); + } + + @Override + public String getPassword(){ + return null; + } + + @Override + public String getUsername(){ + return user.getEmail(); + } + +} diff --git a/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java b/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java index c0786d0..e81b6d1 100644 --- a/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java +++ b/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java @@ -2,6 +2,7 @@ import com.Timo.Timo.domain.user.entity.User; import com.Timo.Timo.domain.user.repository.UserRepository; +import com.Timo.Timo.global.auth.dto.CustomUserDetails; import java.util.Map; import lombok.RequiredArgsConstructor; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; @@ -19,24 +20,19 @@ public class CustomOAuth2UserService implements OAuth2UserService delegate = new DefaultOAuth2UserService(); OAuth2User oAuth2User = delegate.loadUser(userRequest); - // 구글이 준 원본 JSON 데이터 꺼내기 Map attributes = oAuth2User.getAttributes(); String email = (String) attributes.get("email"); String name = (String) attributes.get("name"); String picture = (String) attributes.get("picture"); - // DB에서 유저 찾기 + 없으면 생성 User user = userRepository.findByEmail(email) .map(existing -> { - // 이름/사진 바뀌었을 수도 있으므로 최신 정보로 업데이트 existing.update(name, picture); return existing; }) - // 없는 유저의 경우 첫 로그인 -> 자동 회원가입 .orElseGet(() -> userRepository.save( User.builder() .email(email) @@ -47,6 +43,6 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic )); // 우리 서비스용 객체로 변환 - return new CustomerUserDetails(user, attributes); + return new CustomUserDetails(user, attributes); } } From 2f01497eebae2adb8194ce9a3f69c48bb80d3cb4 Mon Sep 17 00:00:00 2001 From: jy000n Date: Tue, 30 Jun 2026 04:13:31 +0900 Subject: [PATCH 10/49] =?UTF-8?q?feat:=20OAuthFailureHandler=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/handler/OAuthFailureHandler.java | 44 +++++++++++++++++++ .../Timo/global/config/SecurityConfig.java | 2 + 2 files changed, 46 insertions(+) create mode 100644 src/main/java/com/Timo/Timo/global/auth/handler/OAuthFailureHandler.java diff --git a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthFailureHandler.java b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthFailureHandler.java new file mode 100644 index 0000000..8d5ce09 --- /dev/null +++ b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthFailureHandler.java @@ -0,0 +1,44 @@ +package com.Timo.Timo.global.auth.handler; + +import com.Timo.Timo.global.exception.code.ErrorCode; +import com.Timo.Timo.global.exception.dto.ErrorDto; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.time.LocalDateTime; +import org.springframework.http.MediaType; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; +import org.springframework.stereotype.Component; + +@Component +public class OAuthFailureHandler extends SimpleUrlAuthenticationFailureHandler { + + private final ObjectMapper objectMapper = new ObjectMapper() + .registerModule(new JavaTimeModule()); + + @Override + public void onAuthenticationFailure( + HttpServletRequest request, + HttpServletResponse response, + AuthenticationException exception + ) throws IOException { + + ErrorCode errorCode = ErrorCode.UNAUTHORIZED; + + ErrorDto errorDto = new ErrorDto( + LocalDateTime.now(), + errorCode.getHttpStatus().value(), + errorCode.getCode(), + errorCode.getMessage(), + request.getRequestURI() + ); + + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding("UTF-8"); + response.getWriter().write(objectMapper.writeValueAsString(errorDto)); + } +} diff --git a/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java b/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java index 2b91f32..296e883 100644 --- a/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java +++ b/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java @@ -1,5 +1,7 @@ package com.Timo.Timo.global.config; +import com.Timo.Timo.global.auth.handler.OAuthSuccessHandler; +import com.Timo.Timo.global.auth.service.CustomOAuth2UserService; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; From 14479053ed18826541eac7426699cf08412c7a78 Mon Sep 17 00:00:00 2001 From: jy000n Date: Tue, 30 Jun 2026 04:13:45 +0900 Subject: [PATCH 11/49] =?UTF-8?q?feat:=20OAuthSuccessHandler=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/handler/OAuthSuccessHandler.java | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java diff --git a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java new file mode 100644 index 0000000..6f4601a --- /dev/null +++ b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java @@ -0,0 +1,59 @@ +package com.Timo.Timo.global.auth.handler; + +import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.REDIRECT_URI; + +import com.Timo.Timo.global.auth.dto.CustomUserDetails; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.time.Duration; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseCookie; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.stereotype.Component; +import org.springframework.web.util.UriComponentsBuilder; + +@Component +@RequiredArgsConstructor +public class OAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { + + private final JwtTokenProvider jwtTokenProvider; // access/refresh JWT 생성 + private final RefreshTokenService refreshTokenService; // refreshToken DB 저장 + + @Value("${app.oauth2.redirect-uri}") + private String redirectUri; + + @Override + public void onAuthenticationSuccess( + HttpServletRequest request, + HttpServletResponse response, + Authentication authentication + ) throws IOException { +0 + CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); + String email = userDetails.getUser().getEmail(); + + String accessToken = jwtTokenProvider.generateAccessToken(email); + String refreshToken = jwtTokenProvider.generateRefreshToken(email); + + refreshTokenService.save(email, refreshToken); + + RequestCookie cookie = ResponseCookie.from("refreshToken", refreshToken) + .httpOnly(true) + .secure(false) // TODO: 배포 시 true + .path("/") + .maxAge(Duration.ofDays(7)) + .sameSite("Lax") + .build(); + response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); + + String redirectUrl = UriComponentsBuilder.fromUriString(REDIRECT_URI) + .queryParam("accessToken", accessToken) + .build().toUriString(); + + getRedirectStrategy().sendRedirect(request, resoponse, redirectUri); + } +} From b7dce5a9cfc0255098c53303b9530e36e2e2d968 Mon Sep 17 00:00:00 2001 From: jy000n Date: Tue, 30 Jun 2026 04:48:55 +0900 Subject: [PATCH 12/49] =?UTF-8?q?feat:=20JwtTokenProvider=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/handler/OAuthSuccessHandler.java | 5 +- .../global/jwt/provider/JwtTokenProvider.java | 73 +++++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java diff --git a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java index 6f4601a..0694e0e 100644 --- a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java +++ b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java @@ -3,6 +3,7 @@ import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.REDIRECT_URI; import com.Timo.Timo.global.auth.dto.CustomUserDetails; +import com.Timo.Timo.global.jwt.provider.JwtTokenProvider; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; @@ -20,8 +21,8 @@ @RequiredArgsConstructor public class OAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { - private final JwtTokenProvider jwtTokenProvider; // access/refresh JWT 생성 - private final RefreshTokenService refreshTokenService; // refreshToken DB 저장 + private final JwtTokenProvider jwtTokenProvider; + private final RefreshTokenService refreshTokenService; @Value("${app.oauth2.redirect-uri}") private String redirectUri; diff --git a/src/main/java/com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java b/src/main/java/com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java new file mode 100644 index 0000000..d8053d3 --- /dev/null +++ b/src/main/java/com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java @@ -0,0 +1,73 @@ +package com.Timo.Timo.global.jwt.provider; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import javax.crypto.SecretKey; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class JwtTokenProvider { + + @Value("${jwt.secret}") + private String secretKey; + + @Value("${security.jwt.access-token-expires-in-seconds}") + private long accessTokenExpirySeconds; + + @Value("${security.jwt.refresh-token-expires-in-seconds}") + private long refreshTokenExpirySeconds; + + private SecretKey getSigningKey(){ + return Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); + } + + public String generateAccessToken(String email){ + return Jwts.builder() + .subject(email) + .claim("type", "access") + .issuedAt(new Date()) + .expiration(new Date(System.currentTimeMillis() + accessTokenExpirySeconds * 1000)) + .signWith(getSigningKey()) + .compact(); + } + + public String generateRefreshToken(String email){ + return Jwts.builder() + .subject(email) + .claim("type", "refresh") + .issuedAt(new Date()) + .expiration(new Date(System.currentTimeMillis() + refreshTokenExpirySeconds * 1000)) + .signWith(getSigningKey()) + .compact(); + } + + public boolean validateToken(String token){ + try { + getClaims(token); + return true; + } catch (JwtException | IllegalArgumentException e){ + return false; + } + } + + public String getEmail(String token){ + return getClaims(token).getSubject(); + } + + private Claims getClaims(String token){ + return Jwts.parser() + .verifyWith(getSigningKey()) + .build() + .parseSignedClaims(token) + .getPayload(); + } + + public long getRefreshTokenExpiry() { + return refreshTokenExpirySeconds; + } +} \ No newline at end of file From b1440b052681c2cec7c2d0edd344e62504bf221b Mon Sep 17 00:00:00 2001 From: jy000n Date: Tue, 30 Jun 2026 05:03:15 +0900 Subject: [PATCH 13/49] =?UTF-8?q?feat:=20RefreshTokenService=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/handler/OAuthSuccessHandler.java | 7 ++-- .../auth/service/RefreshTokenService.java | 38 +++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java diff --git a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java index 0694e0e..85c2ed4 100644 --- a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java +++ b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java @@ -3,6 +3,7 @@ import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.REDIRECT_URI; import com.Timo.Timo.global.auth.dto.CustomUserDetails; +import com.Timo.Timo.global.auth.service.RefreshTokenService; import com.Timo.Timo.global.jwt.provider.JwtTokenProvider; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -33,7 +34,7 @@ public void onAuthenticationSuccess( HttpServletResponse response, Authentication authentication ) throws IOException { -0 + CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); String email = userDetails.getUser().getEmail(); @@ -42,7 +43,7 @@ public void onAuthenticationSuccess( refreshTokenService.save(email, refreshToken); - RequestCookie cookie = ResponseCookie.from("refreshToken", refreshToken) + ResponseCookie cookie = ResponseCookie.from("refreshToken", refreshToken) .httpOnly(true) .secure(false) // TODO: 배포 시 true .path("/") @@ -55,6 +56,6 @@ public void onAuthenticationSuccess( .queryParam("accessToken", accessToken) .build().toUriString(); - getRedirectStrategy().sendRedirect(request, resoponse, redirectUri); + getRedirectStrategy().sendRedirect(request, response, redirectUri); } } diff --git a/src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java b/src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java new file mode 100644 index 0000000..46c4007 --- /dev/null +++ b/src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java @@ -0,0 +1,38 @@ +package com.Timo.Timo.global.auth.service; + +import com.Timo.Timo.global.jwt.provider.JwtTokenProvider; +import java.util.concurrent.TimeUnit; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class RefreshTokenService { + + private final RedisTemplate redisTemplate; // Redis 저장소 접근 + private final JwtTokenProvider jwtTokenProvider; // 토큰 만료 시간 가져오기 + + private static final String KEY_PREFIX = "refresh:"; + + public void save(String email, String refreshToken){ + redisTemplate.opsForValue().set( + KEY_PREFIX + email, + refreshToken, + jwtTokenProvider.getRefreshTokenExpiry(), + TimeUnit.MILLISECONDS // Redis에서 자동 삭제됨 + ); + } + + public String get(String email){ + return redisTemplate.opsForValue().get(KEY_PREFIX + email); + } + + public void delete(String email){ + redisTemplate.delete(KEY_PREFIX + email); + } + + public boolean isValid(String email, String refreshToken){ + return refreshToken.equals(get(email)); + } +} From a0529cde130f50f4d16fe81108910a06e50d4665 Mon Sep 17 00:00:00 2001 From: jy000n Date: Tue, 30 Jun 2026 06:37:22 +0900 Subject: [PATCH 14/49] =?UTF-8?q?feat:=20JwtAuthenticationFilter=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Timo/global/config/SecurityConfig.java | 2 + .../jwt/filter/JwtAuthenticationFilter.java | 59 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 src/main/java/com/Timo/Timo/global/jwt/filter/JwtAuthenticationFilter.java diff --git a/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java b/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java index 296e883..b8a6bf3 100644 --- a/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java +++ b/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java @@ -1,7 +1,9 @@ package com.Timo.Timo.global.config; +import com.Timo.Timo.global.auth.handler.OAuthFailureHandler; import com.Timo.Timo.global.auth.handler.OAuthSuccessHandler; import com.Timo.Timo.global.auth.service.CustomOAuth2UserService; +import com.Timo.Timo.global.jwt.filter.JwtAuthenticationFilter; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; diff --git a/src/main/java/com/Timo/Timo/global/jwt/filter/JwtAuthenticationFilter.java b/src/main/java/com/Timo/Timo/global/jwt/filter/JwtAuthenticationFilter.java new file mode 100644 index 0000000..8d2cbf6 --- /dev/null +++ b/src/main/java/com/Timo/Timo/global/jwt/filter/JwtAuthenticationFilter.java @@ -0,0 +1,59 @@ +package com.Timo.Timo.global.jwt.filter; + +import com.Timo.Timo.domain.user.repository.UserRepository; +import com.Timo.Timo.global.auth.dto.CustomUserDetails; +import com.Timo.Timo.global.jwt.provider.JwtTokenProvider; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +@Component +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtTokenProvider jwtTokenProvider; + private final UserRepository userRepository; + + @Override + protected void doFilterInternal( + HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain + ) throws ServletException, IOException { + + String token = resolveToken(request); + + if(token!=null && jwtTokenProvider.validateToken(token)){ + String email = jwtTokenProvider.getEmail(token); + userRepository.findByEmail(email).ifPresent(user -> { + CustomUserDetails userDetails = new CustomUserDetails(user, Map.of()); + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.getAuthorities() + ); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authentication); + }); + } + + filterChain.doFilter(request, response); + } + + private String resolveToken(HttpServletRequest request){ + String bearer = request.getHeader("Authorization"); + if(StringUtils.hasText(bearer) && bearer.startsWith("Bearer ")){ + return bearer.substring(7); + } + return null; + } +} From d5cea0e87ae506c1c03690b1dea15f3c4f0f6439 Mon Sep 17 00:00:00 2001 From: jy000n Date: Tue, 30 Jun 2026 07:14:53 +0900 Subject: [PATCH 15/49] =?UTF-8?q?fix:=20=EC=98=A4=ED=83=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Timo/Timo/global/auth/handler/OAuthSuccessHandler.java | 2 +- .../com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java index 85c2ed4..7ee9722 100644 --- a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java +++ b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java @@ -56,6 +56,6 @@ public void onAuthenticationSuccess( .queryParam("accessToken", accessToken) .build().toUriString(); - getRedirectStrategy().sendRedirect(request, response, redirectUri); + getRedirectStrategy().sendRedirect(request, response, redirectUrl); } } diff --git a/src/main/java/com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java b/src/main/java/com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java index d8053d3..0fcc3c7 100644 --- a/src/main/java/com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java +++ b/src/main/java/com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java @@ -16,10 +16,10 @@ public class JwtTokenProvider { @Value("${jwt.secret}") private String secretKey; - @Value("${security.jwt.access-token-expires-in-seconds}") + @Value("${jwt.access-token-expires-in-seconds}") private long accessTokenExpirySeconds; - @Value("${security.jwt.refresh-token-expires-in-seconds}") + @Value("${jwt.refresh-token-expires-in-seconds}") private long refreshTokenExpirySeconds; private SecretKey getSigningKey(){ From bc564f4ed48ef8db0be8894736a3cd933ccab4e8 Mon Sep 17 00:00:00 2001 From: jy000n Date: Tue, 30 Jun 2026 07:25:47 +0900 Subject: [PATCH 16/49] =?UTF-8?q?style:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/Timo/Timo/domain/user/entity/User.java | 1 - .../Timo/global/auth/service/CustomOAuth2UserService.java | 1 - .../Timo/Timo/global/auth/service/RefreshTokenService.java | 6 +++--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/Timo/Timo/domain/user/entity/User.java b/src/main/java/com/Timo/Timo/domain/user/entity/User.java index c7c15f0..f1e0173 100644 --- a/src/main/java/com/Timo/Timo/domain/user/entity/User.java +++ b/src/main/java/com/Timo/Timo/domain/user/entity/User.java @@ -36,7 +36,6 @@ public class User { @Column(nullable = false) private Provider provider; - // TODO : 변경 가능한 건지 기획에게 확인 필요 (피그마에 관련 의견 남겨둠) public void update(String name, String picture){ this.name = name; this.picture = picture; diff --git a/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java b/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java index e81b6d1..61d15e1 100644 --- a/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java +++ b/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java @@ -42,7 +42,6 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic .build() )); - // 우리 서비스용 객체로 변환 return new CustomUserDetails(user, attributes); } } diff --git a/src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java b/src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java index 46c4007..f3891c8 100644 --- a/src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java +++ b/src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java @@ -10,8 +10,8 @@ @RequiredArgsConstructor public class RefreshTokenService { - private final RedisTemplate redisTemplate; // Redis 저장소 접근 - private final JwtTokenProvider jwtTokenProvider; // 토큰 만료 시간 가져오기 + private final RedisTemplate redisTemplate; + private final JwtTokenProvider jwtTokenProvider; private static final String KEY_PREFIX = "refresh:"; @@ -20,7 +20,7 @@ public void save(String email, String refreshToken){ KEY_PREFIX + email, refreshToken, jwtTokenProvider.getRefreshTokenExpiry(), - TimeUnit.MILLISECONDS // Redis에서 자동 삭제됨 + TimeUnit.MILLISECONDS ); } From f8fd683dbcc39bd69efcb267de7fbfabd699f699 Mon Sep 17 00:00:00 2001 From: jy000n Date: Tue, 30 Jun 2026 07:25:47 +0900 Subject: [PATCH 17/49] =?UTF-8?q?chore:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/Timo/Timo/domain/user/entity/User.java | 1 - .../Timo/global/auth/service/CustomOAuth2UserService.java | 1 - .../Timo/Timo/global/auth/service/RefreshTokenService.java | 6 +++--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/Timo/Timo/domain/user/entity/User.java b/src/main/java/com/Timo/Timo/domain/user/entity/User.java index c7c15f0..f1e0173 100644 --- a/src/main/java/com/Timo/Timo/domain/user/entity/User.java +++ b/src/main/java/com/Timo/Timo/domain/user/entity/User.java @@ -36,7 +36,6 @@ public class User { @Column(nullable = false) private Provider provider; - // TODO : 변경 가능한 건지 기획에게 확인 필요 (피그마에 관련 의견 남겨둠) public void update(String name, String picture){ this.name = name; this.picture = picture; diff --git a/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java b/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java index e81b6d1..61d15e1 100644 --- a/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java +++ b/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java @@ -42,7 +42,6 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic .build() )); - // 우리 서비스용 객체로 변환 return new CustomUserDetails(user, attributes); } } diff --git a/src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java b/src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java index 46c4007..f3891c8 100644 --- a/src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java +++ b/src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java @@ -10,8 +10,8 @@ @RequiredArgsConstructor public class RefreshTokenService { - private final RedisTemplate redisTemplate; // Redis 저장소 접근 - private final JwtTokenProvider jwtTokenProvider; // 토큰 만료 시간 가져오기 + private final RedisTemplate redisTemplate; + private final JwtTokenProvider jwtTokenProvider; private static final String KEY_PREFIX = "refresh:"; @@ -20,7 +20,7 @@ public void save(String email, String refreshToken){ KEY_PREFIX + email, refreshToken, jwtTokenProvider.getRefreshTokenExpiry(), - TimeUnit.MILLISECONDS // Redis에서 자동 삭제됨 + TimeUnit.MILLISECONDS ); } From f531a63ab33a456eac24e855e2087084bffc756f Mon Sep 17 00:00:00 2001 From: jy000n Date: Tue, 30 Jun 2026 07:30:47 +0900 Subject: [PATCH 18/49] =?UTF-8?q?chore:=20=ED=8C=8C=EC=9D=BC=20=EB=81=9D?= =?UTF-8?q?=EC=97=90=20=EA=B0=9C=ED=96=89=EB=AC=B8=EC=9E=90=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/Timo/Timo/global/config/RedisConfig.java | 2 +- .../com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/Timo/Timo/global/config/RedisConfig.java b/src/main/java/com/Timo/Timo/global/config/RedisConfig.java index d97759c..2846c60 100644 --- a/src/main/java/com/Timo/Timo/global/config/RedisConfig.java +++ b/src/main/java/com/Timo/Timo/global/config/RedisConfig.java @@ -20,4 +20,4 @@ public RedisTemplate redisTemplate(RedisConnectionFactory factor return template; } -} \ No newline at end of file +} diff --git a/src/main/java/com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java b/src/main/java/com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java index 0fcc3c7..cfd7420 100644 --- a/src/main/java/com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java +++ b/src/main/java/com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java @@ -70,4 +70,4 @@ private Claims getClaims(String token){ public long getRefreshTokenExpiry() { return refreshTokenExpirySeconds; } -} \ No newline at end of file +} From b6cb9b557b07d2b6cec821324e01dc7c9e7f85a4 Mon Sep 17 00:00:00 2001 From: jy000n Date: Tue, 30 Jun 2026 12:46:08 +0900 Subject: [PATCH 19/49] =?UTF-8?q?feat:=20access/refresh=20token=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=20=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jwt/filter/JwtAuthenticationFilter.java | 2 +- .../global/jwt/provider/JwtTokenProvider.java | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/Timo/Timo/global/jwt/filter/JwtAuthenticationFilter.java b/src/main/java/com/Timo/Timo/global/jwt/filter/JwtAuthenticationFilter.java index 8d2cbf6..59688cf 100644 --- a/src/main/java/com/Timo/Timo/global/jwt/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/Timo/Timo/global/jwt/filter/JwtAuthenticationFilter.java @@ -33,7 +33,7 @@ protected void doFilterInternal( String token = resolveToken(request); - if(token!=null && jwtTokenProvider.validateToken(token)){ + if(token!=null && jwtTokenProvider.validateAccessToken(token)){ String email = jwtTokenProvider.getEmail(token); userRepository.findByEmail(email).ifPresent(user -> { CustomUserDetails userDetails = new CustomUserDetails(user, Map.of()); diff --git a/src/main/java/com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java b/src/main/java/com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java index cfd7420..074343f 100644 --- a/src/main/java/com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java +++ b/src/main/java/com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java @@ -46,15 +46,24 @@ public String generateRefreshToken(String email){ .compact(); } - public boolean validateToken(String token){ + public boolean validateAccessToken(String token){ + return validateToken(token, "access"); + } + + public boolean validateRefreshToken(String token){ + return validateToken(token, "refresh"); + } + + private boolean validateToken(String token, String expectedType){ try { - getClaims(token); - return true; + Claims claims = getClaims(token); + return expectedType.equals(claims.get("type", String.class)); } catch (JwtException | IllegalArgumentException e){ return false; } } + public String getEmail(String token){ return getClaims(token).getSubject(); } From 505eb23cd25375940ee10db53022553558147318 Mon Sep 17 00:00:00 2001 From: jy000n Date: Tue, 30 Jun 2026 12:56:07 +0900 Subject: [PATCH 20/49] =?UTF-8?q?feat:=20OAuth2=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9E=90=20=EC=A0=95=EB=B3=B4=20=EA=B2=80=EC=A6=9D=20=EB=B0=8F?= =?UTF-8?q?=20=ED=8A=B8=EB=9E=9C=EC=9E=AD=EC=85=98=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/auth/service/CustomOAuth2UserService.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java b/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java index 61d15e1..ef2f5e0 100644 --- a/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java +++ b/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java @@ -9,8 +9,11 @@ import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; @Service @RequiredArgsConstructor @@ -19,6 +22,7 @@ public class CustomOAuth2UserService implements OAuth2UserService delegate = new DefaultOAuth2UserService(); OAuth2User oAuth2User = delegate.loadUser(userRequest); @@ -28,6 +32,13 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic String name = (String) attributes.get("name"); String picture = (String) attributes.get("picture"); + if (!StringUtils.hasText(email)){ + throw new OAuth2AuthenticationException( + new OAuth2Error("invalid_user_info"), + "OAuth2 user email is required" + ); + } + User user = userRepository.findByEmail(email) .map(existing -> { existing.update(name, picture); From 011a7a57be3452502c39561bc14b6978399fb47e Mon Sep 17 00:00:00 2001 From: jy000n Date: Tue, 30 Jun 2026 13:15:38 +0900 Subject: [PATCH 21/49] =?UTF-8?q?refactor:=20OAuth2=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=EB=A5=BC=20ErrorCode=20=EA=B8=B0=EB=B0=98=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Timo/global/auth/service/CustomOAuth2UserService.java | 7 ++++--- .../com/Timo/Timo/global/exception/code/ErrorCode.java | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java b/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java index ef2f5e0..eb4fd8f 100644 --- a/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java +++ b/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java @@ -3,6 +3,7 @@ import com.Timo.Timo.domain.user.entity.User; import com.Timo.Timo.domain.user.repository.UserRepository; import com.Timo.Timo.global.auth.dto.CustomUserDetails; +import com.Timo.Timo.global.exception.code.ErrorCode; import java.util.Map; import lombok.RequiredArgsConstructor; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; @@ -32,10 +33,10 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic String name = (String) attributes.get("name"); String picture = (String) attributes.get("picture"); - if (!StringUtils.hasText(email)){ + if (!StringUtils.hasText(email)) { throw new OAuth2AuthenticationException( - new OAuth2Error("invalid_user_info"), - "OAuth2 user email is required" + new OAuth2Error(ErrorCode.OAUTH2_INVALID_USER_INFO.getCode()), + ErrorCode.OAUTH2_INVALID_USER_INFO.getMessage() ); } diff --git a/src/main/java/com/Timo/Timo/global/exception/code/ErrorCode.java b/src/main/java/com/Timo/Timo/global/exception/code/ErrorCode.java index a8a8d62..84e8e83 100644 --- a/src/main/java/com/Timo/Timo/global/exception/code/ErrorCode.java +++ b/src/main/java/com/Timo/Timo/global/exception/code/ErrorCode.java @@ -11,6 +11,7 @@ public enum ErrorCode implements BaseErrorCode { BAD_REQUEST(HttpStatus.BAD_REQUEST, "COMMON_400", "잘못된 요청입니다."), UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "COMMON_401", "인증이 필요합니다."), + OAUTH2_INVALID_USER_INFO(HttpStatus.UNAUTHORIZED, "AUTH_401", "OAuth2 유저 정보가 유효하지 않습니다."), FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON_403", "접근 권한이 없습니다."), NOT_FOUND(HttpStatus.NOT_FOUND, "COMMON_404", "요청한 리소스를 찾을 수 없습니다."), METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "COMMON_405", "지원하지 않는 HTTP 메서드입니다."), From 1f44da32ba6500533a856f9d3c7e020a9b5a4ab7 Mon Sep 17 00:00:00 2001 From: jy000n Date: Tue, 30 Jun 2026 13:20:17 +0900 Subject: [PATCH 22/49] =?UTF-8?q?fix:=20Redis=20TTL=20=EB=8B=A8=EC=9C=84?= =?UTF-8?q?=EB=A5=BC=20JWT=20refresh=20token=20=EB=A7=8C=EB=A3=8C=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=20=EB=8B=A8=EC=9C=84(s)=EC=99=80=20=EC=9D=BC?= =?UTF-8?q?=EC=B9=98=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/Timo/Timo/global/auth/service/RefreshTokenService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java b/src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java index f3891c8..d1e9aa7 100644 --- a/src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java +++ b/src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java @@ -20,7 +20,7 @@ public void save(String email, String refreshToken){ KEY_PREFIX + email, refreshToken, jwtTokenProvider.getRefreshTokenExpiry(), - TimeUnit.MILLISECONDS + TimeUnit.SECONDS ); } From cd4ca0356983c51ce17226df0da6688d339e1810 Mon Sep 17 00:00:00 2001 From: jy000n Date: Tue, 30 Jun 2026 13:26:54 +0900 Subject: [PATCH 23/49] =?UTF-8?q?fix:=20refresh=20token=20null=20=EB=B9=84?= =?UTF-8?q?=EA=B5=90=20=EC=8B=9C=20NPE=20=EB=B0=A9=EC=A7=80=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/Timo/Timo/global/auth/service/RefreshTokenService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java b/src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java index d1e9aa7..3292183 100644 --- a/src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java +++ b/src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java @@ -1,6 +1,7 @@ package com.Timo.Timo.global.auth.service; import com.Timo.Timo.global.jwt.provider.JwtTokenProvider; +import java.util.Objects; import java.util.concurrent.TimeUnit; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; @@ -33,6 +34,6 @@ public void delete(String email){ } public boolean isValid(String email, String refreshToken){ - return refreshToken.equals(get(email)); + return Objects.equals(refreshToken, get(email)); } } From 91eb023be51829df592a996244df5ee8f47f1b0d Mon Sep 17 00:00:00 2001 From: jy000n Date: Tue, 30 Jun 2026 13:50:47 +0900 Subject: [PATCH 24/49] =?UTF-8?q?refactor:=20API=20JWT=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20=EC=8B=A4=ED=8C=A8=EC=99=80=20OAuth2=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=B2=98=EB=A6=AC=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Timo/global/config/SecurityConfig.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java b/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java index b8a6bf3..57b859a 100644 --- a/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java +++ b/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java @@ -3,15 +3,21 @@ import com.Timo.Timo.global.auth.handler.OAuthFailureHandler; import com.Timo.Timo.global.auth.handler.OAuthSuccessHandler; import com.Timo.Timo.global.auth.service.CustomOAuth2UserService; +import com.Timo.Timo.global.exception.code.ErrorCode; +import com.Timo.Timo.global.exception.dto.ErrorDto; import com.Timo.Timo.global.jwt.filter.JwtAuthenticationFilter; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.time.LocalDateTime; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfiguration; @@ -26,6 +32,7 @@ public class SecurityConfig { private final CustomOAuth2UserService customOAuth2UserService; private final OAuthSuccessHandler oAuthSuccessHandler; private final OAuthFailureHandler oAuthFailureHandler; + private final AuthenticationEntryPoint authenticationEntryPoint; private final JwtAuthenticationFilter jwtAuthenticationFilter; @Bean @@ -39,6 +46,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(authorize -> authorize .requestMatchers( + "/", "/swagger-ui/**", "/swagger-ui.html", "/v3/api-docs/**", @@ -54,11 +62,35 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .successHandler(oAuthSuccessHandler) .failureHandler(oAuthFailureHandler) ) + .exceptionHandling(exception -> + exception.authenticationEntryPoint(authenticationEntryPoint) + ) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } + @Bean + public AuthenticationEntryPoint authenticationEntryPoint(ObjectMapper objectMapper) { + + return (request, response, authException) -> { + ErrorCode errorCode = ErrorCode.UNAUTHORIZED; + + ErrorDto errorDto = new ErrorDto( + LocalDateTime.now(), + errorCode.getHttpStatus().value(), + errorCode.getCode(), + errorCode.getMessage(), + request.getRequestURI() + ); + + response.setStatus(errorCode.getHttpStatus().value()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding("UTF-8"); + response.getWriter().write(objectMapper.writeValueAsString(errorDto)); + }; + } + @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); From e90308d8d7f3fa2dba4ccc89c98ce78fb3e03362 Mon Sep 17 00:00:00 2001 From: jy000n Date: Tue, 30 Jun 2026 14:06:21 +0900 Subject: [PATCH 25/49] =?UTF-8?q?fix:=20=EC=84=A4=EC=A0=95=EB=90=9C=20redi?= =?UTF-8?q?rectUri=EB=A5=BC=20=EC=82=AC=EC=9A=A9,=20=EC=BF=A0=ED=82=A4=20m?= =?UTF-8?q?axAge=EB=A5=BC=20refresh=20token=20=EB=A7=8C=EB=A3=8C=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=EA=B3=BC=20=EB=A7=9E=EC=B6=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Timo/Timo/global/auth/handler/OAuthSuccessHandler.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java index 7ee9722..6175157 100644 --- a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java +++ b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java @@ -1,7 +1,5 @@ package com.Timo.Timo.global.auth.handler; -import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.REDIRECT_URI; - import com.Timo.Timo.global.auth.dto.CustomUserDetails; import com.Timo.Timo.global.auth.service.RefreshTokenService; import com.Timo.Timo.global.jwt.provider.JwtTokenProvider; @@ -47,12 +45,12 @@ public void onAuthenticationSuccess( .httpOnly(true) .secure(false) // TODO: 배포 시 true .path("/") - .maxAge(Duration.ofDays(7)) + .maxAge(Duration.ofSeconds(jwtTokenProvider.getRefreshTokenExpiry())) .sameSite("Lax") .build(); response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); - String redirectUrl = UriComponentsBuilder.fromUriString(REDIRECT_URI) + String redirectUrl = UriComponentsBuilder.fromUriString(redirectUri) .queryParam("accessToken", accessToken) .build().toUriString(); From 5da973e0701872b012239d7d726e4ac449e634a6 Mon Sep 17 00:00:00 2001 From: jy000n Date: Tue, 30 Jun 2026 14:42:22 +0900 Subject: [PATCH 26/49] =?UTF-8?q?fix:=20accessToken=EC=9D=84=20URL=20?= =?UTF-8?q?=EC=BF=BC=EB=A6=AC=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20?= =?UTF-8?q?=EB=8C=80=EC=8B=A0=20HttpOnly=20=EC=BF=A0=ED=82=A4=EB=A1=9C=20?= =?UTF-8?q?=EC=A0=84=EB=8B=AC=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/handler/OAuthSuccessHandler.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java index 6175157..ddfae61 100644 --- a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java +++ b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java @@ -14,7 +14,6 @@ import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; -import org.springframework.web.util.UriComponentsBuilder; @Component @RequiredArgsConstructor @@ -41,19 +40,24 @@ public void onAuthenticationSuccess( refreshTokenService.save(email, refreshToken); - ResponseCookie cookie = ResponseCookie.from("refreshToken", refreshToken) + ResponseCookie accessCookie = ResponseCookie.from("accessToken", accessToken) .httpOnly(true) .secure(false) // TODO: 배포 시 true .path("/") .maxAge(Duration.ofSeconds(jwtTokenProvider.getRefreshTokenExpiry())) .sameSite("Lax") .build(); - response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); + response.addHeader(HttpHeaders.SET_COOKIE, accessCookie.toString()); - String redirectUrl = UriComponentsBuilder.fromUriString(redirectUri) - .queryParam("accessToken", accessToken) - .build().toUriString(); + ResponseCookie refreshCookie = ResponseCookie.from("refreshToken", refreshToken) + .httpOnly(true) + .secure(false) // TODO: 배포 시 true + .path("/") + .maxAge(Duration.ofSeconds(jwtTokenProvider.getRefreshTokenExpiry())) + .sameSite("Lax") + .build(); + response.addHeader(HttpHeaders.SET_COOKIE, refreshCookie.toString()); - getRedirectStrategy().sendRedirect(request, response, redirectUrl); + getRedirectStrategy().sendRedirect(request, response, redirectUri); } } From 9d40673974e0ff5cb18ce3edc2b6073583d1f396 Mon Sep 17 00:00:00 2001 From: jy000n Date: Tue, 30 Jun 2026 14:44:10 +0900 Subject: [PATCH 27/49] =?UTF-8?q?fix:=20JwtAuthenticationFilter=EC=97=90?= =?UTF-8?q?=EC=84=9C=20accessToken=EC=9D=84=20=EC=BF=A0=ED=82=A4=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=9D=BD=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jwt/filter/JwtAuthenticationFilter.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/Timo/Timo/global/jwt/filter/JwtAuthenticationFilter.java b/src/main/java/com/Timo/Timo/global/jwt/filter/JwtAuthenticationFilter.java index 59688cf..ec0ad2f 100644 --- a/src/main/java/com/Timo/Timo/global/jwt/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/Timo/Timo/global/jwt/filter/JwtAuthenticationFilter.java @@ -5,16 +5,17 @@ import com.Timo.Timo.global.jwt.provider.JwtTokenProvider; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; +import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.Arrays; import java.util.Map; import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; @Component @@ -49,11 +50,13 @@ protected void doFilterInternal( filterChain.doFilter(request, response); } - private String resolveToken(HttpServletRequest request){ - String bearer = request.getHeader("Authorization"); - if(StringUtils.hasText(bearer) && bearer.startsWith("Bearer ")){ - return bearer.substring(7); - } - return null; + private String resolveToken(HttpServletRequest request) { + if (request.getCookies() == null) return null; + + return Arrays.stream(request.getCookies()) + .filter(cookie -> "accessToken".equals(cookie.getName())) + .map(Cookie::getValue) + .findFirst() + .orElse(null); } } From bf208c34efb7eaf279fd6c4692c19283941960eb Mon Sep 17 00:00:00 2001 From: jy000n Date: Tue, 30 Jun 2026 15:07:24 +0900 Subject: [PATCH 28/49] =?UTF-8?q?fix:=20CSRF=20=EA=B3=B5=EA=B2=A9=EC=9D=84?= =?UTF-8?q?=20=EB=B0=A9=EC=A7=80=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=B4=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=EC=BF=A0=ED=82=A4=EC=9D=98=20SameSite?= =?UTF-8?q?=EB=A5=BC=20Strict=EB=A1=9C=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Timo/Timo/global/auth/handler/OAuthSuccessHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java index ddfae61..cc78ad9 100644 --- a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java +++ b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java @@ -45,7 +45,7 @@ public void onAuthenticationSuccess( .secure(false) // TODO: 배포 시 true .path("/") .maxAge(Duration.ofSeconds(jwtTokenProvider.getRefreshTokenExpiry())) - .sameSite("Lax") + .sameSite("Strict") .build(); response.addHeader(HttpHeaders.SET_COOKIE, accessCookie.toString()); @@ -54,7 +54,7 @@ public void onAuthenticationSuccess( .secure(false) // TODO: 배포 시 true .path("/") .maxAge(Duration.ofSeconds(jwtTokenProvider.getRefreshTokenExpiry())) - .sameSite("Lax") + .sameSite("Strict") .build(); response.addHeader(HttpHeaders.SET_COOKIE, refreshCookie.toString()); From 22a203a6175990a837251af4cbbd72b83293dc1b Mon Sep 17 00:00:00 2001 From: jy000n Date: Tue, 30 Jun 2026 15:11:10 +0900 Subject: [PATCH 29/49] =?UTF-8?q?fix:=20=ED=99=98=EA=B2=BD=20=EB=B3=80?= =?UTF-8?q?=EC=88=98=EB=A1=9C=20=EC=BF=A0=ED=82=A4=EC=9D=98=20secure=20?= =?UTF-8?q?=ED=94=8C=EB=9E=98=EA=B7=B8=EB=A5=BC=20=EC=99=B8=EB=B6=80=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=EC=9C=BC=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Timo/Timo/global/auth/handler/OAuthSuccessHandler.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java index cc78ad9..863c1f0 100644 --- a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java +++ b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java @@ -25,6 +25,9 @@ public class OAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { @Value("${app.oauth2.redirect-uri}") private String redirectUri; + @Value("${app.auth.cookie-secure:false}") + private boolean cookieSecure; + @Override public void onAuthenticationSuccess( HttpServletRequest request, @@ -42,7 +45,7 @@ public void onAuthenticationSuccess( ResponseCookie accessCookie = ResponseCookie.from("accessToken", accessToken) .httpOnly(true) - .secure(false) // TODO: 배포 시 true + .secure(cookieSecure) .path("/") .maxAge(Duration.ofSeconds(jwtTokenProvider.getRefreshTokenExpiry())) .sameSite("Strict") @@ -51,7 +54,7 @@ public void onAuthenticationSuccess( ResponseCookie refreshCookie = ResponseCookie.from("refreshToken", refreshToken) .httpOnly(true) - .secure(false) // TODO: 배포 시 true + .secure(cookieSecure) .path("/") .maxAge(Duration.ofSeconds(jwtTokenProvider.getRefreshTokenExpiry())) .sameSite("Strict") From 5de9d41035785e3cf6d9f46ca6067fa0579c54c6 Mon Sep 17 00:00:00 2001 From: jy000n Date: Tue, 30 Jun 2026 15:24:40 +0900 Subject: [PATCH 30/49] =?UTF-8?q?fix:=20=EC=88=9C=ED=99=98=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=EC=9D=84=20=ED=95=B4=EA=B2=B0=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EC=9C=84=ED=95=B4=20JwtAuthenticationEntryPoint=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../handler/JwtAuthenticationEntryPoint.java | 44 +++++++++++++++++++ .../Timo/global/config/SecurityConfig.java | 26 ++--------- 2 files changed, 47 insertions(+), 23 deletions(-) create mode 100644 src/main/java/com/Timo/Timo/global/auth/handler/JwtAuthenticationEntryPoint.java diff --git a/src/main/java/com/Timo/Timo/global/auth/handler/JwtAuthenticationEntryPoint.java b/src/main/java/com/Timo/Timo/global/auth/handler/JwtAuthenticationEntryPoint.java new file mode 100644 index 0000000..c961518 --- /dev/null +++ b/src/main/java/com/Timo/Timo/global/auth/handler/JwtAuthenticationEntryPoint.java @@ -0,0 +1,44 @@ +package com.Timo.Timo.global.auth.handler; + +import com.Timo.Timo.global.exception.code.ErrorCode; +import com.Timo.Timo.global.exception.dto.ErrorDto; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.time.LocalDateTime; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { + + private final ObjectMapper objectMapper; + + @Override + public void commence( + HttpServletRequest request, + HttpServletResponse response, + AuthenticationException authException + ) throws IOException { + + ErrorCode errorCode = ErrorCode.UNAUTHORIZED; + + ErrorDto errorDto = new ErrorDto( + LocalDateTime.now(), + errorCode.getHttpStatus().value(), + errorCode.getCode(), + errorCode.getMessage(), + request.getRequestURI() + ); + + response.setStatus(errorCode.getHttpStatus().value()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding("UTF-8"); + response.getWriter().write(objectMapper.writeValueAsString(errorDto)); + } +} \ No newline at end of file diff --git a/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java b/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java index 57b859a..6cf8e46 100644 --- a/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java +++ b/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java @@ -1,5 +1,6 @@ package com.Timo.Timo.global.config; +import com.Timo.Timo.global.auth.handler.JwtAuthenticationEntryPoint; import com.Timo.Timo.global.auth.handler.OAuthFailureHandler; import com.Timo.Timo.global.auth.handler.OAuthSuccessHandler; import com.Timo.Timo.global.auth.service.CustomOAuth2UserService; @@ -32,7 +33,7 @@ public class SecurityConfig { private final CustomOAuth2UserService customOAuth2UserService; private final OAuthSuccessHandler oAuthSuccessHandler; private final OAuthFailureHandler oAuthFailureHandler; - private final AuthenticationEntryPoint authenticationEntryPoint; + private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; private final JwtAuthenticationFilter jwtAuthenticationFilter; @Bean @@ -63,34 +64,13 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .failureHandler(oAuthFailureHandler) ) .exceptionHandling(exception -> - exception.authenticationEntryPoint(authenticationEntryPoint) + exception.authenticationEntryPoint(jwtAuthenticationEntryPoint) ) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } - @Bean - public AuthenticationEntryPoint authenticationEntryPoint(ObjectMapper objectMapper) { - - return (request, response, authException) -> { - ErrorCode errorCode = ErrorCode.UNAUTHORIZED; - - ErrorDto errorDto = new ErrorDto( - LocalDateTime.now(), - errorCode.getHttpStatus().value(), - errorCode.getCode(), - errorCode.getMessage(), - request.getRequestURI() - ); - - response.setStatus(errorCode.getHttpStatus().value()); - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - response.setCharacterEncoding("UTF-8"); - response.getWriter().write(objectMapper.writeValueAsString(errorDto)); - }; - } - @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); From eabe1d34c2439737cd5fafe3db691381d9e261e8 Mon Sep 17 00:00:00 2001 From: jy000n Date: Tue, 30 Jun 2026 16:48:56 +0900 Subject: [PATCH 31/49] =?UTF-8?q?fix:=20accessToken=EA=B3=BC=20refreshToke?= =?UTF-8?q?n=EC=9D=98=20=EC=BF=A0=ED=82=A4=20maxAge=EB=A5=BC=20=EA=B0=81?= =?UTF-8?q?=EA=B0=81=20=EB=B6=84=EB=A6=AC=ED=95=98=EC=97=AC=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Timo/Timo/global/auth/handler/OAuthSuccessHandler.java | 2 +- .../com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java index 863c1f0..2d94abb 100644 --- a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java +++ b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java @@ -47,7 +47,7 @@ public void onAuthenticationSuccess( .httpOnly(true) .secure(cookieSecure) .path("/") - .maxAge(Duration.ofSeconds(jwtTokenProvider.getRefreshTokenExpiry())) + .maxAge(Duration.ofSeconds(jwtTokenProvider.getAccessTokenExpiry())) .sameSite("Strict") .build(); response.addHeader(HttpHeaders.SET_COOKIE, accessCookie.toString()); diff --git a/src/main/java/com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java b/src/main/java/com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java index 074343f..5e467b1 100644 --- a/src/main/java/com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java +++ b/src/main/java/com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java @@ -63,7 +63,6 @@ private boolean validateToken(String token, String expectedType){ } } - public String getEmail(String token){ return getClaims(token).getSubject(); } @@ -76,6 +75,10 @@ private Claims getClaims(String token){ .getPayload(); } + public long getAccessTokenExpiry() { + return accessTokenExpirySeconds; + } + public long getRefreshTokenExpiry() { return refreshTokenExpirySeconds; } From dcc9ce26dd96ebbbe37a0d2df8ecee17e1165729 Mon Sep 17 00:00:00 2001 From: jy000n Date: Tue, 30 Jun 2026 18:00:40 +0900 Subject: [PATCH 32/49] =?UTF-8?q?chore:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20import=EB=AC=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/Timo/Timo/global/config/SecurityConfig.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java b/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java index 6cf8e46..33ad194 100644 --- a/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java +++ b/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java @@ -4,21 +4,15 @@ import com.Timo.Timo.global.auth.handler.OAuthFailureHandler; import com.Timo.Timo.global.auth.handler.OAuthSuccessHandler; import com.Timo.Timo.global.auth.service.CustomOAuth2UserService; -import com.Timo.Timo.global.exception.code.ErrorCode; -import com.Timo.Timo.global.exception.dto.ErrorDto; import com.Timo.Timo.global.jwt.filter.JwtAuthenticationFilter; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.time.LocalDateTime; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.http.MediaType; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfiguration; From faa733c5628a20a40149e45d8318aec784fcf20a Mon Sep 17 00:00:00 2001 From: jy000n Date: Wed, 1 Jul 2026 00:28:06 +0900 Subject: [PATCH 33/49] =?UTF-8?q?refactor:=20OAuthFailureHandler=20ObjectM?= =?UTF-8?q?apper=20=EC=A7=81=EC=A0=91=20=EC=83=9D=EC=84=B1=20=E2=86=92=20B?= =?UTF-8?q?ean=20=EC=A3=BC=EC=9E=85=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Timo/Timo/global/auth/handler/OAuthFailureHandler.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthFailureHandler.java b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthFailureHandler.java index 8d5ce09..687d30f 100644 --- a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthFailureHandler.java +++ b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthFailureHandler.java @@ -8,16 +8,17 @@ import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.time.LocalDateTime; +import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.stereotype.Component; @Component +@RequiredArgsConstructor public class OAuthFailureHandler extends SimpleUrlAuthenticationFailureHandler { - private final ObjectMapper objectMapper = new ObjectMapper() - .registerModule(new JavaTimeModule()); + private final ObjectMapper objectMapper; @Override public void onAuthenticationFailure( From c1db021317e8e70dfd32e37b0ce71f7aee7b8ff0 Mon Sep 17 00:00:00 2001 From: jy000n Date: Thu, 2 Jul 2026 03:11:03 +0900 Subject: [PATCH 34/49] =?UTF-8?q?fix:=20accessToken=EC=9D=84=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=EB=B3=B8=EB=AC=B8(response=20body)=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=A0=84=EB=8B=AC=ED=95=98=EA=B3=A0=20Authorizatio?= =?UTF-8?q?n=20=ED=97=A4=EB=8D=94=EC=97=90=EC=84=9C=20=EC=9D=BD=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/handler/OAuthSuccessHandler.java | 21 ++++++++----------- .../jwt/filter/JwtAuthenticationFilter.java | 14 ++++++------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java index 2d94abb..8aba122 100644 --- a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java +++ b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java @@ -3,13 +3,15 @@ import com.Timo.Timo.global.auth.dto.CustomUserDetails; import com.Timo.Timo.global.auth.service.RefreshTokenService; import com.Timo.Timo.global.jwt.provider.JwtTokenProvider; +import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.time.Duration; +import java.util.Map; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.http.ResponseCookie; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; @@ -21,6 +23,7 @@ public class OAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { private final JwtTokenProvider jwtTokenProvider; private final RefreshTokenService refreshTokenService; + private final ObjectMapper objectMapper; @Value("${app.oauth2.redirect-uri}") private String redirectUri; @@ -43,15 +46,6 @@ public void onAuthenticationSuccess( refreshTokenService.save(email, refreshToken); - ResponseCookie accessCookie = ResponseCookie.from("accessToken", accessToken) - .httpOnly(true) - .secure(cookieSecure) - .path("/") - .maxAge(Duration.ofSeconds(jwtTokenProvider.getAccessTokenExpiry())) - .sameSite("Strict") - .build(); - response.addHeader(HttpHeaders.SET_COOKIE, accessCookie.toString()); - ResponseCookie refreshCookie = ResponseCookie.from("refreshToken", refreshToken) .httpOnly(true) .secure(cookieSecure) @@ -59,8 +53,11 @@ public void onAuthenticationSuccess( .maxAge(Duration.ofSeconds(jwtTokenProvider.getRefreshTokenExpiry())) .sameSite("Strict") .build(); - response.addHeader(HttpHeaders.SET_COOKIE, refreshCookie.toString()); - getRedirectStrategy().sendRedirect(request, response, redirectUri); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding("UTF-8"); + response.getWriter().write( + objectMapper.writeValueAsString(Map.of("accessToken", accessToken)) + ); } } diff --git a/src/main/java/com/Timo/Timo/global/jwt/filter/JwtAuthenticationFilter.java b/src/main/java/com/Timo/Timo/global/jwt/filter/JwtAuthenticationFilter.java index ec0ad2f..6cdf1df 100644 --- a/src/main/java/com/Timo/Timo/global/jwt/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/Timo/Timo/global/jwt/filter/JwtAuthenticationFilter.java @@ -5,17 +5,16 @@ import com.Timo.Timo.global.jwt.provider.JwtTokenProvider; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; -import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; -import java.util.Arrays; import java.util.Map; import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; @Component @@ -51,12 +50,11 @@ protected void doFilterInternal( } private String resolveToken(HttpServletRequest request) { - if (request.getCookies() == null) return null; - return Arrays.stream(request.getCookies()) - .filter(cookie -> "accessToken".equals(cookie.getName())) - .map(Cookie::getValue) - .findFirst() - .orElse(null); + String bearer = request.getHeader("Authorization"); + if (StringUtils.hasText(bearer) && bearer.startsWith("Bearer ")) { + return bearer.substring(7); + } + return null; } } From f3afcb7fd2d77f63d8675ca4184bd1b52dbc9f8f Mon Sep 17 00:00:00 2001 From: jy000n Date: Thu, 2 Jul 2026 11:19:02 +0900 Subject: [PATCH 35/49] =?UTF-8?q?refactor:=20User=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=EB=B3=80=EC=88=98=EB=AA=85=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?(picture->imageUrl)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/Timo/Timo/domain/user/entity/User.java | 6 +++--- .../Timo/global/auth/service/CustomOAuth2UserService.java | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/Timo/Timo/domain/user/entity/User.java b/src/main/java/com/Timo/Timo/domain/user/entity/User.java index f1e0173..941d83a 100644 --- a/src/main/java/com/Timo/Timo/domain/user/entity/User.java +++ b/src/main/java/com/Timo/Timo/domain/user/entity/User.java @@ -30,15 +30,15 @@ public class User { private String email; private String name; - private String picture; + private String imageUrl; @Enumerated(EnumType.STRING) @Column(nullable = false) private Provider provider; - public void update(String name, String picture){ + public void update(String name, String imageUrl){ this.name = name; - this.picture = picture; + this.imageUrl = imageUrl; } public enum Provider { GOOGLE } diff --git a/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java b/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java index eb4fd8f..3a8b3bb 100644 --- a/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java +++ b/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java @@ -31,7 +31,7 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic Map attributes = oAuth2User.getAttributes(); String email = (String) attributes.get("email"); String name = (String) attributes.get("name"); - String picture = (String) attributes.get("picture"); + String imageUrl = (String) attributes.get("picture"); if (!StringUtils.hasText(email)) { throw new OAuth2AuthenticationException( @@ -42,14 +42,14 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic User user = userRepository.findByEmail(email) .map(existing -> { - existing.update(name, picture); + existing.update(name, imageUrl); return existing; }) .orElseGet(() -> userRepository.save( User.builder() .email(email) .name(name) - .picture(picture) + .imageUrl(imageUrl) .provider(User.Provider.GOOGLE) .build() )); From 88edebbcdca57ed1e1adf3ae8f0a6c2d6fb84998 Mon Sep 17 00:00:00 2001 From: jy000n Date: Thu, 2 Jul 2026 11:27:18 +0900 Subject: [PATCH 36/49] =?UTF-8?q?refactor:=20User=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=EC=9D=98=20Provider=20enum=EC=9D=84=20=EB=B3=84?= =?UTF-8?q?=EB=8F=84=20=ED=8C=8C=EC=9D=BC=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/Timo/Timo/domain/user/enums/Provider.java | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/main/java/com/Timo/Timo/domain/user/enums/Provider.java diff --git a/src/main/java/com/Timo/Timo/domain/user/enums/Provider.java b/src/main/java/com/Timo/Timo/domain/user/enums/Provider.java new file mode 100644 index 0000000..8afa0a1 --- /dev/null +++ b/src/main/java/com/Timo/Timo/domain/user/enums/Provider.java @@ -0,0 +1,5 @@ +package com.Timo.Timo.domain.user.enums; + +public class Provider { + GOOGLE +} From d97344d42c6fbd3fff4e8686787f4e0f6dbb5870 Mon Sep 17 00:00:00 2001 From: jy000n Date: Thu, 2 Jul 2026 11:29:09 +0900 Subject: [PATCH 37/49] =?UTF-8?q?refactor:=20User=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EC=9E=90=EB=A5=BC=20private=20Builder=EB=A1=9C=20=EC=A0=9C?= =?UTF-8?q?=ED=95=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/Timo/Timo/domain/user/entity/User.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/Timo/Timo/domain/user/entity/User.java b/src/main/java/com/Timo/Timo/domain/user/entity/User.java index 941d83a..9f204eb 100644 --- a/src/main/java/com/Timo/Timo/domain/user/entity/User.java +++ b/src/main/java/com/Timo/Timo/domain/user/entity/User.java @@ -1,5 +1,6 @@ package com.Timo.Timo.domain.user.entity; +import com.Timo.Timo.domain.user.enums.Provider; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -9,7 +10,6 @@ import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.AccessLevel; -import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -18,8 +18,6 @@ @Table(name = "users") @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -@Builder -@AllArgsConstructor public class User { @Id @@ -36,10 +34,17 @@ public class User { @Column(nullable = false) private Provider provider; + @Builder + private User(String email, String name, String imageUrl, Provider provider) { + this.email = email; + this.name = name; + this.imageUrl = imageUrl; + this.provider = provider; + } + public void update(String name, String imageUrl){ this.name = name; this.imageUrl = imageUrl; } - public enum Provider { GOOGLE } } From 02a1696f5612342f9083028691eb2de8c257b185 Mon Sep 17 00:00:00 2001 From: jy000n Date: Thu, 2 Jul 2026 11:39:24 +0900 Subject: [PATCH 38/49] =?UTF-8?q?fix:=20enum=20Provider=20=ED=98=95?= =?UTF-8?q?=EC=8B=9D=20enum=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/Timo/Timo/domain/user/enums/Provider.java | 4 ++-- .../Timo/global/auth/service/CustomOAuth2UserService.java | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/Timo/Timo/domain/user/enums/Provider.java b/src/main/java/com/Timo/Timo/domain/user/enums/Provider.java index 8afa0a1..b9e1a3a 100644 --- a/src/main/java/com/Timo/Timo/domain/user/enums/Provider.java +++ b/src/main/java/com/Timo/Timo/domain/user/enums/Provider.java @@ -1,5 +1,5 @@ package com.Timo.Timo.domain.user.enums; -public class Provider { +public enum Provider { GOOGLE -} +} \ No newline at end of file diff --git a/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java b/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java index 3a8b3bb..6685c72 100644 --- a/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java +++ b/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java @@ -1,6 +1,7 @@ package com.Timo.Timo.global.auth.service; import com.Timo.Timo.domain.user.entity.User; +import com.Timo.Timo.domain.user.enums.Provider; import com.Timo.Timo.domain.user.repository.UserRepository; import com.Timo.Timo.global.auth.dto.CustomUserDetails; import com.Timo.Timo.global.exception.code.ErrorCode; @@ -50,7 +51,7 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic .email(email) .name(name) .imageUrl(imageUrl) - .provider(User.Provider.GOOGLE) + .provider(Provider.GOOGLE) .build() )); From 548ef8151602d4da0fc4707f70f0c09f7b7e9600 Mon Sep 17 00:00:00 2001 From: jy000n Date: Thu, 2 Jul 2026 11:48:35 +0900 Subject: [PATCH 39/49] =?UTF-8?q?refactor:=20JWT=20subject=EB=A5=BC=20emai?= =?UTF-8?q?l=EC=97=90=EC=84=9C=20userId=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/auth/handler/OAuthSuccessHandler.java | 8 ++++---- .../global/auth/service/RefreshTokenService.java | 12 ++++++------ .../global/jwt/filter/JwtAuthenticationFilter.java | 4 ++-- .../Timo/global/jwt/provider/JwtTokenProvider.java | 12 ++++++------ 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java index 8aba122..1d1e0e4 100644 --- a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java +++ b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java @@ -39,12 +39,12 @@ public void onAuthenticationSuccess( ) throws IOException { CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); - String email = userDetails.getUser().getEmail(); + Long userId = userDetails.getUser().getId(); - String accessToken = jwtTokenProvider.generateAccessToken(email); - String refreshToken = jwtTokenProvider.generateRefreshToken(email); + String accessToken = jwtTokenProvider.generateAccessToken(userId); + String refreshToken = jwtTokenProvider.generateRefreshToken(userId); - refreshTokenService.save(email, refreshToken); + refreshTokenService.save(String.valueOf(userId), refreshToken); ResponseCookie refreshCookie = ResponseCookie.from("refreshToken", refreshToken) .httpOnly(true) diff --git a/src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java b/src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java index 3292183..ec3b598 100644 --- a/src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java +++ b/src/main/java/com/Timo/Timo/global/auth/service/RefreshTokenService.java @@ -16,9 +16,9 @@ public class RefreshTokenService { private static final String KEY_PREFIX = "refresh:"; - public void save(String email, String refreshToken){ + public void save(String userId, String refreshToken){ redisTemplate.opsForValue().set( - KEY_PREFIX + email, + KEY_PREFIX + userId, refreshToken, jwtTokenProvider.getRefreshTokenExpiry(), TimeUnit.SECONDS @@ -29,11 +29,11 @@ public String get(String email){ return redisTemplate.opsForValue().get(KEY_PREFIX + email); } - public void delete(String email){ - redisTemplate.delete(KEY_PREFIX + email); + public void delete(String userId){ + redisTemplate.delete(KEY_PREFIX + userId); } - public boolean isValid(String email, String refreshToken){ - return Objects.equals(refreshToken, get(email)); + public boolean isValid(String userId, String refreshToken){ + return Objects.equals(refreshToken, get(userId)); } } diff --git a/src/main/java/com/Timo/Timo/global/jwt/filter/JwtAuthenticationFilter.java b/src/main/java/com/Timo/Timo/global/jwt/filter/JwtAuthenticationFilter.java index 6cdf1df..6dc8942 100644 --- a/src/main/java/com/Timo/Timo/global/jwt/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/Timo/Timo/global/jwt/filter/JwtAuthenticationFilter.java @@ -34,8 +34,8 @@ protected void doFilterInternal( String token = resolveToken(request); if(token!=null && jwtTokenProvider.validateAccessToken(token)){ - String email = jwtTokenProvider.getEmail(token); - userRepository.findByEmail(email).ifPresent(user -> { + Long userId = jwtTokenProvider.getUserId(token); + userRepository.findById(userId).ifPresent(user -> { CustomUserDetails userDetails = new CustomUserDetails(user, Map.of()); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( diff --git a/src/main/java/com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java b/src/main/java/com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java index 5e467b1..432dee2 100644 --- a/src/main/java/com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java +++ b/src/main/java/com/Timo/Timo/global/jwt/provider/JwtTokenProvider.java @@ -26,9 +26,9 @@ private SecretKey getSigningKey(){ return Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); } - public String generateAccessToken(String email){ + public String generateAccessToken(Long userId){ return Jwts.builder() - .subject(email) + .subject(String.valueOf(userId)) .claim("type", "access") .issuedAt(new Date()) .expiration(new Date(System.currentTimeMillis() + accessTokenExpirySeconds * 1000)) @@ -36,9 +36,9 @@ public String generateAccessToken(String email){ .compact(); } - public String generateRefreshToken(String email){ + public String generateRefreshToken(Long userId){ return Jwts.builder() - .subject(email) + .subject(String.valueOf(userId)) .claim("type", "refresh") .issuedAt(new Date()) .expiration(new Date(System.currentTimeMillis() + refreshTokenExpirySeconds * 1000)) @@ -63,8 +63,8 @@ private boolean validateToken(String token, String expectedType){ } } - public String getEmail(String token){ - return getClaims(token).getSubject(); + public Long getUserId(String token){ + return Long.parseLong(getClaims(token).getSubject()); } private Claims getClaims(String token){ From 9c320214faf6cd067fccadae7e8ae4be0332a2b1 Mon Sep 17 00:00:00 2001 From: jy000n Date: Thu, 2 Jul 2026 12:12:57 +0900 Subject: [PATCH 40/49] =?UTF-8?q?refactor:=20OAuth2=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9E=90=20=EC=83=9D=EC=84=B1=20=EC=8B=9C=20Provider=20enum=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Timo/global/auth/service/CustomOAuth2UserService.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java b/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java index 6685c72..572f21c 100644 --- a/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java +++ b/src/main/java/com/Timo/Timo/global/auth/service/CustomOAuth2UserService.java @@ -29,6 +29,9 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic OAuth2UserService delegate = new DefaultOAuth2UserService(); OAuth2User oAuth2User = delegate.loadUser(userRequest); + String registrationId = userRequest.getClientRegistration().getRegistrationId(); + Provider provider = Provider.valueOf(registrationId.toUpperCase()); + Map attributes = oAuth2User.getAttributes(); String email = (String) attributes.get("email"); String name = (String) attributes.get("name"); @@ -51,7 +54,7 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic .email(email) .name(name) .imageUrl(imageUrl) - .provider(Provider.GOOGLE) + .provider(provider) .build() )); From 58197dcef49605c92a9399e23982f43a70f8cfb0 Mon Sep 17 00:00:00 2001 From: jy000n Date: Thu, 2 Jul 2026 12:22:35 +0900 Subject: [PATCH 41/49] =?UTF-8?q?fix:=20redirectUri=EB=A1=9C=EC=9D=98=20?= =?UTF-8?q?=EB=A6=AC=EB=8B=A4=EC=9D=B4=EB=A0=89=ED=8A=B8=20=EB=88=84?= =?UTF-8?q?=EB=9D=BD=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java index 1d1e0e4..297d8b4 100644 --- a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java +++ b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java @@ -59,5 +59,7 @@ public void onAuthenticationSuccess( response.getWriter().write( objectMapper.writeValueAsString(Map.of("accessToken", accessToken)) ); + + getRedirectStrategy().sendRedirect(request, response, redirectUri); } } From 33f38ddc7f3efed03a02ead59e5d220059f8c1e3 Mon Sep 17 00:00:00 2001 From: jy000n Date: Thu, 2 Jul 2026 12:24:02 +0900 Subject: [PATCH 42/49] =?UTF-8?q?fix:=20OAuthSuccessHandler=EC=97=90?= =?UTF-8?q?=EC=84=9C=20refreshToken=EC=9D=98=20SET=5FCOOKIE=20=ED=97=A4?= =?UTF-8?q?=EB=8D=94=20=EB=88=84=EB=9D=BD=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java index 297d8b4..6e81b4d 100644 --- a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java +++ b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java @@ -11,6 +11,7 @@ import java.util.Map; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseCookie; import org.springframework.security.core.Authentication; @@ -53,6 +54,7 @@ public void onAuthenticationSuccess( .maxAge(Duration.ofSeconds(jwtTokenProvider.getRefreshTokenExpiry())) .sameSite("Strict") .build(); + response.addHeader(HttpHeaders.SET_COOKIE, refreshCookie.toString()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setCharacterEncoding("UTF-8"); From 52671a5ff3c9020acd2da94992b26cfebcd573cd Mon Sep 17 00:00:00 2001 From: jy000n Date: Thu, 2 Jul 2026 12:47:05 +0900 Subject: [PATCH 43/49] =?UTF-8?q?refactor:=20=EC=A4=91=EB=B3=B5=EB=90=9C?= =?UTF-8?q?=20=EC=97=90=EB=9F=AC=20=EC=9D=91=EB=8B=B5=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0=EB=A5=BC=20=EC=9C=84=ED=95=B4=20AuthError?= =?UTF-8?q?ResponseWriter=20=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/handler/AuthErrorResponseWriter.java | 36 +++++++++++++++++++ .../handler/JwtAuthenticationEntryPoint.java | 22 ++---------- .../auth/handler/OAuthFailureHandler.java | 23 ++---------- 3 files changed, 40 insertions(+), 41 deletions(-) create mode 100644 src/main/java/com/Timo/Timo/global/auth/handler/AuthErrorResponseWriter.java diff --git a/src/main/java/com/Timo/Timo/global/auth/handler/AuthErrorResponseWriter.java b/src/main/java/com/Timo/Timo/global/auth/handler/AuthErrorResponseWriter.java new file mode 100644 index 0000000..67b445f --- /dev/null +++ b/src/main/java/com/Timo/Timo/global/auth/handler/AuthErrorResponseWriter.java @@ -0,0 +1,36 @@ +package com.Timo.Timo.global.auth.handler; + +import com.Timo.Timo.global.exception.code.ErrorCode; +import com.Timo.Timo.global.exception.dto.ErrorDto; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.time.LocalDateTime; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class AuthErrorResponseWriter { + + private final ObjectMapper objectMapper; + + public void write(HttpServletResponse response, ErrorCode errorCode, String path) + throws IOException { + + ErrorDto errorDto = new ErrorDto( + LocalDateTime.now(), + errorCode.getHttpStatus().value(), + errorCode.getCode(), + errorCode.getMessage(), + path + ); + + response.setStatus(errorCode.getHttpStatus().value()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding("UTF-8"); + response.getWriter().write(objectMapper.writeValueAsString(errorDto)); + } + +} diff --git a/src/main/java/com/Timo/Timo/global/auth/handler/JwtAuthenticationEntryPoint.java b/src/main/java/com/Timo/Timo/global/auth/handler/JwtAuthenticationEntryPoint.java index c961518..8a9f2ce 100644 --- a/src/main/java/com/Timo/Timo/global/auth/handler/JwtAuthenticationEntryPoint.java +++ b/src/main/java/com/Timo/Timo/global/auth/handler/JwtAuthenticationEntryPoint.java @@ -1,14 +1,10 @@ package com.Timo.Timo.global.auth.handler; import com.Timo.Timo.global.exception.code.ErrorCode; -import com.Timo.Timo.global.exception.dto.ErrorDto; -import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; -import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; -import org.springframework.http.MediaType; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; @@ -17,7 +13,7 @@ @RequiredArgsConstructor public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { - private final ObjectMapper objectMapper; + private final AuthErrorResponseWriter authErrorResponseWriter; @Override public void commence( @@ -25,20 +21,6 @@ public void commence( HttpServletResponse response, AuthenticationException authException ) throws IOException { - - ErrorCode errorCode = ErrorCode.UNAUTHORIZED; - - ErrorDto errorDto = new ErrorDto( - LocalDateTime.now(), - errorCode.getHttpStatus().value(), - errorCode.getCode(), - errorCode.getMessage(), - request.getRequestURI() - ); - - response.setStatus(errorCode.getHttpStatus().value()); - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - response.setCharacterEncoding("UTF-8"); - response.getWriter().write(objectMapper.writeValueAsString(errorDto)); + authErrorResponseWriter.write(response, ErrorCode.UNAUTHORIZED, request.getRequestURI()); } } \ No newline at end of file diff --git a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthFailureHandler.java b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthFailureHandler.java index 687d30f..99e2f34 100644 --- a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthFailureHandler.java +++ b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthFailureHandler.java @@ -1,15 +1,10 @@ package com.Timo.Timo.global.auth.handler; import com.Timo.Timo.global.exception.code.ErrorCode; -import com.Timo.Timo.global.exception.dto.ErrorDto; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; -import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; -import org.springframework.http.MediaType; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.stereotype.Component; @@ -18,7 +13,7 @@ @RequiredArgsConstructor public class OAuthFailureHandler extends SimpleUrlAuthenticationFailureHandler { - private final ObjectMapper objectMapper; + private final AuthErrorResponseWriter authErrorResponseWriter; @Override public void onAuthenticationFailure( @@ -26,20 +21,6 @@ public void onAuthenticationFailure( HttpServletResponse response, AuthenticationException exception ) throws IOException { - - ErrorCode errorCode = ErrorCode.UNAUTHORIZED; - - ErrorDto errorDto = new ErrorDto( - LocalDateTime.now(), - errorCode.getHttpStatus().value(), - errorCode.getCode(), - errorCode.getMessage(), - request.getRequestURI() - ); - - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - response.setCharacterEncoding("UTF-8"); - response.getWriter().write(objectMapper.writeValueAsString(errorDto)); + authErrorResponseWriter.write(response, ErrorCode.UNAUTHORIZED, request.getRequestURI()); } } From ecb5853769daf3b99302809a0562c250de3c9d69 Mon Sep 17 00:00:00 2001 From: jy000n Date: Thu, 2 Jul 2026 12:50:04 +0900 Subject: [PATCH 44/49] =?UTF-8?q?feat:=20OAUTH=5FLOGIN=5FFAILED=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20OAuthFailureHandler=EC=97=90=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/auth/handler/OAuthFailureHandler.java | 2 +- .../Timo/Timo/global/exception/code/ErrorCode.java | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthFailureHandler.java b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthFailureHandler.java index 99e2f34..8558df7 100644 --- a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthFailureHandler.java +++ b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthFailureHandler.java @@ -21,6 +21,6 @@ public void onAuthenticationFailure( HttpServletResponse response, AuthenticationException exception ) throws IOException { - authErrorResponseWriter.write(response, ErrorCode.UNAUTHORIZED, request.getRequestURI()); + authErrorResponseWriter.write(response, ErrorCode.OAUTH_LOGIN_FAILED, request.getRequestURI()); } } diff --git a/src/main/java/com/Timo/Timo/global/exception/code/ErrorCode.java b/src/main/java/com/Timo/Timo/global/exception/code/ErrorCode.java index 84e8e83..3f21b38 100644 --- a/src/main/java/com/Timo/Timo/global/exception/code/ErrorCode.java +++ b/src/main/java/com/Timo/Timo/global/exception/code/ErrorCode.java @@ -11,11 +11,15 @@ public enum ErrorCode implements BaseErrorCode { BAD_REQUEST(HttpStatus.BAD_REQUEST, "COMMON_400", "잘못된 요청입니다."), UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "COMMON_401", "인증이 필요합니다."), - OAUTH2_INVALID_USER_INFO(HttpStatus.UNAUTHORIZED, "AUTH_401", "OAuth2 유저 정보가 유효하지 않습니다."), FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON_403", "접근 권한이 없습니다."), - NOT_FOUND(HttpStatus.NOT_FOUND, "COMMON_404", "요청한 리소스를 찾을 수 없습니다."), - METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "COMMON_405", "지원하지 않는 HTTP 메서드입니다."), - INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON_500", "서버 내부 오류가 발생했습니다."); + NOT_FOUND(HttpStatus.NOT_FOUND, "COMMON_404", "요청한 리소스를 찾을 수 없습니다."), + METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "COMMON_405", "지원하지 않는 HTTP 메서드입니다."), + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON_500", "서버 내부 오류가 발생했습니다."), + + // Auth + OAUTH2_INVALID_USER_INFO(HttpStatus.UNAUTHORIZED, "AUTH_401", "OAuth2 유저 정보가 유효하지 않습니다."), + OAUTH_LOGIN_FAILED(HttpStatus.UNAUTHORIZED, "AUTH_402", "소셜 로그인에 실패했습니다."), + INVALID_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "AUTH_403", "유효하지 않거나 만료된 리프레시 토큰입니다."); private final HttpStatus httpStatus; private final String code; From 8d5761c3e5d207b968d88a9ad59a1885310f83c9 Mon Sep 17 00:00:00 2001 From: jy000n Date: Thu, 2 Jul 2026 13:13:03 +0900 Subject: [PATCH 45/49] =?UTF-8?q?feat:=20=EC=95=88=EC=A0=84=ED=95=9C=20acc?= =?UTF-8?q?essToken=20=EB=B0=9C=EA=B8=89=EC=9D=84=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=EC=9D=BC=ED=9A=8C=EC=9A=A9=20=EC=9D=B8=EC=A6=9D=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/auth/handler/OAuthSuccessHandler.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java index 6e81b4d..3cb1393 100644 --- a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java +++ b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java @@ -42,9 +42,7 @@ public void onAuthenticationSuccess( CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); Long userId = userDetails.getUser().getId(); - String accessToken = jwtTokenProvider.generateAccessToken(userId); String refreshToken = jwtTokenProvider.generateRefreshToken(userId); - refreshTokenService.save(String.valueOf(userId), refreshToken); ResponseCookie refreshCookie = ResponseCookie.from("refreshToken", refreshToken) @@ -56,11 +54,12 @@ public void onAuthenticationSuccess( .build(); response.addHeader(HttpHeaders.SET_COOKIE, refreshCookie.toString()); - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - response.setCharacterEncoding("UTF-8"); - response.getWriter().write( - objectMapper.writeValueAsString(Map.of("accessToken", accessToken)) - ); + // 1회성 code 생성 → URL 파라미터로 전달 + String code = authCodeService.generateAndSave(String.valueOf(userId)); + + String redirectUrl = UriComponentsBuilder.fromUriString(redirectUri) + .queryParam("code", code) + .build().toUriString(); getRedirectStrategy().sendRedirect(request, response, redirectUri); } From 770cfa094f5d547e5a3d8b930d030c969a96e45b Mon Sep 17 00:00:00 2001 From: jy000n Date: Thu, 2 Jul 2026 13:39:17 +0900 Subject: [PATCH 46/49] =?UTF-8?q?feat:=20=EC=95=88=EC=A0=84=ED=95=9C=20acc?= =?UTF-8?q?essToken=20=EB=B0=9C=EA=B8=89=EC=9D=84=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=EC=9D=BC=ED=9A=8C=EC=9A=A9=20=EC=9D=B8=EC=A6=9D=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/handler/OAuthSuccessHandler.java | 9 ++--- .../global/auth/service/AuthCodeService.java | 37 +++++++++++++++++++ 2 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/Timo/Timo/global/auth/service/AuthCodeService.java diff --git a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java index 3cb1393..f93e750 100644 --- a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java +++ b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java @@ -1,6 +1,7 @@ package com.Timo.Timo.global.auth.handler; import com.Timo.Timo.global.auth.dto.CustomUserDetails; +import com.Timo.Timo.global.auth.service.AuthCodeService; import com.Timo.Timo.global.auth.service.RefreshTokenService; import com.Timo.Timo.global.jwt.provider.JwtTokenProvider; import com.fasterxml.jackson.databind.ObjectMapper; @@ -8,15 +9,14 @@ import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.time.Duration; -import java.util.Map; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; import org.springframework.http.ResponseCookie; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; +import org.springframework.web.util.UriComponentsBuilder; @Component @RequiredArgsConstructor @@ -24,7 +24,7 @@ public class OAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { private final JwtTokenProvider jwtTokenProvider; private final RefreshTokenService refreshTokenService; - private final ObjectMapper objectMapper; + private final AuthCodeService authCodeService; @Value("${app.oauth2.redirect-uri}") private String redirectUri; @@ -54,13 +54,12 @@ public void onAuthenticationSuccess( .build(); response.addHeader(HttpHeaders.SET_COOKIE, refreshCookie.toString()); - // 1회성 code 생성 → URL 파라미터로 전달 String code = authCodeService.generateAndSave(String.valueOf(userId)); String redirectUrl = UriComponentsBuilder.fromUriString(redirectUri) .queryParam("code", code) .build().toUriString(); - getRedirectStrategy().sendRedirect(request, response, redirectUri); + getRedirectStrategy().sendRedirect(request, response, redirectUrl); } } diff --git a/src/main/java/com/Timo/Timo/global/auth/service/AuthCodeService.java b/src/main/java/com/Timo/Timo/global/auth/service/AuthCodeService.java new file mode 100644 index 0000000..852673e --- /dev/null +++ b/src/main/java/com/Timo/Timo/global/auth/service/AuthCodeService.java @@ -0,0 +1,37 @@ +package com.Timo.Timo.global.auth.service; + +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class AuthCodeService { + + private final RedisTemplate redisTemplate; + + private static final String KEY_PREFIX = "auth:code:"; + private static final long CODE_EXPIRY_SECONDS = 30L; + + public String generateAndSave(String userId) { + String code = UUID.randomUUID().toString(); + redisTemplate.opsForValue().set( + KEY_PREFIX + code, + userId, + CODE_EXPIRY_SECONDS, + TimeUnit.SECONDS + ); + return code; + } + + public String getAndDelete(String code) { + String key = KEY_PREFIX + code; + String userId = redisTemplate.opsForValue().get(key); + if (userId != null) { + redisTemplate.delete(key); + } + return userId; + } +} \ No newline at end of file From 18d23a3bde5b4f82a187b40743a51f189899be86 Mon Sep 17 00:00:00 2001 From: jy000n Date: Thu, 2 Jul 2026 13:47:54 +0900 Subject: [PATCH 47/49] =?UTF-8?q?feat:=20=EC=9D=BC=ED=9A=8C=EC=9A=A9=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=EC=BD=94=EB=93=9C=EB=A1=9C=20accessToken?= =?UTF-8?q?=EC=9D=84=20=EB=B0=9C=EA=B8=89=ED=95=98=EB=8A=94=20GET=20/api/a?= =?UTF-8?q?uth/token=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/controller/AuthController.java | 35 +++++++++++++++++++ .../auth/handler/OAuthSuccessHandler.java | 1 - .../Timo/global/config/SecurityConfig.java | 3 +- 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/Timo/Timo/domain/user/controller/AuthController.java diff --git a/src/main/java/com/Timo/Timo/domain/user/controller/AuthController.java b/src/main/java/com/Timo/Timo/domain/user/controller/AuthController.java new file mode 100644 index 0000000..c721ed4 --- /dev/null +++ b/src/main/java/com/Timo/Timo/domain/user/controller/AuthController.java @@ -0,0 +1,35 @@ +package com.Timo.Timo.domain.user.controller; + +import com.Timo.Timo.global.auth.service.AuthCodeService; +import com.Timo.Timo.global.jwt.provider.JwtTokenProvider; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/auth") +@RequiredArgsConstructor +public class AuthController { + + private final AuthCodeService authCodeService; + private final JwtTokenProvider jwtTokenProvider; + + @GetMapping("/token") + public ResponseEntity> token( + @RequestParam String code + ) { + String userId = authCodeService.getAndDelete(code); + + if (userId == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + + String accessToken = jwtTokenProvider.generateAccessToken(Long.parseLong(userId)); + return ResponseEntity.ok(Map.of("accessToken", accessToken)); + } +} \ No newline at end of file diff --git a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java index f93e750..58b72ae 100644 --- a/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java +++ b/src/main/java/com/Timo/Timo/global/auth/handler/OAuthSuccessHandler.java @@ -4,7 +4,6 @@ import com.Timo.Timo.global.auth.service.AuthCodeService; import com.Timo.Timo.global.auth.service.RefreshTokenService; import com.Timo.Timo.global.jwt.provider.JwtTokenProvider; -import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; diff --git a/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java b/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java index 33ad194..1fdad75 100644 --- a/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java +++ b/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java @@ -47,7 +47,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti "/v3/api-docs/**", "/login/**", "/oauth2/**", - "/api/auth/reissue" + "/api/auth/reissue", + "/api/auth/token" ).permitAll() .anyRequest().authenticated()) .oauth2Login(oauth2 -> oauth2 From 56893ba4fdb4e0fde34a8eeb93824b2472676f85 Mon Sep 17 00:00:00 2001 From: jy000n Date: Thu, 2 Jul 2026 14:27:31 +0900 Subject: [PATCH 48/49] =?UTF-8?q?fix:=20=EC=9D=B8=EC=A6=9D=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=A1=B0=ED=9A=8C/=EC=82=AD=EC=A0=9C=20=EC=9B=90?= =?UTF-8?q?=EC=9E=90=EC=A0=81=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/Timo/Timo/global/auth/service/AuthCodeService.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/java/com/Timo/Timo/global/auth/service/AuthCodeService.java b/src/main/java/com/Timo/Timo/global/auth/service/AuthCodeService.java index 852673e..e9810c0 100644 --- a/src/main/java/com/Timo/Timo/global/auth/service/AuthCodeService.java +++ b/src/main/java/com/Timo/Timo/global/auth/service/AuthCodeService.java @@ -27,11 +27,6 @@ public String generateAndSave(String userId) { } public String getAndDelete(String code) { - String key = KEY_PREFIX + code; - String userId = redisTemplate.opsForValue().get(key); - if (userId != null) { - redisTemplate.delete(key); - } - return userId; + return redisTemplate.opsForValue().getAndDelete(KEY_PREFIX + code); } } \ No newline at end of file From f2adfd0f62edf1f9e8225fd358cfa642948a4aec Mon Sep 17 00:00:00 2001 From: jy000n Date: Thu, 2 Jul 2026 14:34:16 +0900 Subject: [PATCH 49/49] =?UTF-8?q?fix:=20AuthController=EC=97=90=EC=84=9C?= =?UTF-8?q?=20AuthErrorResponseWriter=EB=A5=BC=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=97=90=EB=9F=AC=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=ED=98=95=EC=8B=9D=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/controller/AuthController.java | 35 ------------- .../auth/controller/AuthController.java | 51 +++++++++++++++++++ .../Timo/global/exception/code/ErrorCode.java | 3 +- 3 files changed, 53 insertions(+), 36 deletions(-) delete mode 100644 src/main/java/com/Timo/Timo/domain/user/controller/AuthController.java create mode 100644 src/main/java/com/Timo/Timo/global/auth/controller/AuthController.java diff --git a/src/main/java/com/Timo/Timo/domain/user/controller/AuthController.java b/src/main/java/com/Timo/Timo/domain/user/controller/AuthController.java deleted file mode 100644 index c721ed4..0000000 --- a/src/main/java/com/Timo/Timo/domain/user/controller/AuthController.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.Timo.Timo.domain.user.controller; - -import com.Timo.Timo.global.auth.service.AuthCodeService; -import com.Timo.Timo.global.jwt.provider.JwtTokenProvider; -import java.util.Map; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/api/auth") -@RequiredArgsConstructor -public class AuthController { - - private final AuthCodeService authCodeService; - private final JwtTokenProvider jwtTokenProvider; - - @GetMapping("/token") - public ResponseEntity> token( - @RequestParam String code - ) { - String userId = authCodeService.getAndDelete(code); - - if (userId == null) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); - } - - String accessToken = jwtTokenProvider.generateAccessToken(Long.parseLong(userId)); - return ResponseEntity.ok(Map.of("accessToken", accessToken)); - } -} \ No newline at end of file diff --git a/src/main/java/com/Timo/Timo/global/auth/controller/AuthController.java b/src/main/java/com/Timo/Timo/global/auth/controller/AuthController.java new file mode 100644 index 0000000..aac9342 --- /dev/null +++ b/src/main/java/com/Timo/Timo/global/auth/controller/AuthController.java @@ -0,0 +1,51 @@ +package com.Timo.Timo.global.auth.controller; + +import com.Timo.Timo.global.auth.handler.AuthErrorResponseWriter; +import com.Timo.Timo.global.auth.service.AuthCodeService; +import com.Timo.Timo.global.exception.code.ErrorCode; +import com.Timo.Timo.global.jwt.provider.JwtTokenProvider; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/auth") +@RequiredArgsConstructor +public class AuthController { + + private final AuthCodeService authCodeService; + private final JwtTokenProvider jwtTokenProvider; + private final AuthErrorResponseWriter authErrorResponseWriter; + private final ObjectMapper objectMapper; + + @GetMapping("/token") + public void token( + @RequestParam String code, + HttpServletRequest request, + HttpServletResponse response + ) throws IOException { + + String userId = authCodeService.getAndDelete(code); + + if (userId == null) { + authErrorResponseWriter.write(response, ErrorCode.INVALID_AUTH_CODE, request.getRequestURI()); + return; + } + + String accessToken = jwtTokenProvider.generateAccessToken(Long.parseLong(userId)); + + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding("UTF-8"); + response.getWriter().write( + objectMapper.writeValueAsString(Map.of("accessToken", accessToken)) + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/Timo/Timo/global/exception/code/ErrorCode.java b/src/main/java/com/Timo/Timo/global/exception/code/ErrorCode.java index 3f21b38..948d834 100644 --- a/src/main/java/com/Timo/Timo/global/exception/code/ErrorCode.java +++ b/src/main/java/com/Timo/Timo/global/exception/code/ErrorCode.java @@ -19,7 +19,8 @@ public enum ErrorCode implements BaseErrorCode { // Auth OAUTH2_INVALID_USER_INFO(HttpStatus.UNAUTHORIZED, "AUTH_401", "OAuth2 유저 정보가 유효하지 않습니다."), OAUTH_LOGIN_FAILED(HttpStatus.UNAUTHORIZED, "AUTH_402", "소셜 로그인에 실패했습니다."), - INVALID_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "AUTH_403", "유효하지 않거나 만료된 리프레시 토큰입니다."); + INVALID_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "AUTH_403", "유효하지 않거나 만료된 리프레시 토큰입니다."), + INVALID_AUTH_CODE(HttpStatus.UNAUTHORIZED, "AUTH_404", "유효하지 않거나 만료된 인증 코드입니다."); private final HttpStatus httpStatus; private final String code;