Skip to content

Commit bf67657

Browse files
authored
Merge branch 'develop' into feat/447-add-updating-interested-country-region-api
2 parents 90272f9 + afe27ab commit bf67657

File tree

22 files changed

+432
-101
lines changed

22 files changed

+432
-101
lines changed

src/main/java/com/example/solidconnection/auth/controller/AuthController.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import com.example.solidconnection.auth.dto.EmailSignInRequest;
44
import com.example.solidconnection.auth.dto.EmailSignUpTokenRequest;
55
import com.example.solidconnection.auth.dto.EmailSignUpTokenResponse;
6-
import com.example.solidconnection.auth.dto.ReissueRequest;
76
import com.example.solidconnection.auth.dto.ReissueResponse;
87
import com.example.solidconnection.auth.dto.SignInResponse;
98
import com.example.solidconnection.auth.dto.SignUpRequest;
@@ -19,6 +18,7 @@
1918
import com.example.solidconnection.common.exception.ErrorCode;
2019
import com.example.solidconnection.common.resolver.AuthorizedUser;
2120
import com.example.solidconnection.siteuser.domain.AuthType;
21+
import jakarta.servlet.http.HttpServletRequest;
2222
import jakarta.servlet.http.HttpServletResponse;
2323
import jakarta.validation.Valid;
2424
import lombok.RequiredArgsConstructor;
@@ -118,10 +118,10 @@ public ResponseEntity<Void> quit(
118118

119119
@PostMapping("/reissue")
120120
public ResponseEntity<ReissueResponse> reissueToken(
121-
@AuthorizedUser long siteUserId,
122-
@Valid @RequestBody ReissueRequest reissueRequest
121+
HttpServletRequest request
123122
) {
124-
ReissueResponse reissueResponse = authService.reissue(siteUserId, reissueRequest);
123+
String refreshToken = refreshTokenCookieManager.getRefreshToken(request);
124+
ReissueResponse reissueResponse = authService.reissue(refreshToken);
125125
return ResponseEntity.ok(reissueResponse);
126126
}
127127

src/main/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManager.java

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,35 @@
11
package com.example.solidconnection.auth.controller;
22

3+
import static com.example.solidconnection.common.exception.ErrorCode.REFRESH_TOKEN_NOT_EXISTS;
4+
5+
import com.example.solidconnection.auth.controller.config.RefreshTokenCookieProperties;
36
import com.example.solidconnection.auth.domain.TokenType;
7+
import com.example.solidconnection.common.exception.CustomException;
8+
import jakarta.servlet.http.Cookie;
9+
import jakarta.servlet.http.HttpServletRequest;
410
import jakarta.servlet.http.HttpServletResponse;
11+
import java.util.Arrays;
12+
import lombok.RequiredArgsConstructor;
513
import org.springframework.http.HttpHeaders;
614
import org.springframework.http.ResponseCookie;
715
import org.springframework.stereotype.Component;
816

917
@Component
18+
@RequiredArgsConstructor
1019
public class RefreshTokenCookieManager {
1120

1221
private static final String COOKIE_NAME = "refreshToken";
1322
private static final String PATH = "/";
14-
private static final String SAME_SITE = "Strict";
23+
24+
private final RefreshTokenCookieProperties properties;
1525

1626
public void setCookie(HttpServletResponse response, String refreshToken) {
1727
long maxAge = convertExpireTimeToCookieMaxAge(TokenType.REFRESH.getExpireTime());
1828
setRefreshTokenCookie(response, refreshToken, maxAge);
1929
}
2030

2131
private long convertExpireTimeToCookieMaxAge(long milliSeconds) {
22-
// jwt의 expireTime: millisecond, cookie의 maxAge: second
32+
// jwt의 expireTime 단위인 millisecond를 cookie의 maxAge 단위인 second로 변환
2333
return milliSeconds / 1000;
2434
}
2535

@@ -35,8 +45,31 @@ private void setRefreshTokenCookie(
3545
.secure(true)
3646
.path(PATH)
3747
.maxAge(maxAge)
38-
.sameSite(SAME_SITE)
48+
.domain(properties.cookieDomain())
49+
.sameSite(properties.sameSite())
3950
.build();
4051
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
4152
}
53+
54+
public String getRefreshToken(HttpServletRequest request) {
55+
// 쿠키가 없거나 비어있는 경우 예외 발생
56+
Cookie[] cookies = request.getCookies();
57+
if (cookies == null || cookies.length == 0) {
58+
throw new CustomException(REFRESH_TOKEN_NOT_EXISTS);
59+
}
60+
61+
// refreshToken 쿠키가 없는 경우 예외 발생
62+
Cookie refreshTokenCookie = Arrays.stream(cookies)
63+
.filter(cookie -> COOKIE_NAME.equals(cookie.getName()))
64+
.findFirst()
65+
.orElseThrow(() -> new CustomException(REFRESH_TOKEN_NOT_EXISTS));
66+
67+
// 쿠키 값이 비어있는 경우 예외 발생
68+
String refreshToken = refreshTokenCookie.getValue();
69+
if (refreshToken == null || refreshToken.isBlank()) {
70+
throw new CustomException(REFRESH_TOKEN_NOT_EXISTS);
71+
}
72+
return refreshToken;
73+
}
4274
}
75+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.example.solidconnection.auth.controller.config;
2+
3+
import org.springframework.boot.context.properties.ConfigurationProperties;
4+
import org.springframework.boot.web.server.Cookie.SameSite;
5+
6+
@ConfigurationProperties(prefix = "token.refresh")
7+
public record RefreshTokenCookieProperties(
8+
String cookieDomain
9+
) {
10+
11+
public String sameSite() {
12+
if (isDomainSet()) {
13+
return SameSite.STRICT.attributeValue(); // 도메인을 지정한 경우 SameSite=Strict
14+
}
15+
return SameSite.NONE.attributeValue(); // 도메인을 지정하지 않은 경우 SameSite=None
16+
}
17+
18+
private boolean isDomainSet() {
19+
return cookieDomain != null && !cookieDomain.isBlank();
20+
}
21+
}

src/main/java/com/example/solidconnection/auth/dto/ReissueRequest.java

Lines changed: 0 additions & 9 deletions
This file was deleted.

src/main/java/com/example/solidconnection/auth/service/AuthService.java

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import static com.example.solidconnection.common.exception.ErrorCode.REFRESH_TOKEN_EXPIRED;
44
import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND;
55

6-
import com.example.solidconnection.auth.dto.ReissueRequest;
76
import com.example.solidconnection.auth.dto.ReissueResponse;
87
import com.example.solidconnection.auth.token.TokenBlackListService;
98
import com.example.solidconnection.common.exception.CustomException;
@@ -28,12 +27,8 @@ public class AuthService {
2827
* - 리프레시 토큰을 삭제한다.
2928
* */
3029
public void signOut(String token) {
31-
Subject subject = authTokenProvider.parseSubject(token);
32-
long siteUserId = Long.parseLong(subject.value());
33-
SiteUser siteUser = siteUserRepository.findById(siteUserId)
34-
.orElseThrow(() -> new CustomException(USER_NOT_FOUND));
35-
36-
AccessToken accessToken = authTokenProvider.generateAccessToken(subject, siteUser.getRole());
30+
SiteUser siteUser = authTokenProvider.parseSiteUser(token);
31+
AccessToken accessToken = authTokenProvider.generateAccessToken(siteUser);
3732
authTokenProvider.deleteRefreshTokenByAccessToken(accessToken);
3833
tokenBlackListService.addToBlacklist(accessToken);
3934
}
@@ -58,17 +53,14 @@ public void quit(long siteUserId, String token) {
5853
* - 유효한 리프레시토큰이면, 액세스 토큰을 재발급한다.
5954
* - 그렇지 않으면 예외를 발생시킨다.
6055
* */
61-
public ReissueResponse reissue(long siteUserId, ReissueRequest reissueRequest) {
56+
public ReissueResponse reissue(String requestedRefreshToken) {
6257
// 리프레시 토큰 확인
63-
String requestedRefreshToken = reissueRequest.refreshToken();
6458
if (!authTokenProvider.isValidRefreshToken(requestedRefreshToken)) {
6559
throw new CustomException(REFRESH_TOKEN_EXPIRED);
6660
}
6761
// 액세스 토큰 재발급
68-
SiteUser siteUser = siteUserRepository.findById(siteUserId)
69-
.orElseThrow(() -> new CustomException(USER_NOT_FOUND));
70-
Subject subject = authTokenProvider.parseSubject(requestedRefreshToken);
71-
AccessToken newAccessToken = authTokenProvider.generateAccessToken(subject, siteUser.getRole());
62+
SiteUser siteUser = authTokenProvider.parseSiteUser(requestedRefreshToken);
63+
AccessToken newAccessToken = authTokenProvider.generateAccessToken(siteUser);
7264
return ReissueResponse.from(newAccessToken);
7365
}
7466
}

src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package com.example.solidconnection.auth.service;
22

3+
import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND;
4+
35
import com.example.solidconnection.auth.domain.TokenType;
6+
import com.example.solidconnection.common.exception.CustomException;
47
import com.example.solidconnection.siteuser.domain.Role;
58
import com.example.solidconnection.siteuser.domain.SiteUser;
9+
import com.example.solidconnection.siteuser.repository.SiteUserRepository;
610
import java.util.Map;
711
import java.util.Objects;
812
import lombok.RequiredArgsConstructor;
@@ -17,15 +21,21 @@ public class AuthTokenProvider {
1721

1822
private final RedisTemplate<String, String> redisTemplate;
1923
private final TokenProvider tokenProvider;
24+
private final SiteUserRepository siteUserRepository;
2025

21-
public AccessToken generateAccessToken(Subject subject, Role role) {
26+
public AccessToken generateAccessToken(SiteUser siteUser) {
27+
Subject subject = toSubject(siteUser);
28+
Role role = siteUser.getRole();
2229
String token = tokenProvider.generateToken(
23-
subject.value(), Map.of(ROLE_CLAIM_KEY, role.name()), TokenType.ACCESS
30+
subject.value(),
31+
Map.of(ROLE_CLAIM_KEY, role.name()),
32+
TokenType.ACCESS
2433
);
2534
return new AccessToken(subject, role, token);
2635
}
2736

28-
public RefreshToken generateAndSaveRefreshToken(Subject subject) {
37+
public RefreshToken generateAndSaveRefreshToken(SiteUser siteUser) {
38+
Subject subject = toSubject(siteUser);
2939
String token = tokenProvider.generateToken(subject.value(), TokenType.REFRESH);
3040
tokenProvider.saveToken(token, TokenType.REFRESH);
3141
return new RefreshToken(subject, token);
@@ -49,9 +59,11 @@ public void deleteRefreshTokenByAccessToken(AccessToken accessToken) {
4959
redisTemplate.delete(refreshTokenKey);
5060
}
5161

52-
public Subject parseSubject(String token) {
62+
public SiteUser parseSiteUser(String token) {
5363
String subject = tokenProvider.parseSubject(token);
54-
return new Subject(subject);
64+
long siteUserId = Long.parseLong(subject);
65+
return siteUserRepository.findById(siteUserId)
66+
.orElseThrow(() -> new CustomException(USER_NOT_FOUND));
5567
}
5668

5769
public Subject toSubject(SiteUser siteUser) {

src/main/java/com/example/solidconnection/auth/service/SignInService.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@ public class SignInService {
1515
@Transactional
1616
public SignInResponse signIn(SiteUser siteUser) {
1717
resetQuitedAt(siteUser);
18-
Subject subject = authTokenProvider.toSubject(siteUser);
19-
AccessToken accessToken = authTokenProvider.generateAccessToken(subject, siteUser.getRole());
20-
RefreshToken refreshToken = authTokenProvider.generateAndSaveRefreshToken(subject);
18+
AccessToken accessToken = authTokenProvider.generateAccessToken(siteUser);
19+
RefreshToken refreshToken = authTokenProvider.generateAndSaveRefreshToken(siteUser);
2120
return SignInResponse.of(accessToken, refreshToken);
2221
}
2322

src/main/java/com/example/solidconnection/chat/controller/ChatController.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.example.solidconnection.chat.controller;
22

33
import com.example.solidconnection.chat.dto.ChatMessageResponse;
4+
import com.example.solidconnection.chat.dto.ChatParticipantResponse;
45
import com.example.solidconnection.chat.dto.ChatRoomListResponse;
56
import com.example.solidconnection.chat.service.ChatService;
67
import com.example.solidconnection.common.dto.SliceResponse;
@@ -41,6 +42,15 @@ public ResponseEntity<SliceResponse<ChatMessageResponse>> getChatMessages(
4142
return ResponseEntity.ok(response);
4243
}
4344

45+
@GetMapping("rooms/{room-id}/partner")
46+
public ResponseEntity<ChatParticipantResponse> getChatPartner(
47+
@AuthorizedUser long siteUserId,
48+
@PathVariable("room-id") Long roomId
49+
) {
50+
ChatParticipantResponse response = chatService.getChatPartner(siteUserId, roomId);
51+
return ResponseEntity.ok(response);
52+
}
53+
4454
@PutMapping("/rooms/{room-id}/read")
4555
public ResponseEntity<Void> markChatMessagesAsRead(
4656
@AuthorizedUser long siteUserId,

src/main/java/com/example/solidconnection/chat/service/ChatService.java

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,6 @@ private ChatRoomResponse toChatRoomResponse(ChatRoom chatRoom, long siteUserId)
8484
return ChatRoomResponse.of(chatRoom.getId(), lastChatMessage, lastReceivedTime, partner, unReadCount);
8585
}
8686

87-
private ChatParticipant findPartner(ChatRoom chatRoom, long siteUserId) {
88-
if (chatRoom.isGroup()) {
89-
throw new CustomException(INVALID_CHAT_ROOM_STATE);
90-
}
91-
return chatRoom.getChatParticipants().stream()
92-
.filter(participant -> participant.getSiteUserId() != siteUserId)
93-
.findFirst()
94-
.orElseThrow(() -> new CustomException(CHAT_PARTNER_NOT_FOUND));
95-
}
96-
9787
@Transactional(readOnly = true)
9888
public SliceResponse<ChatMessageResponse> getChatMessages(long siteUserId, long roomId, Pageable pageable) {
9989
validateChatRoomParticipant(siteUserId, roomId);
@@ -107,6 +97,26 @@ public SliceResponse<ChatMessageResponse> getChatMessages(long siteUserId, long
10797
return SliceResponse.of(content, chatMessages);
10898
}
10999

100+
@Transactional(readOnly = true)
101+
public ChatParticipantResponse getChatPartner(long siteUserId, Long roomId) {
102+
ChatRoom chatRoom = chatRoomRepository.findById(roomId)
103+
.orElseThrow(() -> new CustomException(INVALID_CHAT_ROOM_STATE));
104+
ChatParticipant partnerParticipant = findPartner(chatRoom, siteUserId);
105+
SiteUser siteUser = siteUserRepository.findById(partnerParticipant.getSiteUserId())
106+
.orElseThrow(() -> new CustomException(USER_NOT_FOUND));
107+
return ChatParticipantResponse.of(siteUser.getId(), siteUser.getNickname(), siteUser.getProfileImageUrl());
108+
}
109+
110+
private ChatParticipant findPartner(ChatRoom chatRoom, long siteUserId) {
111+
if (chatRoom.isGroup()) {
112+
throw new CustomException(INVALID_CHAT_ROOM_STATE);
113+
}
114+
return chatRoom.getChatParticipants().stream()
115+
.filter(participant -> participant.getSiteUserId() != siteUserId)
116+
.findFirst()
117+
.orElseThrow(() -> new CustomException(CHAT_PARTNER_NOT_FOUND));
118+
}
119+
110120
public void validateChatRoomParticipant(long siteUserId, long roomId) {
111121
boolean isParticipant = chatParticipantRepository.existsByChatRoomIdAndSiteUserId(roomId, siteUserId);
112122
if (!isParticipant) {

src/main/java/com/example/solidconnection/common/exception/ErrorCode.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ public enum ErrorCode {
5656
ACCESS_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED.value(), "액세스 토큰이 만료되었습니다. 재발급 api를 호출해주세요."),
5757
REFRESH_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED.value(), "리프레시 토큰이 만료되었습니다. 다시 로그인을 진행해주세요."),
5858
ACCESS_DENIED(HttpStatus.FORBIDDEN.value(), "접근 권한이 없습니다."),
59+
REFRESH_TOKEN_NOT_EXISTS(HttpStatus.BAD_REQUEST.value(), "리프레시 토큰이 존재하지 않습니다."),
5960
PASSWORD_MISMATCH(HttpStatus.BAD_REQUEST.value(), "비밀번호가 일치하지 않습니다."),
6061
PASSWORD_NOT_CHANGED(HttpStatus.BAD_REQUEST.value(), "현재 비밀번호와 새 비밀번호가 동일합니다."),
6162
PASSWORD_NOT_CONFIRMED(HttpStatus.BAD_REQUEST.value(), "새 비밀번호가 일치하지 않습니다."),

0 commit comments

Comments
 (0)