Skip to content

Commit 08d3200

Browse files
authored
Merge pull request #38 from trans-talk/develop
[release] add withdraw member feature
2 parents bd236c9 + 93ce533 commit 08d3200

28 files changed

Lines changed: 413 additions & 220 deletions

src/main/java/com/wootech/transtalk/client/GoogleClient.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ public class GoogleClient {
3838
private String redirectUri;
3939
@Value("${GOOGLE_AUTHORIZE_URI}")
4040
private String authorizeUri;
41+
@Value("${spring.oauth.google.client.uri.revoke}")
42+
private String revokeUri;
43+
4144

4245
public static final String BEARER_PREFIX = "Bearer ";
4346

@@ -118,5 +121,33 @@ public GoogleProfileResponse requestProfile(String accessToken) {
118121
return googleProfileResponse;
119122
}
120123

124+
// 회원탈퇴
125+
public void revokeToken(String token) {
126+
if (token == null || token.isEmpty()) {
127+
log.warn("[GoogleClient] Revoke token is empty, skipping revocation.");
128+
return;
129+
}
130+
131+
URI uri = UriComponentsBuilder.fromUriString(this.revokeUri)
132+
.queryParam("token", token)
133+
.build()
134+
.toUri();
135+
log.info("[GoogleClient] Revoke API URI: {}", uri);
136+
137+
try {
138+
restClient.post()
139+
.uri(uri)
140+
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
141+
.retrieve()
142+
.onStatus(HttpStatusCode::isError, (request, response) -> {
143+
String body = new String(response.getBody().readAllBytes());
144+
log.error("[GoogleClient] Revoke Token Failed for token: [{}]. Response: {}", token, body);
145+
})
146+
.toBodilessEntity();
147+
log.info("[GoogleClient] Successfully Revoked Token for User Token: [{}]", token);
148+
} catch (Exception e) {
149+
log.error("[GoogleClient] Exception During Token Revocation for Token: [{}]. Error: {}", token, e.getMessage(), e);
150+
}
151+
}
121152

122153
}

src/main/java/com/wootech/transtalk/config/DataInitializer.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@
1010
import com.wootech.transtalk.repository.user.UserRepository;
1111
import com.wootech.transtalk.service.chatroom.ChatRoomService;
1212
import jakarta.annotation.PostConstruct;
13-
import java.time.Instant;
14-
import java.util.List;
1513
import lombok.RequiredArgsConstructor;
1614
import org.springframework.stereotype.Component;
1715

16+
import java.time.Instant;
17+
import java.util.List;
18+
1819
@Component
1920
@RequiredArgsConstructor
2021
public class DataInitializer {
@@ -70,4 +71,4 @@ private void createChat(Long ownerId, String ownerEmail, Long chatRoomId) {
7071
}
7172
}
7273
}
73-
}
74+
}

src/main/java/com/wootech/transtalk/config/jwt/JwtAuthenticationFilter.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import com.wootech.transtalk.config.util.JwtUtil;
44
import com.wootech.transtalk.dto.auth.AuthUser;
55
import com.wootech.transtalk.enums.UserRole;
6+
import com.wootech.transtalk.exception.custom.NotFoundException;
7+
import com.wootech.transtalk.service.user.UserDetailsServiceImpl;
68
import io.jsonwebtoken.Claims;
79
import io.jsonwebtoken.ExpiredJwtException;
810
import io.jsonwebtoken.MalformedJwtException;
@@ -14,6 +16,7 @@
1416
import lombok.NonNull;
1517
import lombok.RequiredArgsConstructor;
1618
import lombok.extern.slf4j.Slf4j;
19+
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
1720
import org.springframework.security.core.AuthenticationException;
1821
import org.springframework.security.core.context.SecurityContextHolder;
1922
import org.springframework.stereotype.Component;
@@ -29,6 +32,7 @@
2932
public class JwtAuthenticationFilter extends OncePerRequestFilter {
3033

3134
private final JwtUtil jwtUtil;
35+
private final UserDetailsServiceImpl userDetailsService;
3236

3337
@Override
3438
protected void doFilterInternal(
@@ -43,11 +47,18 @@ protected void doFilterInternal(
4347

4448
try {
4549
Claims claims = jwtUtil.extractClaims(jwt);
50+
String email = jwtUtil.getEmail(jwt);
51+
52+
// 탈퇴된 사용자라면 404 에러
53+
userDetailsService.loadUserByUsername(email);
4654

4755
if (SecurityContextHolder.getContext().getAuthentication() == null) {
4856
setAuthentication(claims);
4957
}
5058

59+
}catch (NotFoundException e) {
60+
log.error(WITHDRAWN_USER_ERROR);
61+
throw new AuthenticationCredentialsNotFoundException(WITHDRAWN_USER_ERROR, e);
5162
} catch (SecurityException | MalformedJwtException e) {
5263
log.error(INVALID_JWT_SIGNATURE_ERROR, e);
5364
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, INVALID_JWT_SIGNATURE_ERROR);

src/main/java/com/wootech/transtalk/config/jwt/JwtChannelInterceptor.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
import com.wootech.transtalk.config.util.JwtUtil;
44
import com.wootech.transtalk.enums.UserRole;
5+
import com.wootech.transtalk.exception.custom.NotFoundException;
56
import com.wootech.transtalk.exception.custom.UnauthorizedException;
67
import com.wootech.transtalk.event.Events;
78
import com.wootech.transtalk.event.ExitToChatRoomEvent;
9+
import com.wootech.transtalk.service.user.UserDetailsServiceImpl;
810
import lombok.RequiredArgsConstructor;
911
import lombok.extern.slf4j.Slf4j;
1012
import org.springframework.http.HttpStatusCode;
@@ -20,13 +22,15 @@
2022
import java.util.List;
2123

2224
import static com.wootech.transtalk.exception.ErrorMessages.JWT_DOES_NOT_EXIST_ERROR;
25+
import static com.wootech.transtalk.exception.ErrorMessages.WITHDRAWN_USER_ERROR;
2326

2427
@Slf4j
2528
@Component
2629
@RequiredArgsConstructor
2730
public class JwtChannelInterceptor implements ChannelInterceptor {
2831

2932
private final JwtUtil jwtUtil;
33+
private final UserDetailsServiceImpl userDetailsService;
3034

3135
@Override
3236
public Message<?> preSend(Message<?> message, MessageChannel channel) {
@@ -53,8 +57,14 @@ private void handleConnect(StompHeaderAccessor accessor) {
5357
validateAccessToken(accessToken);
5458

5559
String userEmail = jwtUtil.getEmail(accessToken);
60+
// 탈퇴된 사용자라면 404 에러
61+
try {
62+
userDetailsService.loadUserByUsername(userEmail);
63+
} catch (NotFoundException e) {
64+
log.error(WITHDRAWN_USER_ERROR);
65+
throw new NotFoundException(WITHDRAWN_USER_ERROR, HttpStatusCode.valueOf(404));
66+
}
5667
accessor.setUser(new UsernamePasswordAuthenticationToken(userEmail, null, List.of(UserRole.ROLE_USER)));
57-
5868
log.info("[JwtChannelInterceptor] CONNECT - JWT Token validated for user={}", userEmail);
5969
}
6070
private String extractAccessToken(StompHeaderAccessor accessor) {

src/main/java/com/wootech/transtalk/config/util/CookieUtil.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
import jakarta.servlet.http.HttpServletResponse;
55
import lombok.AccessLevel;
66
import lombok.NoArgsConstructor;
7+
import lombok.extern.slf4j.Slf4j;
78

89
import static com.wootech.transtalk.config.util.JwtUtil.REFRESH_TOKEN_TIME;
910

11+
@Slf4j
1012
@NoArgsConstructor(access = AccessLevel.PRIVATE)
1113
public class CookieUtil {
1214

@@ -28,4 +30,16 @@ public static void addRefreshTokenCookie(HttpServletResponse response, String re
2830

2931
response.addHeader("Set-Cookie", cookieValue);
3032
}
33+
34+
public static void deleteRefreshTokenCookie(HttpServletResponse response) {
35+
String cookieName = "refreshToken";
36+
37+
String cookieValue = String.format(
38+
"%s=; Max-Age=0; Path=/; Secure; HttpOnly; SameSite=None",
39+
cookieName
40+
);
41+
42+
response.addHeader("Set-Cookie", cookieValue);
43+
log.info("[CookieUtil] Refresh Token Cookie Deleted: {}", cookieName);
44+
}
3145
}

src/main/java/com/wootech/transtalk/controller/auth/AuthController.java

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@
99
import jakarta.servlet.http.HttpServletResponse;
1010
import lombok.RequiredArgsConstructor;
1111
import lombok.extern.slf4j.Slf4j;
12+
import org.springframework.http.HttpHeaders;
1213
import org.springframework.security.core.annotation.AuthenticationPrincipal;
1314
import org.springframework.web.bind.annotation.*;
1415

1516
import java.net.URI;
1617
import java.time.LocalDateTime;
1718

1819
import static com.wootech.transtalk.config.util.CookieUtil.addRefreshTokenCookie;
20+
import static com.wootech.transtalk.config.util.CookieUtil.deleteRefreshTokenCookie;
21+
import static com.wootech.transtalk.exception.ErrorMessages.ACCESS_TOKEN_DOES_NOT_EXISTS_ERROR;
1922

2023
@Slf4j
2124
@RequiredArgsConstructor
@@ -69,12 +72,25 @@ public ApiResponse<Object> logOut() {
6972

7073
// 회원탈퇴
7174
@DeleteMapping("/withdraw")
72-
public ApiResponse<Object> withdrawUser(@AuthenticationPrincipal AuthUser authUser) {
73-
userService.withdrawUser(authUser);
74-
return ApiResponse.builder()
75-
.success(true)
76-
.message("회원탈퇴에 성공했습니다.")
77-
.timestamp(LocalDateTime.now())
78-
.build();
75+
public ApiResponse<Object> withdrawUser(
76+
@RequestHeader(HttpHeaders.AUTHORIZATION) String authorizationHeader,
77+
@AuthenticationPrincipal AuthUser authUser,
78+
HttpServletResponse response
79+
) {
80+
String accessToken = null;
81+
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
82+
accessToken = authorizationHeader.substring("Bearer ".length()).trim();
83+
}
84+
if (accessToken != null) {
85+
authService.withdrawUser(authUser, accessToken);
86+
deleteRefreshTokenCookie(response);
87+
return ApiResponse.builder()
88+
.success(true)
89+
.message("회원탈퇴에 성공했습니다.")
90+
.timestamp(LocalDateTime.now())
91+
.build();
92+
} else {
93+
throw new IllegalArgumentException(ACCESS_TOKEN_DOES_NOT_EXISTS_ERROR);
94+
}
7995
}
8096
}

src/main/java/com/wootech/transtalk/dto/auth/AuthUser.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
import lombok.Getter;
77
import org.springframework.security.core.GrantedAuthority;
88
import org.springframework.security.core.authority.SimpleGrantedAuthority;
9+
import org.springframework.security.core.userdetails.UserDetails;
910

1011
import java.util.Collection;
1112
import java.util.List;
1213

1314
@Getter
14-
public class AuthUser {
15+
public class AuthUser implements UserDetails {
1516

1617
private final Long userId;
1718
private final String email;
@@ -25,4 +26,14 @@ public AuthUser(Long userId, String email, String name, UserRole role) {
2526
this.name = name;
2627
this.authorities = List.of(new SimpleGrantedAuthority(role.name()));
2728
}
29+
30+
@Override
31+
public String getPassword() {
32+
return "";
33+
}
34+
35+
@Override
36+
public String getUsername() {
37+
return email;
38+
}
2839
}

src/main/java/com/wootech/transtalk/entity/Chat.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
import jakarta.persistence.GeneratedValue;
99
import jakarta.persistence.GenerationType;
1010
import jakarta.persistence.Id;
11-
import java.time.LocalDateTime;
12-
import java.time.ZoneId;
1311
import lombok.AccessLevel;
1412
import lombok.Getter;
1513
import lombok.NoArgsConstructor;

src/main/java/com/wootech/transtalk/entity/ChatRoom.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
package com.wootech.transtalk.entity;
22

3-
import com.fasterxml.jackson.core.JsonToken;
43
import com.wootech.transtalk.enums.TranslateLanguage;
54
import com.wootech.transtalk.exception.custom.NotFoundException;
65
import jakarta.persistence.*;
7-
import java.time.Instant;
86
import lombok.AccessLevel;
97
import lombok.Getter;
108
import lombok.NoArgsConstructor;
9+
import org.hibernate.annotations.SQLDelete;
10+
import org.hibernate.annotations.Where;
1111
import org.springframework.http.HttpStatusCode;
1212

13+
import java.time.Instant;
1314
import java.util.ArrayList;
1415
import java.util.List;
1516

1617
import static com.wootech.transtalk.exception.ErrorMessages.PARTICIPANT_NOT_FOUND_ERROR;
1718

19+
@SQLDelete(sql = "UPDATE chat_room SET deleted_at = NOW() WHERE chat_room_id = ?")
20+
@Where(clause = "deleted_at IS NULL")
1821
@Getter
1922
@Entity
2023
@NoArgsConstructor(access = AccessLevel.PROTECTED)

src/main/java/com/wootech/transtalk/entity/Participant.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
import lombok.AccessLevel;
77
import lombok.Getter;
88
import lombok.NoArgsConstructor;
9+
import org.hibernate.annotations.SQLDelete;
10+
import org.hibernate.annotations.Where;
911

12+
@SQLDelete(sql = "UPDATE participant SET deleted_at = NOW() WHERE participant_id = ?")
13+
@Where(clause = "deleted_at IS NULL")
1014
@Getter
1115
@Entity
1216
@NoArgsConstructor(access = AccessLevel.PROTECTED)

0 commit comments

Comments
 (0)