diff --git a/src/main/java/org/tuna/zoopzoop/backend/domain/auth/controller/ApiV1AuthController.java b/src/main/java/org/tuna/zoopzoop/backend/domain/auth/controller/ApiV1AuthController.java index 60d187ba..3f9cfca2 100644 --- a/src/main/java/org/tuna/zoopzoop/backend/domain/auth/controller/ApiV1AuthController.java +++ b/src/main/java/org/tuna/zoopzoop/backend/domain/auth/controller/ApiV1AuthController.java @@ -8,12 +8,13 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.AuthenticationException; import org.springframework.web.bind.annotation.*; -import org.springframework.web.reactive.function.client.WebClient; -import org.tuna.zoopzoop.backend.domain.auth.service.KakaoUserInfoService; +import org.tuna.zoopzoop.backend.domain.auth.dto.AuthResultData; +import org.tuna.zoopzoop.backend.domain.auth.entity.AuthResult; +import org.tuna.zoopzoop.backend.domain.auth.entity.RefreshToken; +import org.tuna.zoopzoop.backend.domain.auth.service.RefreshTokenService; import org.tuna.zoopzoop.backend.domain.member.entity.Member; -import org.tuna.zoopzoop.backend.domain.member.service.MemberService; -import org.tuna.zoopzoop.backend.global.config.jwt.JwtProperties; import org.tuna.zoopzoop.backend.global.rsData.RsData; import org.tuna.zoopzoop.backend.global.security.jwt.JwtUtil; @@ -23,10 +24,8 @@ @Tag(name = "ApiV1AuthController", description = "인증/인가 REST API 컨트롤러") public class ApiV1AuthController { private final JwtUtil jwtUtil; - private final MemberService memberService; - private final JwtProperties jwtProperties; - private final KakaoUserInfoService kakaoUserInfoService; - private final WebClient webClient; + private final RefreshTokenService refreshTokenService; + private final AuthResult authResult; /** * 사용자 로그아웃 API @@ -34,58 +33,79 @@ public class ApiV1AuthController { */ @GetMapping("/logout") @Operation(summary = "사용자 로그아웃") - public ResponseEntity> logout(HttpServletResponse response) { + public ResponseEntity> logout( + @CookieValue(name = "sessionId") + String sessionId, + HttpServletResponse response) { + + // 서버에서 RefreshToken 삭제 + refreshTokenService.deleteBySessionId(sessionId); + + // 클라이언트 쿠키 삭제 (AccessToken + SessionId) ResponseCookie accessCookie = ResponseCookie.from("accessToken", "") .httpOnly(true) .path("/") - .maxAge(0) // 쿠키 삭제 + .maxAge(0) .sameSite("Lax") .build(); - ResponseCookie refreshCookie = ResponseCookie.from("refreshToken", "") + ResponseCookie sessionCookie = ResponseCookie.from("sessionId", "") .httpOnly(true) .path("/") - .maxAge(0) // 쿠키 삭제 + .maxAge(0) .sameSite("Lax") .build(); response.addHeader(HttpHeaders.SET_COOKIE, accessCookie.toString()); - response.addHeader(HttpHeaders.SET_COOKIE, refreshCookie.toString()); + response.addHeader(HttpHeaders.SET_COOKIE, sessionCookie.toString()); return ResponseEntity .status(HttpStatus.OK) - .body(new RsData<>( - "200", - "정상적으로 로그아웃 했습니다.", - null - ) - ); + .body(new RsData<>("200", "정상적으로 로그아웃 했습니다.", null)); } /** * refreshToken 기반으로 accessToken 재발급 - * @param refreshToken 쿠키에 포함된 현재 로그인한 사용자의 refreshToken + * @param sessionId 쿠키에 포함된 현재 로그인한 사용자의 sessionId. * @param response Servlet 기반 웹에서 server -> client로 http 응답을 보내기 위한 객체, 자동 주입. */ @PostMapping("/refresh") - @Operation(summary = "사용자 액세스 토큰 재발급 (리프레시 토큰이 유효할 경우)") - public ResponseEntity> refreshToken(@CookieValue(name = "refreshToken", required = false) String refreshToken, - HttpServletResponse response) { + @Operation(summary = "사용자 액세스 토큰 재발급 (서버 저장 RefreshToken 사용)") + public ResponseEntity> refreshToken( + @CookieValue(name = "sessionId") + String sessionId, + HttpServletResponse response + ) { - if (refreshToken == null || !jwtUtil.validateToken(refreshToken) || !jwtUtil.isRefreshToken(refreshToken)) { + if (sessionId == null) { return ResponseEntity .status(HttpStatus.UNAUTHORIZED) - .body(new RsData<>( - "401", - "유효하지 않은 리프레시 토큰입니다.", - null - )); + .body(new RsData<>("401", "세션이 존재하지 않습니다.", null)); + } + + // sessionId로 RefreshToken 조회 + RefreshToken refreshTokenEntity; + try { + refreshTokenEntity = refreshTokenService.getBySessionId(sessionId); + } catch (AuthenticationException e) { + return ResponseEntity + .status(HttpStatus.UNAUTHORIZED) + .body(new RsData<>("401", e.getMessage(), null)); + } + + String refreshToken = refreshTokenEntity.getRefreshToken(); + + // RefreshToken 유효성 검사 + if (!jwtUtil.validateToken(refreshToken) || !jwtUtil.isRefreshToken(refreshToken)) { + return ResponseEntity + .status(HttpStatus.UNAUTHORIZED) + .body(new RsData<>("401", "유효하지 않은 리프레시 토큰입니다.", null)); } - String providerKey = jwtUtil.getProviderKeyFromToken(refreshToken); - Member member = memberService.findByProviderKey(providerKey); + Member member = refreshTokenEntity.getMember(); + // 새 AccessToken 발급 String newAccessToken = jwtUtil.generateToken(member); ResponseCookie accessCookie = ResponseCookie.from("accessToken", newAccessToken) @@ -97,12 +117,33 @@ public ResponseEntity> refreshToken(@CookieValue(name = "refreshTok response.addHeader(HttpHeaders.SET_COOKIE, accessCookie.toString()); + return ResponseEntity + .status(HttpStatus.OK) + .body(new RsData<>("200", "액세스 토큰을 재발급 했습니다.", null)); + } + + @GetMapping("/result") + @Operation(summary = "확장프로그램 백그라운드 풀링 대응 API") + public ResponseEntity> pullingResult( + @RequestParam String state + ) { + AuthResultData resultData = authResult.get(state); + if(resultData == null) { + return ResponseEntity + .status(HttpStatus.NOT_FOUND) + .body(new RsData<>( + "404", + "state에 해당하는 토큰이 준비되지 않았거나, 잘못된 state 입니다.", + null + ) + ); + } return ResponseEntity .status(HttpStatus.OK) .body(new RsData<>( - "200", - "액세스 토큰을 재발급 했습니다.", - null + "200", + "토큰이 정상적으로 발급되었습니다.", + resultData )); } } diff --git a/src/main/java/org/tuna/zoopzoop/backend/domain/auth/dto/AuthResultData.java b/src/main/java/org/tuna/zoopzoop/backend/domain/auth/dto/AuthResultData.java new file mode 100644 index 00000000..dbdace4a --- /dev/null +++ b/src/main/java/org/tuna/zoopzoop/backend/domain/auth/dto/AuthResultData.java @@ -0,0 +1,19 @@ +package org.tuna.zoopzoop.backend.domain.auth.dto; + +public class AuthResultData { + private final String accessToken; + private final String sessionId; + + public AuthResultData(String accessToken, String sessionId) { + this.accessToken = accessToken; + this.sessionId = sessionId; + } + + public String getAccessToken() { + return accessToken; + } + + public String getSessionId() { + return sessionId; + } +} \ No newline at end of file diff --git a/src/main/java/org/tuna/zoopzoop/backend/domain/auth/entity/AuthResult.java b/src/main/java/org/tuna/zoopzoop/backend/domain/auth/entity/AuthResult.java new file mode 100644 index 00000000..e29721ac --- /dev/null +++ b/src/main/java/org/tuna/zoopzoop/backend/domain/auth/entity/AuthResult.java @@ -0,0 +1,23 @@ +package org.tuna.zoopzoop.backend.domain.auth.entity; + +import org.springframework.stereotype.Component; +import org.tuna.zoopzoop.backend.domain.auth.dto.AuthResultData; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Component +public class AuthResult { + private final Map results = new ConcurrentHashMap<>(); + + public void put(String state, String accessToken, String sessionId) { + results.put(state, new AuthResultData(accessToken, sessionId)); + } + + public AuthResultData get(String state) { + return results.remove(state); + } + + public void consume(String state) { + results.remove(state); + } +} \ No newline at end of file diff --git a/src/main/java/org/tuna/zoopzoop/backend/domain/auth/entity/RefreshToken.java b/src/main/java/org/tuna/zoopzoop/backend/domain/auth/entity/RefreshToken.java new file mode 100644 index 00000000..414ad42e --- /dev/null +++ b/src/main/java/org/tuna/zoopzoop/backend/domain/auth/entity/RefreshToken.java @@ -0,0 +1,39 @@ +package org.tuna.zoopzoop.backend.domain.auth.entity; + +import jakarta.persistence.*; +import lombok.*; +import org.tuna.zoopzoop.backend.domain.member.entity.Member; +import org.tuna.zoopzoop.backend.global.jpa.entity.BaseEntity; + +import java.time.LocalDateTime; + +@Getter +@Setter +@Entity +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class RefreshToken extends BaseEntity { + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id", unique = true, nullable = false) + private Member member; + + @Column(name = "session_id", unique = true, nullable = false) + private String sessionId; + + @Column(unique = true, nullable = false) + private String refreshToken; + + @Column(name = "created_at", nullable = false, updatable = false) + private LocalDateTime createdAt; + + @Column(name = "expired_at") + private LocalDateTime expiredAt; + + @PrePersist + public void prePersist() { + if(createdAt == null) { + this.createdAt = LocalDateTime.now(); + } + } +} diff --git a/src/main/java/org/tuna/zoopzoop/backend/domain/auth/global/CustomOAuth2AuthorizationRequestResolver.java b/src/main/java/org/tuna/zoopzoop/backend/domain/auth/global/CustomOAuth2AuthorizationRequestResolver.java index 04429882..f673afba 100644 --- a/src/main/java/org/tuna/zoopzoop/backend/domain/auth/global/CustomOAuth2AuthorizationRequestResolver.java +++ b/src/main/java/org/tuna/zoopzoop/backend/domain/auth/global/CustomOAuth2AuthorizationRequestResolver.java @@ -1,11 +1,16 @@ package org.tuna.zoopzoop.backend.domain.auth.global; +import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver; import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + public class CustomOAuth2AuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver { private final OAuth2AuthorizationRequestResolver defaultResolver; @@ -28,13 +33,25 @@ private OAuth2AuthorizationRequest customize(OAuth2AuthorizationRequest req, Htt if (req == null) return null; String source = request.getParameter("source"); // 로그인 시작 시 전달된 source - OAuth2AuthorizationRequest.Builder builder = OAuth2AuthorizationRequest.from(req); if ("extension".equals(source)) { - // state에 source 정보를 안전하게 포함 - builder.state("source:extension;" + req.getState()); + String state = request.getParameter("state"); + Map stateData = new HashMap<>(); + stateData.put("source", "extension"); + stateData.put("customState", state); + stateData.put("originalState", req.getState()); + + try { + String encodedState = Base64.getUrlEncoder() + .encodeToString(new ObjectMapper().writeValueAsBytes(stateData)); + builder.state(encodedState); + } catch (Exception e) { + e.printStackTrace(); + return builder.build(); + } } + return builder.build(); } } \ No newline at end of file diff --git a/src/main/java/org/tuna/zoopzoop/backend/domain/auth/handler/OAuth2SuccessHandler.java b/src/main/java/org/tuna/zoopzoop/backend/domain/auth/handler/OAuth2SuccessHandler.java index abd7e075..7b5e15cc 100644 --- a/src/main/java/org/tuna/zoopzoop/backend/domain/auth/handler/OAuth2SuccessHandler.java +++ b/src/main/java/org/tuna/zoopzoop/backend/domain/auth/handler/OAuth2SuccessHandler.java @@ -1,5 +1,7 @@ package org.tuna.zoopzoop.backend.domain.auth.handler; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -12,6 +14,8 @@ import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; +import org.tuna.zoopzoop.backend.domain.auth.entity.AuthResult; +import org.tuna.zoopzoop.backend.domain.auth.service.RefreshTokenService; import org.tuna.zoopzoop.backend.domain.member.entity.Member; import org.tuna.zoopzoop.backend.domain.member.repository.MemberRepository; import org.tuna.zoopzoop.backend.domain.member.service.MemberService; @@ -20,6 +24,8 @@ import java.io.IOException; import java.net.URLEncoder; +import java.util.Base64; +import java.util.Map; @Component @RequiredArgsConstructor @@ -29,6 +35,8 @@ public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler private final JwtProperties jwtProperties; private final MemberRepository memberRepository; private final MemberService memberService; + private final RefreshTokenService refreshTokenService; + private final AuthResult authResult; @Value("${front.redirect_domain}") private String redirect_domain; @@ -41,9 +49,8 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo Authentication authentication) throws IOException { // OAuth2 로그인 사용자의 속성 - OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); - // 소셜 로그인 공급자(Google, Kakao) + OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); String registrationId = ((OAuth2AuthenticationToken) authentication).getAuthorizedClientRegistrationId(); // 공급자 별로 DB 에서 회원 조회 @@ -58,24 +65,35 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo throw new IllegalArgumentException(registrationId + "는 지원하지 않는 소셜 로그인입니다."); } - // 조회된 회원 정보를 기반으로 AccessToken 및 RefreshToken 생성 + // 조회된 회원 정보를 기반으로 AccessToken 생성 String accessToken = jwtUtil.generateToken(member); + + // RefreshToken 생성 및 DB 저장, SessionId 생성 String refreshToken = jwtUtil.generateRefreshToken(member); + String sessionId = refreshTokenService.saveSession(member, refreshToken); - String source = request.getParameter("source"); - String state = request.getParameter("state"); - log.info("[OAuth2SuccessHandler] Source: {}", source); - log.info("[OAuth2SuccessHandler] State: {}", state); - boolean isExtension = state != null && state.contains("source:extension"); + log.info("[OAuth2SuccessHandler] Member: {}, SessionId: {}", member.getId(), sessionId); - // 확장 프로그램에서 로그인 했을 경우. - if(isExtension){ - String redirectUrl = redirect_domain + "/extension/callback" - + "?success=true" - + "&accessToken=" + URLEncoder.encode(accessToken, "UTF-8") - + "&refreshToken=" + URLEncoder.encode(refreshToken, "UTF-8"); - response.sendRedirect(redirectUrl); - return; + String state = request.getParameter("state"); + if(state != null && state.startsWith("ey")) { + Map stateData = new ObjectMapper().readValue( + Base64.getUrlDecoder().decode(state), + new TypeReference>() { + } + ); + + String source = stateData.get("source"); + String customState = stateData.get("customState"); + + log.info("[OAuth2SuccessHandler] Source: {}", source); + log.info("[OAuth2SuccessHandler] CustomState: {}", customState); + + // 확장 프로그램에서 로그인 했을 경우. + if ("extension".equals(source)) { + authResult.put(customState, accessToken, sessionId); + response.sendRedirect(redirect_domain + "/extension/success"); + return; + } } if ("http://localhost:3000".equals(redirect_domain)) { @@ -83,7 +101,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo String redirectUrl = redirect_domain + "/api/auth/callback" + "?success=true" + "&accessToken=" + URLEncoder.encode(accessToken, "UTF-8") - + "&refreshToken=" + URLEncoder.encode(refreshToken, "UTF-8"); + + "&sessionId=" + URLEncoder.encode(sessionId, "UTF-8"); response.sendRedirect(redirectUrl); } else { @@ -98,12 +116,10 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo .sameSite("None") .build(); - ResponseCookie refreshCookie = ResponseCookie.from("refreshToken", refreshToken) + ResponseCookie sessionCookie = ResponseCookie.from("sessionId", sessionId) .httpOnly(true) .path("/") - .maxAge(jwtProperties.getRefreshTokenValidity() / 1000) - // .domain() // 프론트엔드 & 백엔드 상위 도메인 - // .secure(true) // https 필수 설정. + .maxAge(jwtProperties.getRefreshTokenValidity() / 1000) // RefreshToken 유효기간과 동일하게 .domain(redirect_domain) .secure(true) .sameSite("None") @@ -111,7 +127,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo // HTTP 응답에서 쿠키 값 추가. response.addHeader(HttpHeaders.SET_COOKIE, accessCookie.toString()); - response.addHeader(HttpHeaders.SET_COOKIE, refreshCookie.toString()); + response.addHeader(HttpHeaders.SET_COOKIE, sessionCookie.toString()); // 로그인 성공 후 리다이렉트. // 배포 시에 프론트엔드와 조율이 필요한 부분일 듯 함. diff --git a/src/main/java/org/tuna/zoopzoop/backend/domain/auth/repository/RefreshTokenRepository.java b/src/main/java/org/tuna/zoopzoop/backend/domain/auth/repository/RefreshTokenRepository.java new file mode 100644 index 00000000..89a81727 --- /dev/null +++ b/src/main/java/org/tuna/zoopzoop/backend/domain/auth/repository/RefreshTokenRepository.java @@ -0,0 +1,14 @@ +package org.tuna.zoopzoop.backend.domain.auth.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import org.tuna.zoopzoop.backend.domain.auth.entity.RefreshToken; +import org.tuna.zoopzoop.backend.domain.member.entity.Member; + +import java.util.Optional; + +@Repository +public interface RefreshTokenRepository extends JpaRepository { + Optional findBySessionId(String sessionId); + Optional findByMember(Member member); +} diff --git a/src/main/java/org/tuna/zoopzoop/backend/domain/auth/service/RefreshTokenService.java b/src/main/java/org/tuna/zoopzoop/backend/domain/auth/service/RefreshTokenService.java new file mode 100644 index 00000000..9ec6aea2 --- /dev/null +++ b/src/main/java/org/tuna/zoopzoop/backend/domain/auth/service/RefreshTokenService.java @@ -0,0 +1,54 @@ +package org.tuna.zoopzoop.backend.domain.auth.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.stereotype.Service; +import org.tuna.zoopzoop.backend.domain.auth.entity.RefreshToken; +import org.tuna.zoopzoop.backend.domain.auth.repository.RefreshTokenRepository; +import org.tuna.zoopzoop.backend.domain.member.entity.Member; +import org.tuna.zoopzoop.backend.global.security.jwt.JwtUtil; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Date; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class RefreshTokenService { + private final RefreshTokenRepository refreshTokenRepository; + private final JwtUtil jwtUtil; + + private LocalDateTime getExpirationLocalDateTimeFromToken(String token) { + Date expirationDate = jwtUtil.getExpirationDateFromToken(token); // 기존 메서드 + if (expirationDate == null) return null; + + return LocalDateTime.ofInstant(expirationDate.toInstant(), ZoneId.systemDefault()); + } + + public String saveSession(Member member, String refreshToken) { + String sessionId = UUID.randomUUID().toString(); + + refreshTokenRepository.findByMember(member).ifPresent(refreshTokenRepository::delete); + + RefreshToken token = RefreshToken.builder() + .member(member) + .refreshToken(refreshToken) + .sessionId(sessionId) + .expiredAt(getExpirationLocalDateTimeFromToken(refreshToken)) + .build(); + + refreshTokenRepository.save(token); + return sessionId; + } + + public RefreshToken getBySessionId(String sessionId) { + return refreshTokenRepository.findBySessionId(sessionId) + .orElseThrow(() -> new BadCredentialsException("세션을 찾을 수 없습니다.")); + } + + public void deleteBySessionId(String sessionId) { + refreshTokenRepository.findBySessionId(sessionId) + .orElseThrow(() -> new BadCredentialsException("잘못된 요청입니다.")); + } +} diff --git a/src/main/java/org/tuna/zoopzoop/backend/domain/member/controller/ApiV1MemberController.java b/src/main/java/org/tuna/zoopzoop/backend/domain/member/controller/ApiV1MemberController.java index a38d67ad..70b6d872 100644 --- a/src/main/java/org/tuna/zoopzoop/backend/domain/member/controller/ApiV1MemberController.java +++ b/src/main/java/org/tuna/zoopzoop/backend/domain/member/controller/ApiV1MemberController.java @@ -11,6 +11,7 @@ import org.tuna.zoopzoop.backend.domain.member.dto.req.ReqBodyForEditMemberName; import org.tuna.zoopzoop.backend.domain.member.dto.res.ResBodyForEditMemberName; import org.tuna.zoopzoop.backend.domain.member.dto.res.ResBodyForGetMemberInfo; +import org.tuna.zoopzoop.backend.domain.member.dto.res.ResBodyForGetMemberInfoV2; import org.tuna.zoopzoop.backend.domain.member.entity.Member; import org.tuna.zoopzoop.backend.domain.member.service.MemberService; import org.tuna.zoopzoop.backend.global.rsData.RsData; @@ -40,7 +41,7 @@ public class ApiV1MemberController { */ @GetMapping("/me") @Operation(summary = "사용자 정보 조회") - public ResponseEntity> getMemberInfo( + public ResponseEntity> getMemberInfo( @AuthenticationPrincipal CustomUserDetails userDetails ) { Member member = userDetails.getMember(); @@ -50,7 +51,7 @@ public ResponseEntity> getMemberInfo( new RsData<>( "200", "사용자 정보를 조회했습니다.", - new ResBodyForGetMemberInfo(member) + new ResBodyForGetMemberInfoV2(member) ) ); } diff --git a/src/main/java/org/tuna/zoopzoop/backend/domain/member/dto/res/ResBodyForGetMemberInfo.java b/src/main/java/org/tuna/zoopzoop/backend/domain/member/dto/res/ResBodyForGetMemberInfo.java index 5bd2df27..91e714c1 100644 --- a/src/main/java/org/tuna/zoopzoop/backend/domain/member/dto/res/ResBodyForGetMemberInfo.java +++ b/src/main/java/org/tuna/zoopzoop/backend/domain/member/dto/res/ResBodyForGetMemberInfo.java @@ -5,14 +5,12 @@ public record ResBodyForGetMemberInfo( Integer id, String name, -// String email, String profileUrl ) { public ResBodyForGetMemberInfo(Member member){ this( member.getId(), member.getName(), -// member.getEmail(), member.getProfileImageUrl() ); } diff --git a/src/main/java/org/tuna/zoopzoop/backend/domain/member/dto/res/ResBodyForGetMemberInfoV2.java b/src/main/java/org/tuna/zoopzoop/backend/domain/member/dto/res/ResBodyForGetMemberInfoV2.java new file mode 100644 index 00000000..9eeb0059 --- /dev/null +++ b/src/main/java/org/tuna/zoopzoop/backend/domain/member/dto/res/ResBodyForGetMemberInfoV2.java @@ -0,0 +1,19 @@ +package org.tuna.zoopzoop.backend.domain.member.dto.res; + +import org.tuna.zoopzoop.backend.domain.member.entity.Member; + +public record ResBodyForGetMemberInfoV2( + Integer id, + String name, + String profileUrl, + String provider +) { + public ResBodyForGetMemberInfoV2(Member member){ + this( + member.getId(), + member.getName(), + member.getProfileImageUrl(), + member.getProvider().name() + ); + } +}