From 1cf9d336fbe4e0bffae76220a4b1394528517bcb Mon Sep 17 00:00:00 2001 From: WooJJam <111514410+WooJJam@users.noreply.github.com> Date: Fri, 29 May 2026 17:26:06 +0900 Subject: [PATCH 01/12] =?UTF-8?q?chore:=20spring-boot-starter-security=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 --- application/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/application/build.gradle b/application/build.gradle index b831cbe..86034f2 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -21,6 +21,7 @@ dependencies { // Spring Boot implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-aop' implementation 'org.springframework.boot:spring-boot-starter-validation' From c3a69e7bd588ea929eb43a34d794cb854b25937f Mon Sep 17 00:00:00 2001 From: WooJJam <111514410+WooJJam@users.noreply.github.com> Date: Fri, 29 May 2026 17:26:09 +0900 Subject: [PATCH 02/12] =?UTF-8?q?refactor:=20JWT=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=EB=A5=BC=20Spring=20Security=20=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=EC=9C=BC=EB=A1=9C=20=EA=B5=90=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/component/JwtTokenProvider.java | 15 ++++ .../filter/CurrentUuidArgumentResolver.java | 41 +++++------ .../auth/filter/JwtAuthenticationFilter.java | 70 ++++++++++++------- 3 files changed, 79 insertions(+), 47 deletions(-) diff --git a/application/src/main/java/com/yapp/ndgl/application/domains/auth/component/JwtTokenProvider.java b/application/src/main/java/com/yapp/ndgl/application/domains/auth/component/JwtTokenProvider.java index 975bc46..c5f7042 100644 --- a/application/src/main/java/com/yapp/ndgl/application/domains/auth/component/JwtTokenProvider.java +++ b/application/src/main/java/com/yapp/ndgl/application/domains/auth/component/JwtTokenProvider.java @@ -1,6 +1,7 @@ package com.yapp.ndgl.application.domains.auth.component; import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import org.springframework.beans.factory.annotation.Value; @@ -56,4 +57,18 @@ public boolean validateToken(String token) { return false; } } + + public boolean isExpired(final String token) { + try { + Jwts.parser() + .verifyWith(secretKey) + .build() + .parseSignedClaims(token); + return false; + } catch (ExpiredJwtException e) { + return true; + } catch (Exception e) { + return false; + } + } } diff --git a/application/src/main/java/com/yapp/ndgl/application/domains/auth/filter/CurrentUuidArgumentResolver.java b/application/src/main/java/com/yapp/ndgl/application/domains/auth/filter/CurrentUuidArgumentResolver.java index 75697a3..8355c39 100644 --- a/application/src/main/java/com/yapp/ndgl/application/domains/auth/filter/CurrentUuidArgumentResolver.java +++ b/application/src/main/java/com/yapp/ndgl/application/domains/auth/filter/CurrentUuidArgumentResolver.java @@ -3,8 +3,10 @@ import com.yapp.ndgl.application.domains.auth.annotation.CurrentUuid; import com.yapp.ndgl.common.exception.CommonErrorCode; import com.yapp.ndgl.common.exception.GlobalException; -import jakarta.servlet.http.HttpServletRequest; import org.springframework.core.MethodParameter; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; @@ -14,28 +16,23 @@ @Component public class CurrentUuidArgumentResolver implements HandlerMethodArgumentResolver { - private static final String UUID_ATTRIBUTE = "uuid"; + @Override + public boolean supportsParameter(final MethodParameter parameter) { + return parameter.hasParameterAnnotation(CurrentUuid.class) && + parameter.getParameterType().equals(String.class); + } - @Override - public boolean supportsParameter(MethodParameter parameter) { - return parameter.hasParameterAnnotation(CurrentUuid.class) && - parameter.getParameterType().equals(String.class); - } + @Override + public Object resolveArgument( + final MethodParameter parameter, + final ModelAndViewContainer mavContainer, + final NativeWebRequest webRequest, + final WebDataBinderFactory binderFactory) { - @Override - public Object resolveArgument( - MethodParameter parameter, - ModelAndViewContainer mavContainer, - NativeWebRequest webRequest, - WebDataBinderFactory binderFactory) { - HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); - if (request != null) { - String uuid = (String) request.getAttribute(UUID_ATTRIBUTE); - if (uuid == null) { - throw new GlobalException(CommonErrorCode.UNAUTHORIZED); - } - return uuid; + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null || authentication instanceof AnonymousAuthenticationToken) { + throw new GlobalException(CommonErrorCode.UNAUTHORIZED); + } + return (String) authentication.getPrincipal(); } - throw new GlobalException(CommonErrorCode.UNAUTHORIZED); - } } diff --git a/application/src/main/java/com/yapp/ndgl/application/domains/auth/filter/JwtAuthenticationFilter.java b/application/src/main/java/com/yapp/ndgl/application/domains/auth/filter/JwtAuthenticationFilter.java index a290184..ce36a42 100644 --- a/application/src/main/java/com/yapp/ndgl/application/domains/auth/filter/JwtAuthenticationFilter.java +++ b/application/src/main/java/com/yapp/ndgl/application/domains/auth/filter/JwtAuthenticationFilter.java @@ -1,50 +1,70 @@ package com.yapp.ndgl.application.domains.auth.filter; +import com.fasterxml.jackson.databind.ObjectMapper; import com.yapp.ndgl.application.domains.auth.component.JwtTokenProvider; +import com.yapp.ndgl.common.exception.CommonErrorCode; +import com.yapp.ndgl.common.response.ErrorResponse; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; -import org.springframework.core.annotation.Order; -import org.springframework.stereotype.Component; +import org.springframework.http.MediaType; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; +import java.util.Collections; -@Component -@Order(10) @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { - private static final String AUTHORIZATION_HEADER = "Authorization"; - private static final String BEARER_PREFIX = "Bearer "; - private static final String UUID_ATTRIBUTE = "uuid"; + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String BEARER_PREFIX = "Bearer "; - private final JwtTokenProvider jwtTokenProvider; + private final JwtTokenProvider jwtTokenProvider; + private final ObjectMapper objectMapper; - @Override - protected void doFilterInternal( - HttpServletRequest request, - HttpServletResponse response, - FilterChain filterChain) throws ServletException, IOException { + @Override + protected void doFilterInternal( + final HttpServletRequest request, + final HttpServletResponse response, + final FilterChain filterChain) throws ServletException, IOException { - String token = resolveToken(request); + String token = resolveToken(request); - if (StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) { - String uuid = jwtTokenProvider.getUuidFromToken(token); - request.setAttribute(UUID_ATTRIBUTE, uuid); + if (StringUtils.hasText(token)) { + if (jwtTokenProvider.isExpired(token)) { + sendErrorResponse(response, CommonErrorCode.EXPIRED_TOKEN); + return; + } + if (!jwtTokenProvider.validateToken(token)) { + sendErrorResponse(response, CommonErrorCode.INVALID_TOKEN); + return; + } + String uuid = jwtTokenProvider.getUuidFromToken(token); + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken(uuid, null, Collections.emptyList()); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + + filterChain.doFilter(request, response); } - filterChain.doFilter(request, response); - } + private void sendErrorResponse(final HttpServletResponse response, final CommonErrorCode errorCode) throws IOException { + SecurityContextHolder.clearContext(); + response.setStatus(errorCode.getStatusCode().getCode()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8"); + objectMapper.writeValue(response.getWriter(), ErrorResponse.error(errorCode)); + } - private String resolveToken(HttpServletRequest request) { - String bearerToken = request.getHeader(AUTHORIZATION_HEADER); - if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(BEARER_PREFIX)) { - return bearerToken.substring(BEARER_PREFIX.length()); + private String resolveToken(final HttpServletRequest request) { + String bearerToken = request.getHeader(AUTHORIZATION_HEADER); + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(BEARER_PREFIX)) { + return bearerToken.substring(BEARER_PREFIX.length()); + } + return null; } - return null; - } } From 062890c0f0e72c168a095fa9cc8e6132bf28cfe7 Mon Sep 17 00:00:00 2001 From: WooJJam <111514410+WooJJam@users.noreply.github.com> Date: Fri, 29 May 2026 17:26:12 +0900 Subject: [PATCH 03/12] =?UTF-8?q?feat:=20SecurityConfig=20=EB=B0=8F=20Cust?= =?UTF-8?q?omAuthenticationEntryPoint=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CustomAuthenticationEntryPoint.java | 33 +++++++++ .../application/config/SecurityConfig.java | 73 +++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 application/src/main/java/com/yapp/ndgl/application/config/CustomAuthenticationEntryPoint.java create mode 100644 application/src/main/java/com/yapp/ndgl/application/config/SecurityConfig.java diff --git a/application/src/main/java/com/yapp/ndgl/application/config/CustomAuthenticationEntryPoint.java b/application/src/main/java/com/yapp/ndgl/application/config/CustomAuthenticationEntryPoint.java new file mode 100644 index 0000000..0b2628b --- /dev/null +++ b/application/src/main/java/com/yapp/ndgl/application/config/CustomAuthenticationEntryPoint.java @@ -0,0 +1,33 @@ +package com.yapp.ndgl.application.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.yapp.ndgl.common.exception.CommonErrorCode; +import com.yapp.ndgl.common.response.ErrorResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +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; + +import java.io.IOException; + +@Component +@RequiredArgsConstructor +public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { + + private final ObjectMapper objectMapper; + + @Override + public void commence( + final HttpServletRequest request, + final HttpServletResponse response, + final AuthenticationException authException) throws IOException { + + CommonErrorCode errorCode = CommonErrorCode.UNAUTHORIZED; + response.setStatus(errorCode.getStatusCode().getCode()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8"); + objectMapper.writeValue(response.getWriter(), ErrorResponse.error(errorCode)); + } +} diff --git a/application/src/main/java/com/yapp/ndgl/application/config/SecurityConfig.java b/application/src/main/java/com/yapp/ndgl/application/config/SecurityConfig.java new file mode 100644 index 0000000..7fce67d --- /dev/null +++ b/application/src/main/java/com/yapp/ndgl/application/config/SecurityConfig.java @@ -0,0 +1,73 @@ +package com.yapp.ndgl.application.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.yapp.ndgl.application.domains.auth.component.JwtTokenProvider; +import com.yapp.ndgl.application.domains.auth.filter.JwtAuthenticationFilter; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +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; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig { + + private static final String[] PUBLIC_PATHS = { + "/api/v1/auth/**", // 기존 v1 인증 (하위 호환) + "/api/v2/auth/social/**", // v2 소셜 로그인 + "/api/v2/auth/reissue", // v2 토큰 재발급 + "/swagger", + "/swagger-ui/**", + "/api-docs/**", + "/v3/api-docs/**", + "/admin/**", // AdminAuthInterceptor가 별도 토큰 검증 + "/actuator/**" + }; + + private final JwtTokenProvider jwtTokenProvider; + private final ObjectMapper objectMapper; + private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint; + + @Bean + public JwtAuthenticationFilter jwtAuthenticationFilter() { + return new JwtAuthenticationFilter(jwtTokenProvider, objectMapper); + } + + @Bean + public FilterRegistrationBean jwtFilterRegistration( + final JwtAuthenticationFilter jwtAuthenticationFilter) { + FilterRegistrationBean registration = + new FilterRegistrationBean<>(jwtAuthenticationFilter); + registration.setEnabled(false); + return registration; + } + + @Bean + public SecurityFilterChain securityFilterChain( + final HttpSecurity http, + final JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception { + http + .csrf(AbstractHttpConfigurer::disable) + .httpBasic(AbstractHttpConfigurer::disable) + .formLogin(AbstractHttpConfigurer::disable) + .sessionManagement(session -> + session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> auth + .requestMatchers(PUBLIC_PATHS).permitAll() + .anyRequest().authenticated() + ) + .exceptionHandling(ex -> + ex.authenticationEntryPoint(customAuthenticationEntryPoint) + ) + .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } +} From 1d6e53204ae98757f771ac09a8a8be4f6545236b Mon Sep 17 00:00:00 2001 From: WooJJam <111514410+WooJJam@users.noreply.github.com> Date: Sat, 30 May 2026 00:21:51 +0900 Subject: [PATCH 04/12] =?UTF-8?q?=20fix:=20MdcFilter=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9E=90=20ID=20=EC=A1=B0=ED=9A=8C=EB=A5=BC=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20=EC=BB=A8=ED=85=8D=EC=8A=A4=ED=8A=B8=20=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/common/logging/MdcFilter.java | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/application/src/main/java/com/yapp/ndgl/application/common/logging/MdcFilter.java b/application/src/main/java/com/yapp/ndgl/application/common/logging/MdcFilter.java index 78f5636..1283468 100644 --- a/application/src/main/java/com/yapp/ndgl/application/common/logging/MdcFilter.java +++ b/application/src/main/java/com/yapp/ndgl/application/common/logging/MdcFilter.java @@ -1,20 +1,24 @@ package com.yapp.ndgl.application.common.logging; import java.io.IOException; -import java.util.UUID; import java.util.List; +import java.util.UUID; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.extern.slf4j.Slf4j; import org.slf4j.MDC; import org.springframework.core.annotation.Order; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; + @Slf4j @Component @Order(20) @@ -38,12 +42,12 @@ public class MdcFilter extends OncePerRequestFilter { @Override protected void doFilterInternal( - HttpServletRequest request, - HttpServletResponse response, - FilterChain filterChain + final HttpServletRequest request, + final HttpServletResponse response, + final FilterChain filterChain ) throws ServletException, IOException { String requestId = resolveRequestId(request); - String userId = resolveUserId(request); + String userId = resolveUserId(); String clientIp = resolveClientIp(request); MDC.put(REQUEST_ID_KEY, requestId); @@ -71,12 +75,13 @@ private String resolveRequestId(HttpServletRequest request) { return UUID.randomUUID().toString(); } - private String resolveUserId(HttpServletRequest request) { - Object attribute = request.getAttribute(USER_ID_ATTRIBUTE); - if (attribute instanceof String userId && StringUtils.hasText(userId)) { - return userId; + private String resolveUserId() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null || authentication instanceof AnonymousAuthenticationToken) { + return "anonymous"; } - return "anonymous"; + + return authentication.getPrincipal().toString(); } private String resolveClientIp(HttpServletRequest request) { From 9cfd691faa33763759fc6648316d3901670ccc1e Mon Sep 17 00:00:00 2001 From: WooJJam <111514410+WooJJam@users.noreply.github.com> Date: Sat, 30 May 2026 00:41:30 +0900 Subject: [PATCH 05/12] =?UTF-8?q?refactor:=20JWT=20=ED=8C=8C=EC=8B=B1?= =?UTF-8?q?=EC=9D=84=20=EB=8B=A8=EC=9D=BC=20parseClaims()=EB=A1=9C=20?= =?UTF-8?q?=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/component/JwtTokenProvider.java | 35 ++----------------- .../auth/filter/JwtAuthenticationFilter.java | 17 +++++---- 2 files changed, 13 insertions(+), 39 deletions(-) diff --git a/application/src/main/java/com/yapp/ndgl/application/domains/auth/component/JwtTokenProvider.java b/application/src/main/java/com/yapp/ndgl/application/domains/auth/component/JwtTokenProvider.java index c5f7042..ab3daa7 100644 --- a/application/src/main/java/com/yapp/ndgl/application/domains/auth/component/JwtTokenProvider.java +++ b/application/src/main/java/com/yapp/ndgl/application/domains/auth/component/JwtTokenProvider.java @@ -1,7 +1,6 @@ package com.yapp.ndgl.application.domains.auth.component; import io.jsonwebtoken.Claims; -import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import org.springframework.beans.factory.annotation.Value; @@ -24,7 +23,7 @@ public JwtTokenProvider( this.expiration = expiration; } - public String generateToken(String uuid) { + public String generateToken(final String uuid) { Date now = new Date(); Date expiryDate = new Date(now.getTime() + expiration); @@ -36,39 +35,11 @@ public String generateToken(String uuid) { .compact(); } - public String getUuidFromToken(String token) { - Claims claims = Jwts.parser() + public Claims parseClaims(final String token) { + return Jwts.parser() .verifyWith(secretKey) .build() .parseSignedClaims(token) .getPayload(); - - return claims.getSubject(); - } - - public boolean validateToken(String token) { - try { - Jwts.parser() - .verifyWith(secretKey) - .build() - .parseSignedClaims(token); - return true; - } catch (Exception e) { - return false; - } - } - - public boolean isExpired(final String token) { - try { - Jwts.parser() - .verifyWith(secretKey) - .build() - .parseSignedClaims(token); - return false; - } catch (ExpiredJwtException e) { - return true; - } catch (Exception e) { - return false; - } } } diff --git a/application/src/main/java/com/yapp/ndgl/application/domains/auth/filter/JwtAuthenticationFilter.java b/application/src/main/java/com/yapp/ndgl/application/domains/auth/filter/JwtAuthenticationFilter.java index ce36a42..2de8516 100644 --- a/application/src/main/java/com/yapp/ndgl/application/domains/auth/filter/JwtAuthenticationFilter.java +++ b/application/src/main/java/com/yapp/ndgl/application/domains/auth/filter/JwtAuthenticationFilter.java @@ -4,6 +4,8 @@ import com.yapp.ndgl.application.domains.auth.component.JwtTokenProvider; import com.yapp.ndgl.common.exception.CommonErrorCode; import com.yapp.ndgl.common.response.ErrorResponse; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -36,18 +38,19 @@ protected void doFilterInternal( String token = resolveToken(request); if (StringUtils.hasText(token)) { - if (jwtTokenProvider.isExpired(token)) { + try { + Claims claims = jwtTokenProvider.parseClaims(token); + String uuid = claims.getSubject(); + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken(uuid, null, Collections.emptyList()); + SecurityContextHolder.getContext().setAuthentication(authentication); + } catch (ExpiredJwtException e) { sendErrorResponse(response, CommonErrorCode.EXPIRED_TOKEN); return; - } - if (!jwtTokenProvider.validateToken(token)) { + } catch (Exception e) { sendErrorResponse(response, CommonErrorCode.INVALID_TOKEN); return; } - String uuid = jwtTokenProvider.getUuidFromToken(token); - UsernamePasswordAuthenticationToken authentication = - new UsernamePasswordAuthenticationToken(uuid, null, Collections.emptyList()); - SecurityContextHolder.getContext().setAuthentication(authentication); } filterChain.doFilter(request, response); From 658a737628774946450ded5fa530aea3ae56f790 Mon Sep 17 00:00:00 2001 From: WooJJam <111514410+WooJJam@users.noreply.github.com> Date: Sat, 30 May 2026 00:41:33 +0900 Subject: [PATCH 06/12] =?UTF-8?q?refactor:=20getPrincipal()=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EC=95=88=EC=A0=84=20=EC=B2=98=EB=A6=AC=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domains/auth/filter/CurrentUuidArgumentResolver.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/com/yapp/ndgl/application/domains/auth/filter/CurrentUuidArgumentResolver.java b/application/src/main/java/com/yapp/ndgl/application/domains/auth/filter/CurrentUuidArgumentResolver.java index 8355c39..f87e06a 100644 --- a/application/src/main/java/com/yapp/ndgl/application/domains/auth/filter/CurrentUuidArgumentResolver.java +++ b/application/src/main/java/com/yapp/ndgl/application/domains/auth/filter/CurrentUuidArgumentResolver.java @@ -33,6 +33,9 @@ public Object resolveArgument( if (authentication == null || authentication instanceof AnonymousAuthenticationToken) { throw new GlobalException(CommonErrorCode.UNAUTHORIZED); } - return (String) authentication.getPrincipal(); + if (!(authentication.getPrincipal() instanceof String uuid)) { + throw new GlobalException(CommonErrorCode.UNAUTHORIZED); + } + return uuid; } } From 8731b400c0b87c1e3558b323aac5e40bf951e606 Mon Sep 17 00:00:00 2001 From: WooJJam <111514410+WooJJam@users.noreply.github.com> Date: Sat, 30 May 2026 01:08:44 +0900 Subject: [PATCH 07/12] =?UTF-8?q?feat:=20JWT=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=EC=8A=A4=EB=B3=84=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80=20(COMM-05-004~006)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yapp/ndgl/common/exception/CommonErrorCode.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/common/src/main/java/com/yapp/ndgl/common/exception/CommonErrorCode.java b/common/src/main/java/com/yapp/ndgl/common/exception/CommonErrorCode.java index 18e03fc..085baef 100644 --- a/common/src/main/java/com/yapp/ndgl/common/exception/CommonErrorCode.java +++ b/common/src/main/java/com/yapp/ndgl/common/exception/CommonErrorCode.java @@ -56,6 +56,12 @@ public enum CommonErrorCode implements BaseErrorCode { CategoryCode.AUTHENTICATION, "002", "유효하지 않은 JWT 토큰입니다."), EXPIRED_TOKEN(StatusCode.UNAUTHORIZED, DomainCode.COMM, CategoryCode.AUTHENTICATION, "003", "만료된 JWT 토큰입니다."), + INVALID_TOKEN_SIGNATURE(StatusCode.UNAUTHORIZED, DomainCode.COMM, + CategoryCode.AUTHENTICATION, "004", "토큰 서명이 유효하지 않습니다."), + MALFORMED_TOKEN(StatusCode.UNAUTHORIZED, DomainCode.COMM, + CategoryCode.AUTHENTICATION, "005", "토큰 형식이 올바르지 않습니다."), + UNSUPPORTED_TOKEN(StatusCode.UNAUTHORIZED, DomainCode.COMM, + CategoryCode.AUTHENTICATION, "006", "지원하지 않는 토큰 형식입니다."), /** * COMM-06-xxx From 8bce66bfca4a44350c72ab9845669e3df56b4da1 Mon Sep 17 00:00:00 2001 From: WooJJam <111514410+WooJJam@users.noreply.github.com> Date: Sat, 30 May 2026 01:08:48 +0900 Subject: [PATCH 08/12] =?UTF-8?q?refactor:=20JWT=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=EC=8A=A4=EB=B3=84=20=EA=B5=AC=EC=B2=B4?= =?UTF-8?q?=EC=A0=81=EC=9D=B8=20=EC=97=90=EB=9F=AC=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/filter/JwtAuthenticationFilter.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/com/yapp/ndgl/application/domains/auth/filter/JwtAuthenticationFilter.java b/application/src/main/java/com/yapp/ndgl/application/domains/auth/filter/JwtAuthenticationFilter.java index 2de8516..140b2c4 100644 --- a/application/src/main/java/com/yapp/ndgl/application/domains/auth/filter/JwtAuthenticationFilter.java +++ b/application/src/main/java/com/yapp/ndgl/application/domains/auth/filter/JwtAuthenticationFilter.java @@ -6,6 +6,9 @@ import com.yapp.ndgl.common.response.ErrorResponse; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.UnsupportedJwtException; +import io.jsonwebtoken.security.SignatureException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -47,7 +50,16 @@ protected void doFilterInternal( } catch (ExpiredJwtException e) { sendErrorResponse(response, CommonErrorCode.EXPIRED_TOKEN); return; - } catch (Exception e) { + } catch (SignatureException e) { + sendErrorResponse(response, CommonErrorCode.INVALID_TOKEN_SIGNATURE); + return; + } catch (MalformedJwtException e) { + sendErrorResponse(response, CommonErrorCode.MALFORMED_TOKEN); + return; + } catch (UnsupportedJwtException e) { + sendErrorResponse(response, CommonErrorCode.UNSUPPORTED_TOKEN); + return; + } catch (IllegalArgumentException e) { sendErrorResponse(response, CommonErrorCode.INVALID_TOKEN); return; } From e4ae1c9a7c1d5f5f92ff6b8ee6e566fee895deb7 Mon Sep 17 00:00:00 2001 From: WooJJam <111514410+WooJJam@users.noreply.github.com> Date: Sat, 30 May 2026 01:08:51 +0900 Subject: [PATCH 09/12] =?UTF-8?q?test:=20JwtTokenProvider=20parseClaims()?= =?UTF-8?q?=20=EC=98=88=EC=99=B8=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/component/JwtTokenProviderTest.java | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 application/src/test/java/com/yapp/ndgl/application/domains/auth/component/JwtTokenProviderTest.java diff --git a/application/src/test/java/com/yapp/ndgl/application/domains/auth/component/JwtTokenProviderTest.java b/application/src/test/java/com/yapp/ndgl/application/domains/auth/component/JwtTokenProviderTest.java new file mode 100644 index 0000000..00e0015 --- /dev/null +++ b/application/src/test/java/com/yapp/ndgl/application/domains/auth/component/JwtTokenProviderTest.java @@ -0,0 +1,113 @@ +package com.yapp.ndgl.application.domains.auth.component; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.UnsupportedJwtException; +import io.jsonwebtoken.security.Keys; +import io.jsonwebtoken.security.SignatureException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@DisplayName("JwtTokenProvider") +class JwtTokenProviderTest { + + private static final String SECRET = "test-secret-key-for-unit-test-minimum-256-bits!!"; + private static final long EXPIRATION = 86400000L; + + private JwtTokenProvider jwtTokenProvider; + private SecretKey secretKey; + + @BeforeEach + void setUp() { + jwtTokenProvider = new JwtTokenProvider(SECRET, EXPIRATION); + secretKey = Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8)); + } + + @Nested + @DisplayName("parseClaims()는") + class ParseClaims { + + @Test + @DisplayName("유효한 토큰이면 Claims를 반환한다") + void 유효한_토큰이면_Claims를_반환한다() { + final String token = jwtTokenProvider.generateToken("test-uuid"); + + final Claims claims = jwtTokenProvider.parseClaims(token); + + assertThat(claims.getSubject()).isEqualTo("test-uuid"); + } + + @Nested + @DisplayName("예외 케이스") + class 예외_케이스 { + + @Test + @DisplayName("만료된 토큰이면 ExpiredJwtException을 던진다") + void 만료된_토큰이면_ExpiredJwtException을_던진다() { + final String expiredToken = Jwts.builder() + .subject("test-uuid") + .issuedAt(new Date(System.currentTimeMillis() - 86400000L)) + .expiration(new Date(System.currentTimeMillis() - 1000L)) + .signWith(secretKey) + .compact(); + + assertThatThrownBy(() -> jwtTokenProvider.parseClaims(expiredToken)) + .isInstanceOf(ExpiredJwtException.class); + } + + @Test + @DisplayName("서명이 변조된 토큰이면 SignatureException을 던진다") + void 서명이_변조된_토큰이면_SignatureException을_던진다() { + final SecretKey wrongKey = Keys.hmacShaKeyFor( + "wrong-secret-key-minimum-256-bits-long!!!!!!".getBytes(StandardCharsets.UTF_8)); + final String tamperedToken = Jwts.builder() + .subject("test-uuid") + .issuedAt(new Date()) + .expiration(new Date(System.currentTimeMillis() + EXPIRATION)) + .signWith(wrongKey) + .compact(); + + assertThatThrownBy(() -> jwtTokenProvider.parseClaims(tamperedToken)) + .isInstanceOf(SignatureException.class); + } + + @Test + @DisplayName("형식이 깨진 토큰이면 MalformedJwtException을 던진다") + void 형식이_깨진_토큰이면_MalformedJwtException을_던진다() { + final String malformedToken = "this.is.not.a.jwt"; + + assertThatThrownBy(() -> jwtTokenProvider.parseClaims(malformedToken)) + .isInstanceOf(MalformedJwtException.class); + } + + @Test + @DisplayName("서명 없는 토큰이면 UnsupportedJwtException을 던진다") + void 서명_없는_토큰이면_UnsupportedJwtException을_던진다() { + final String unsignedToken = Jwts.builder() + .subject("test-uuid") + .compact(); + + assertThatThrownBy(() -> jwtTokenProvider.parseClaims(unsignedToken)) + .isInstanceOf(UnsupportedJwtException.class); + } + + @Test + @DisplayName("빈 문자열이면 IllegalArgumentException을 던진다") + void 빈_문자열이면_IllegalArgumentException을_던진다() { + assertThatThrownBy(() -> jwtTokenProvider.parseClaims("")) + .isInstanceOf(IllegalArgumentException.class); + } + } + } +} From 735d20e051fe89ad9b66c5e869330b9a58459c72 Mon Sep 17 00:00:00 2001 From: WooJJam <111514410+WooJJam@users.noreply.github.com> Date: Sat, 30 May 2026 01:47:50 +0900 Subject: [PATCH 10/12] =?UTF-8?q?feat:=20SocialProvider=20enum=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(KAKAO,=20APPLE)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/yapp/ndgl/common/type/SocialProvider.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 common/src/main/java/com/yapp/ndgl/common/type/SocialProvider.java diff --git a/common/src/main/java/com/yapp/ndgl/common/type/SocialProvider.java b/common/src/main/java/com/yapp/ndgl/common/type/SocialProvider.java new file mode 100644 index 0000000..2b92259 --- /dev/null +++ b/common/src/main/java/com/yapp/ndgl/common/type/SocialProvider.java @@ -0,0 +1,13 @@ +package com.yapp.ndgl.common.type; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum SocialProvider { + KAKAO("카카오"), + APPLE("애플"); + + private final String label; +} From f6a43ead071bfc4a82e4e13310bdce31ef1341e2 Mon Sep 17 00:00:00 2001 From: WooJJam <111514410+WooJJam@users.noreply.github.com> Date: Sat, 30 May 2026 01:47:54 +0900 Subject: [PATCH 11/12] =?UTF-8?q?feat:=20UserEntity=20=EC=86=8C=EC=85=9C?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=95=84=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20fcmToken=20nullable=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 --- .../ndgl/domain/user/entity/UserEntity.java | 29 +++++++++++++++++-- .../user/repository/UserRepository.java | 4 ++- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/domain/domain-rdb/src/main/java/com/yapp/ndgl/domain/user/entity/UserEntity.java b/domain/domain-rdb/src/main/java/com/yapp/ndgl/domain/user/entity/UserEntity.java index 68d0ef1..876b4ce 100644 --- a/domain/domain-rdb/src/main/java/com/yapp/ndgl/domain/user/entity/UserEntity.java +++ b/domain/domain-rdb/src/main/java/com/yapp/ndgl/domain/user/entity/UserEntity.java @@ -1,16 +1,23 @@ package com.yapp.ndgl.domain.user.entity; +import com.yapp.ndgl.common.type.SocialProvider; import com.yapp.ndgl.domain.common.entity.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @Entity -@Table(name = "users") +@Table( + name = "users", + uniqueConstraints = @UniqueConstraint(columnNames = {"provider", "provider_id"}) +) @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class UserEntity extends BaseEntity { @@ -18,7 +25,7 @@ public class UserEntity extends BaseEntity { @Column(nullable = false, unique = true, length = 36) private String uuid; - @Column(nullable = false, length = 500) + @Column(length = 500) private String fcmToken; @Column(length = 100) @@ -36,6 +43,16 @@ public class UserEntity extends BaseEntity { @Column(nullable = false, length = 50) private String nickname; + @Enumerated(EnumType.STRING) + @Column(length = 20) + private SocialProvider provider; + + @Column(name = "provider_id", length = 100) + private String providerId; + + @Column(length = 200) + private String email; + @Builder public UserEntity( final String uuid, @@ -44,7 +61,10 @@ public UserEntity( final String deviceOs, final String deviceOsVersion, final String appVersion, - final String nickname + final String nickname, + final SocialProvider provider, + final String providerId, + final String email ) { this.uuid = uuid; this.fcmToken = fcmToken; @@ -53,5 +73,8 @@ public UserEntity( this.deviceOsVersion = deviceOsVersion; this.appVersion = appVersion; this.nickname = nickname; + this.provider = provider; + this.providerId = providerId; + this.email = email; } } diff --git a/domain/domain-rdb/src/main/java/com/yapp/ndgl/domain/user/repository/UserRepository.java b/domain/domain-rdb/src/main/java/com/yapp/ndgl/domain/user/repository/UserRepository.java index 5cd9ff6..477cfa4 100644 --- a/domain/domain-rdb/src/main/java/com/yapp/ndgl/domain/user/repository/UserRepository.java +++ b/domain/domain-rdb/src/main/java/com/yapp/ndgl/domain/user/repository/UserRepository.java @@ -4,10 +4,12 @@ import org.springframework.data.jpa.repository.JpaRepository; +import com.yapp.ndgl.common.type.SocialProvider; import com.yapp.ndgl.domain.user.entity.UserEntity; public interface UserRepository extends JpaRepository { - Optional findByUuid(String uuid); + Optional findByUuid(String uuid); + Optional findByProviderAndProviderId(SocialProvider provider, String providerId); } From 088b1384802dc86f9c5a996f0a409c9fe2761cfe Mon Sep 17 00:00:00 2001 From: WooJJam <111514410+WooJJam@users.noreply.github.com> Date: Sat, 30 May 2026 01:48:01 +0900 Subject: [PATCH 12/12] =?UTF-8?q?feat:=20User=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EC=86=8C=EC=85=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=86=8C?= =?UTF-8?q?=EC=85=9C=20=EA=B0=80=EC=9E=85/=EC=A1=B0=ED=9A=8C=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/yapp/ndgl/domain/user/User.java | 79 +++++++++++-------- .../ndgl/domain/user/mapper/UserMapper.java | 56 +++++++------ .../user/service/UserDomainService.java | 35 +++++--- 3 files changed, 102 insertions(+), 68 deletions(-) diff --git a/domain/domain-service/src/main/java/com/yapp/ndgl/domain/user/User.java b/domain/domain-service/src/main/java/com/yapp/ndgl/domain/user/User.java index bfeddb5..ef743ec 100644 --- a/domain/domain-service/src/main/java/com/yapp/ndgl/domain/user/User.java +++ b/domain/domain-service/src/main/java/com/yapp/ndgl/domain/user/User.java @@ -3,6 +3,8 @@ import java.time.LocalDateTime; import java.util.UUID; +import com.yapp.ndgl.common.type.SocialProvider; + import lombok.Builder; import lombok.Getter; @@ -10,38 +12,51 @@ @Builder public class User { - private final Long id; - private final String uuid; - private final String fcmToken; - private final String deviceModel; - private final String deviceOs; - private final String deviceOsVersion; - private final String appVersion; - private final String nickname; - private final LocalDateTime createdAt; - private final LocalDateTime updatedAt; + private final Long id; + private final String uuid; + private final String fcmToken; + private final String deviceModel; + private final String deviceOs; + private final String deviceOsVersion; + private final String appVersion; + private final String nickname; + private final SocialProvider provider; + private final String providerId; + private final String email; + private final LocalDateTime createdAt; + private final LocalDateTime updatedAt; - public static User create( - final String fcmToken, - final String deviceModel, - final String deviceOs, - final String deviceOsVersion, - final String appVersion, - final String nickname - ) { - String uuid = UUID.randomUUID().toString(); - LocalDateTime now = LocalDateTime.now(); + public static User create( + final String fcmToken, + final String deviceModel, + final String deviceOs, + final String deviceOsVersion, + final String appVersion, + final String nickname + ) { + return User.builder() + .uuid(UUID.randomUUID().toString()) + .fcmToken(fcmToken) + .deviceModel(deviceModel) + .deviceOs(deviceOs) + .deviceOsVersion(deviceOsVersion) + .appVersion(appVersion) + .nickname(nickname) + .build(); + } - return User.builder() - .uuid(uuid) - .fcmToken(fcmToken) - .deviceModel(deviceModel) - .deviceOs(deviceOs) - .deviceOsVersion(deviceOsVersion) - .appVersion(appVersion) - .nickname(nickname) - .createdAt(now) - .updatedAt(now) - .build(); - } + public static User createSocialUser( + final SocialProvider provider, + final String providerId, + final String email, + final String nickname + ) { + return User.builder() + .uuid(UUID.randomUUID().toString()) + .provider(provider) + .providerId(providerId) + .email(email) + .nickname(nickname) + .build(); + } } diff --git a/domain/domain-service/src/main/java/com/yapp/ndgl/domain/user/mapper/UserMapper.java b/domain/domain-service/src/main/java/com/yapp/ndgl/domain/user/mapper/UserMapper.java index 9b568b7..78c1f28 100644 --- a/domain/domain-service/src/main/java/com/yapp/ndgl/domain/user/mapper/UserMapper.java +++ b/domain/domain-service/src/main/java/com/yapp/ndgl/domain/user/mapper/UserMapper.java @@ -5,30 +5,36 @@ public class UserMapper { - public static UserEntity toEntity(final User user) { - return UserEntity.builder() - .uuid(user.getUuid()) - .fcmToken(user.getFcmToken()) - .deviceModel(user.getDeviceModel()) - .deviceOs(user.getDeviceOs()) - .deviceOsVersion(user.getDeviceOsVersion()) - .appVersion(user.getAppVersion()) - .nickname(user.getNickname()) - .build(); - } + public static UserEntity toEntity(final User user) { + return UserEntity.builder() + .uuid(user.getUuid()) + .fcmToken(user.getFcmToken()) + .deviceModel(user.getDeviceModel()) + .deviceOs(user.getDeviceOs()) + .deviceOsVersion(user.getDeviceOsVersion()) + .appVersion(user.getAppVersion()) + .nickname(user.getNickname()) + .provider(user.getProvider()) + .providerId(user.getProviderId()) + .email(user.getEmail()) + .build(); + } - public static User toDomain(final UserEntity entity) { - return User.builder() - .id(entity.getId()) - .uuid(entity.getUuid()) - .fcmToken(entity.getFcmToken()) - .deviceModel(entity.getDeviceModel()) - .deviceOs(entity.getDeviceOs()) - .deviceOsVersion(entity.getDeviceOsVersion()) - .appVersion(entity.getAppVersion()) - .nickname(entity.getNickname()) - .createdAt(entity.getCreatedAt()) - .updatedAt(entity.getUpdatedAt()) - .build(); - } + public static User toDomain(final UserEntity entity) { + return User.builder() + .id(entity.getId()) + .uuid(entity.getUuid()) + .fcmToken(entity.getFcmToken()) + .deviceModel(entity.getDeviceModel()) + .deviceOs(entity.getDeviceOs()) + .deviceOsVersion(entity.getDeviceOsVersion()) + .appVersion(entity.getAppVersion()) + .nickname(entity.getNickname()) + .provider(entity.getProvider()) + .providerId(entity.getProviderId()) + .email(entity.getEmail()) + .createdAt(entity.getCreatedAt()) + .updatedAt(entity.getUpdatedAt()) + .build(); + } } diff --git a/domain/domain-service/src/main/java/com/yapp/ndgl/domain/user/service/UserDomainService.java b/domain/domain-service/src/main/java/com/yapp/ndgl/domain/user/service/UserDomainService.java index 5ddbebc..0ffc277 100644 --- a/domain/domain-service/src/main/java/com/yapp/ndgl/domain/user/service/UserDomainService.java +++ b/domain/domain-service/src/main/java/com/yapp/ndgl/domain/user/service/UserDomainService.java @@ -1,14 +1,17 @@ package com.yapp.ndgl.domain.user.service; +import java.util.Optional; + import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.yapp.ndgl.common.exception.GlobalException; import com.yapp.ndgl.common.exception.UserErrorCode; +import com.yapp.ndgl.common.type.SocialProvider; import com.yapp.ndgl.domain.user.User; -import com.yapp.ndgl.domain.user.mapper.UserMapper; import com.yapp.ndgl.domain.user.UserNicknameGenerator; import com.yapp.ndgl.domain.user.entity.UserEntity; +import com.yapp.ndgl.domain.user.mapper.UserMapper; import com.yapp.ndgl.domain.user.repository.UserRepository; import lombok.RequiredArgsConstructor; @@ -28,24 +31,34 @@ public User createUser( final String deviceOsVersion, final String appVersion) { String nickname = UserNicknameGenerator.generate(); - - User user = User.create( - fcmToken, - deviceModel, - deviceOs, - deviceOsVersion, - appVersion, - nickname - ); - + User user = User.create(fcmToken, deviceModel, deviceOs, deviceOsVersion, appVersion, nickname); UserEntity savedUserEntity = userRepository.save(UserMapper.toEntity(user)); return UserMapper.toDomain(savedUserEntity); } + @Transactional + public User createSocialUser( + final SocialProvider provider, + final String providerId, + final String email) { + String nickname = UserNicknameGenerator.generate(); + User user = User.createSocialUser(provider, providerId, email, nickname); + UserEntity savedEntity = userRepository.save(UserMapper.toEntity(user)); + return UserMapper.toDomain(savedEntity); + } + @Transactional(readOnly = true) public User findByUuid(final String uuid) { return userRepository.findByUuid(uuid) .map(UserMapper::toDomain) .orElseThrow(() -> new GlobalException(UserErrorCode.NOT_FOUND_USER)); } + + @Transactional(readOnly = true) + public Optional findByProviderAndProviderId( + final SocialProvider provider, + final String providerId) { + return userRepository.findByProviderAndProviderId(provider, providerId) + .map(UserMapper::toDomain); + } }