Skip to content

Commit 6720832

Browse files
authored
Merge pull request #51 from DropThe8bit/refactor/profile
[refactor] 프로필 기반 로그아웃/탈퇴(삭제)/reissue 코드 구조 수정
2 parents 6c08fd0 + a6aecfb commit 6720832

10 files changed

Lines changed: 108 additions & 55 deletions

File tree

src/main/java/everTale/everTale_be/auth/controller/AuthController.java

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,6 @@ public ApiResponse<LoginTokenResponseDto> naverLogin(@RequestParam String code,
4949
return ApiResponse.onSuccess(responseDto);
5050
}
5151

52-
@PostMapping("/profile/reissue")
53-
@Operation(summary = "프로필 기반 AccessToken 재발급", description = "만료된 access token을 refresh token으로 재발급합니다.")
54-
public ApiResponse<ProfileEnterResponseDto> reissueProfileAccessToken(@Valid @RequestBody ProfileReissueRequestDto requestDto) {
55-
ProfileEnterResponseDto responseDto = profileService.reissueWithProfile(requestDto.getRefreshToken(), requestDto.getProfileId());
56-
return ApiResponse.onSuccess(responseDto);
57-
}
58-
5952
// 로그아웃
6053
@Operation(summary = "로그아웃", description = "현재 로그인한 사용자의 토큰을 만료 처리합니다.")
6154
@PostMapping("/logout")
@@ -75,4 +68,31 @@ public ApiResponse<String> withdraw(HttpServletRequest request){
7568
authService.withdraw(accessToken);
7669
return ApiResponse.onSuccess("회원 탈퇴가 성공적으로 완료되었습니다.");
7770
}
71+
72+
@PostMapping("/profile/reissue")
73+
@Operation(summary = "프로필 기반 AccessToken 재발급", description = "만료된 access token을 refresh token으로 재발급합니다.")
74+
public ApiResponse<ProfileEnterResponseDto> reissueProfileAccessToken(@Valid @RequestBody ProfileReissueRequestDto requestDto) {
75+
ProfileEnterResponseDto responseDto = authService.reissueWithProfile(requestDto.getRefreshToken());
76+
return ApiResponse.onSuccess(responseDto);
77+
}
78+
79+
// 프로필 로그아웃
80+
@Operation(summary = "프로필 로그아웃", description = "선택한 프로필에서 로그아웃합니다.")
81+
@DeleteMapping("/profile/logout")
82+
public ApiResponse<String> logoutProfile(HttpServletRequest request){
83+
String accessToken = jwtUtil.extractAccessToken(request);
84+
85+
authService.logoutProfile(accessToken);
86+
return ApiResponse.onSuccess("로그아웃이 성공적으로 완료되었습니다.");
87+
}
88+
89+
// 프로필 삭제
90+
@Operation(summary = "프로필 삭제", description = "선택한 프로필을 삭제합니다.")
91+
@DeleteMapping("/profile")
92+
public ApiResponse<String> deleteProfile(HttpServletRequest request){
93+
String accessToken = jwtUtil.extractAccessToken(request);
94+
95+
authService.deleteProfile(accessToken);
96+
return ApiResponse.onSuccess("프로필이 성공적으로 삭제되었습니다.");
97+
}
7898
}

src/main/java/everTale/everTale_be/auth/dto/request/ProfileReissueRequestDto.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,4 @@ public class ProfileReissueRequestDto {
1212

1313
@Schema(description = "Refresh Token", example = "eyJhbGciOiJIUzI1NiIsInR5cCI...")
1414
private String refreshToken;
15-
16-
@Schema(description = "프로필 ID", example = "42")
17-
private Long profileId;
1815
}

src/main/java/everTale/everTale_be/auth/jwt/JwtProvider.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ public Long getUserIdFromToken(String token) {
6565
return claims.get("userId", Long.class);
6666
}
6767

68+
public Long getProfileIdFromToken(String token) {
69+
Claims claims = jwtUtil.extractClaims(token);
70+
return claims.get("profileId", Long.class);
71+
}
72+
6873
public boolean isTokenContainsProfileId(String token) {
6974
Claims claims = jwtUtil.extractClaims(token);
7075
return claims.containsKey("profileId");

src/main/java/everTale/everTale_be/auth/service/AuthService.java

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
import everTale.everTale_be.auth.jwt.JwtProvider;
88
import everTale.everTale_be.auth.jwt.JwtUtil;
99
import everTale.everTale_be.auth.util.UserHelper;
10+
import everTale.everTale_be.domain.profile.dto.response.ProfileEnterResponseDto;
11+
import everTale.everTale_be.domain.profile.entity.Profile;
12+
import everTale.everTale_be.domain.profile.repository.ProfileRepository;
13+
import everTale.everTale_be.domain.profile.util.ProfileHelper;
1014
import everTale.everTale_be.domain.user.entity.Enum.LoginProvider;
1115
import everTale.everTale_be.domain.user.entity.User;
1216
import everTale.everTale_be.domain.user.repository.UserRepository;
@@ -25,11 +29,14 @@
2529
public class AuthService {
2630

2731
private final JwtUtil jwtUtil;
32+
private final JwtProvider jwtProvider;
2833
private final TokenAuthService tokenAuthService;
2934
private final PasswordEncoder passwordEncoder;
3035
private final UserRepository userRepository;
3136
private final NaverService naverService;
3237
private final UserHelper userHelper;
38+
private final ProfileHelper profileHelper;
39+
private final ProfileRepository profileRepository;
3340

3441
// 일반 회원가입
3542
public void signup(SignUpRequestDto requestDto) {
@@ -97,20 +104,54 @@ public User createUserForNaver(UserInfoResponseDto responseDto){
97104

98105
// 로그아웃
99106
public void logout(String accessToken) {
100-
Long userId = userHelper.getRootUserId();
101107
tokenAuthService.validateNotBlackListed(accessToken);
102-
103108
tokenAuthService.addToBlackListForAccessToken(accessToken, "LOGOUT");
104-
tokenAuthService.deleteRefreshToken(userId);
105109
}
106110

107111
// 회원 탈퇴
108112
public void withdraw(String accessToken) {
109113
Long userId = userHelper.getRootUserId();
110114
tokenAuthService.validateNotBlackListed(accessToken);
111-
112115
tokenAuthService.addToBlackListForAccessToken(accessToken, "WITHDRAW");
113-
tokenAuthService.deleteRefreshToken(userId);
114116
userRepository.anonymizeUser(userId);
115117
}
118+
119+
public ProfileEnterResponseDto reissueWithProfile(String refreshToken) {
120+
Long userId = jwtProvider.getUserIdFromToken(refreshToken);
121+
Long profileId = jwtProvider.getProfileIdFromToken(refreshToken);
122+
123+
String storedRefreshToken = tokenAuthService.getRefreshToken(profileId);
124+
if (!storedRefreshToken.equals(refreshToken)) {
125+
throw new UnAuthorizedHandler(ErrorStatus.INVALID_REFRESH_TOKEN);
126+
}
127+
128+
Profile profile = profileRepository.findById(profileId)
129+
.orElseThrow(()-> new NotFoundHandler(ErrorStatus.NOT_FOUND_PROFILE));
130+
String newAccessToken = jwtUtil.generateAccessTokenWithProfile(userId, profile.getId());
131+
String newRefreshToken = jwtUtil.generateRefreshTokenWithProfile(userId, profile.getId());
132+
tokenAuthService.saveRefreshToken(profileId, newRefreshToken);
133+
134+
return ProfileEnterResponseDto.from(profile, newAccessToken, newRefreshToken, jwtUtil);
135+
}
136+
137+
// 프로필 나가기 (프로필 로그아웃)
138+
public void logoutProfile(String accessToken){
139+
Long profileId = profileHelper.getAuthenticatedProfileId();
140+
tokenAuthService.validateNotBlackListed(accessToken);
141+
142+
tokenAuthService.addToBlackListForAccessToken(accessToken, "LOGOUT");
143+
tokenAuthService.deleteRefreshToken(profileId);
144+
}
145+
146+
// 프로필 삭제 (회원 탈퇴 X)
147+
public void deleteProfile(String accessToken){
148+
Long profileId = profileHelper.getAuthenticatedProfileId();
149+
Profile profile = profileRepository.findById(profileId)
150+
.orElseThrow(()-> new NotFoundHandler(ErrorStatus.NOT_FOUND_PROFILE));
151+
profile.deleteProfile();
152+
153+
tokenAuthService.addToBlackListForAccessToken(accessToken, "WITHDRAW");
154+
tokenAuthService.deleteRefreshToken(profileId);
155+
profileRepository.anonymizeProfile(profileId);
156+
}
116157
}

src/main/java/everTale/everTale_be/domain/profile/controller/ProfileController.java

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import io.swagger.v3.oas.annotations.Operation;
1212
import io.swagger.v3.oas.annotations.Parameter;
1313
import io.swagger.v3.oas.annotations.tags.Tag;
14-
import jakarta.servlet.http.HttpServletRequest;
1514
import jakarta.validation.Valid;
1615
import lombok.RequiredArgsConstructor;
1716
import org.springframework.web.bind.annotation.*;
@@ -86,14 +85,4 @@ public ApiResponse<String> updatePassword(@Valid @RequestBody PasswordUpdateRequ
8685
profileService.updatePassword(requestDto);
8786
return ApiResponse.onSuccess("비밀번호가 성공적으로 수정되었습니다.");
8887
}
89-
90-
// 프로필 삭제
91-
@Operation(summary = "프로필 삭제", description = "선택한 프로필을 삭제합니다.")
92-
@DeleteMapping
93-
public ApiResponse<String> deleteProfile(HttpServletRequest request){
94-
String accessToken = jwtUtil.extractAccessToken(request);
95-
96-
profileService.deleteProfile(accessToken);
97-
return ApiResponse.onSuccess("프로필이 성공적으로 삭제되었습니다.");
98-
}
9988
}

src/main/java/everTale/everTale_be/domain/profile/dto/request/ChildProfileRequestDto.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package everTale.everTale_be.domain.profile.dto.request;
22

33
import com.fasterxml.jackson.annotation.JsonFormat;
4+
import everTale.everTale_be.domain.profile.entity.Enum.ProfileStatus;
45
import everTale.everTale_be.domain.profile.entity.Enum.ProfileType;
56
import everTale.everTale_be.domain.profile.entity.Profile;
67
import everTale.everTale_be.domain.user.entity.User;
@@ -29,12 +30,13 @@ public class ChildProfileRequestDto {
2930
@Schema(description = "자녀 기관명", example = "새싹 유치원")
3031
private String institution;
3132

32-
public Profile toEntity(User user, ProfileType profileType){
33+
public Profile toEntity(User user, ProfileType profileType, ProfileStatus profileStatus){
3334
return Profile.builder()
3435
.name(name)
3536
.birthDate(birthDate)
3637
.institution(institution)
3738
.profileType(profileType)
39+
.profileStatus(profileStatus)
3840
.user(user)
3941
.build();
4042
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package everTale.everTale_be.domain.profile.entity.Enum;
2+
3+
public enum ProfileStatus {
4+
ACTIVE,
5+
DEACTIVATED,
6+
DELETED
7+
}

src/main/java/everTale/everTale_be/domain/profile/entity/Profile.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package everTale.everTale_be.domain.profile.entity;
22

3+
import everTale.everTale_be.domain.profile.entity.Enum.ProfileStatus;
34
import everTale.everTale_be.domain.profile.entity.Enum.ProfileType;
45
import everTale.everTale_be.domain.profile.dto.request.ChildProfileUpdateRequestDto;
56
import everTale.everTale_be.domain.profile.dto.request.ParentProfileUpdateRequestDto;
@@ -49,6 +50,10 @@ public class Profile extends BaseTimeEntity {
4950
@Column(name = "profile_type", nullable = false)
5051
private ProfileType profileType;
5152

53+
@Enumerated(EnumType.STRING)
54+
@Column(nullable = false)
55+
private ProfileStatus profileStatus;
56+
5257
@ManyToOne(fetch = FetchType.LAZY)
5358
@JoinColumn(name = "user_id", updatable = false, nullable = false)
5459
private User user;
@@ -66,6 +71,7 @@ public Profile(String name,
6671
String phone,
6772
String email,
6873
ProfileType profileType,
74+
ProfileStatus profileStatus,
6975
Badge badge,
7076
User user) {
7177
this.name = name;
@@ -74,6 +80,7 @@ public Profile(String name,
7480
this.phone = phone;
7581
this.email = email;
7682
this.profileType = profileType;
83+
this.profileStatus = profileStatus;
7784
this.user = user;
7885
this.badge = (badge != null) ? badge : Badge.fromSolvedCount(this.quizSolvedCount);
7986

@@ -98,4 +105,8 @@ public void incrementQuizSolvedCount() {
98105
public void refreshBadge() {
99106
this.badge = Badge.fromSolvedCount(this.quizSolvedCount);
100107
}
108+
109+
public void deleteProfile(){
110+
this.profileStatus = ProfileStatus.DELETED;
111+
}
101112
}

src/main/java/everTale/everTale_be/domain/profile/repository/ProfileRepository.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package everTale.everTale_be.domain.profile.repository;
22

3+
import everTale.everTale_be.domain.profile.entity.Enum.ProfileStatus;
34
import everTale.everTale_be.domain.profile.entity.Profile;
45
import org.springframework.data.jpa.repository.JpaRepository;
56
import org.springframework.data.jpa.repository.Modifying;
@@ -18,7 +19,7 @@ public interface ProfileRepository extends JpaRepository<Profile, Long> {
1819
boolean existsByUserIdAndId(Long userId, Long id);
1920

2021
// 현재 로그인한 회원의 프로필들 가져오기
21-
List<Profile> findAllByUserId(Long userId);
22+
List<Profile> findAllByUserIdAndProfileStatusNot(Long userId, ProfileStatus status);
2223

2324
@Modifying
2425
@Transactional

src/main/java/everTale/everTale_be/domain/profile/service/ProfileService.java

Lines changed: 7 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import everTale.everTale_be.auth.jwt.JwtUtil;
55
import everTale.everTale_be.auth.service.TokenAuthService;
66
import everTale.everTale_be.auth.util.UserHelper;
7+
import everTale.everTale_be.domain.profile.entity.Enum.ProfileStatus;
78
import everTale.everTale_be.domain.profile.entity.Enum.ProfileType;
89
import everTale.everTale_be.domain.profile.entity.Profile;
910
import everTale.everTale_be.domain.profile.dto.request.ChildProfileRequestDto;
@@ -46,7 +47,7 @@ public void createChildProfile(ChildProfileRequestDto requestDto){
4647
if (exists){
4748
throw new BadRequestHandler(ErrorStatus.ALREADY_EXISTS_PROFILE);
4849
}
49-
profileRepository.save(requestDto.toEntity(rootUser, ProfileType.CHILD));
50+
profileRepository.save(requestDto.toEntity(rootUser, ProfileType.CHILD, ProfileStatus.ACTIVE));
5051
}
5152

5253
public void createParentProfile() {
@@ -62,6 +63,7 @@ public void createParentProfile() {
6263
.phone(rootUser.getPhone())
6364
.user(rootUser)
6465
.profileType(ProfileType.PARENT)
66+
.profileStatus(ProfileStatus.ACTIVE)
6567
.build();
6668

6769
profileRepository.save(parentProfile);
@@ -70,7 +72,7 @@ public void createParentProfile() {
7072
@Transactional(readOnly = true)
7173
public ProfileListResponseDto getProfiles(){
7274
Long userId = userHelper.getRootUserId();
73-
List<Profile> profiles = profileRepository.findAllByUserId(userId);
75+
List<Profile> profiles = profileRepository.findAllByUserIdAndProfileStatusNot(userId, ProfileStatus.DELETED);
7476

7577
return ProfileListResponseDto.from(profiles);
7678
}
@@ -127,43 +129,21 @@ public void updatePassword(PasswordUpdateRequestDto requestDto) {
127129
rootUser.changePassword(passwordEncoder.encode(requestDto.getNewPassword()));
128130
}
129131

130-
public ProfileEnterResponseDto reissueWithProfile(String refreshToken, Long profileId) {
131-
Long userId = jwtProvider.getUserIdFromToken(refreshToken);
132-
133-
String storedRefreshToken = tokenAuthService.getRefreshToken(userId);
134-
if (!storedRefreshToken.equals(refreshToken)) {
135-
throw new UnAuthorizedHandler(ErrorStatus.INVALID_REFRESH_TOKEN);
136-
}
137-
138-
Profile profile = profileRepository.findById(profileId)
139-
.orElseThrow(()-> new NotFoundHandler(ErrorStatus.NOT_FOUND_PROFILE));
140-
String newAccessToken = jwtUtil.generateAccessTokenWithProfile(userId, profile.getId());
141-
String newRefreshToken = jwtUtil.generateRefreshTokenWithProfile(userId, profile.getId());
142-
tokenAuthService.saveRefreshToken(profileId, newRefreshToken);
143-
144-
return ProfileEnterResponseDto.from(profile, newAccessToken, newRefreshToken, jwtUtil);
145-
}
146-
147-
// 프로필 삭제 (회원 탈퇴 X)
148-
public void deleteProfile(String accessToken){
149-
Long profileId = profileHelper.getAuthenticatedProfileId();
150-
151-
tokenAuthService.addToBlackListForAccessToken(accessToken, "WITHDRAW");
152-
profileRepository.anonymizeProfile(profileId);
153-
}
154-
132+
@Transactional(readOnly = true)
155133
public void isParent(Profile profile) {
156134
if (profile.getProfileType() != ProfileType.PARENT) {
157135
throw new UnAuthorizedHandler(ErrorStatus.UNAUTHORIZED_PROFILE_ACCESS);
158136
}
159137
}
160138

139+
@Transactional(readOnly = true)
161140
public void validateChildProfileAccess(Profile profile, Long profileId) {
162141
if (!profile.getId().equals(profileId)) {
163142
throw new UnAuthorizedHandler(ErrorStatus.UNAUTHORIZED_PROFILE_ACCESS);
164143
}
165144
}
166145

146+
@Transactional(readOnly = true)
167147
public void validateParentProfileAccess(Profile parent, Long profileId) {
168148
boolean isMyChild = profileRepository.existsByUserIdAndId(parent.getUser().getId(), profileId);
169149
if (!isMyChild) {

0 commit comments

Comments
 (0)