Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Refactor] 토큰 관리 방식 변경 #34

Merged
merged 6 commits into from
Sep 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class AuthService {

private final TokenProvider tokenProvider;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@
@Component
public class TokenProvider {

private final BlacklistTokenRedisRepository blacklistTokenRedisRepository;

private static final String AUTH_ID = "ID";
private static final String AUTH_KEY = "AUTHORITY";
private static final String AUTH_EMAIL = "EMAIL";

private final BlacklistTokenRedisRepository blacklistTokenRedisRepository;

private Key key;
private final String secretKey;
private final long accessExpirations;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
package project.backend.business.auth.response;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class TokenServiceResponse {

private final String accessToken;
private final String refreshToken;

public TokenServiceResponse(String accessToken) {
this.accessToken = accessToken;
this.refreshToken = null;
}

public TokenServiceResponse(String accessToken, String refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
}

public static TokenServiceResponse of(final String accessToken, final String refreshToken) {
return new TokenServiceResponse(accessToken, refreshToken);
}

public TokenServiceResponse withoutRefreshToken() {
return new TokenServiceResponse(this.accessToken);
}
}
5 changes: 3 additions & 2 deletions src/main/java/project/backend/business/post/PostService.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
@RequiredArgsConstructor
public class PostService {

private final int PAGE_SIZE = 10;
private static final int PAGE_SIZE = 10;

private final UserReader userReader;
private final PostReader postReader;
private final PostManager postManager;
Expand Down Expand Up @@ -67,7 +68,7 @@ public PostDetailResponse getPostDetail(Long userId,
public CreateUpdatePostResponse createNewPostDetail(Long userId,
CreatePostServiceRequest createPostServiceRequest) {
String summary = summaryAIManager.getSummary(createPostServiceRequest);
User user = userReader.findUserById(userId);
User user = userReader.readUserById(userId);
Long postId = postManager.createTempPost(user, createPostServiceRequest.getUrl(), summary);

return new CreateUpdatePostResponse(postId);
Expand Down
10 changes: 5 additions & 5 deletions src/main/java/project/backend/business/user/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import project.backend.business.user.implement.UserReader;
import project.backend.business.user.response.UserResponse;
import project.backend.business.user.response.UserInfoResponse;
import project.backend.entity.user.User;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class UserService {

private final UserReader userReader;

public UserResponse findUserById(Long id) {
User user = userReader.findUserById(id);
return new UserResponse(user.getName(), user.getEmail(), user.getProfileImageUrl());
@Transactional(readOnly = true)
public UserInfoResponse getUserInfo(Long id) {
User user = userReader.readUserById(id);
return new UserInfoResponse(user.getName(), user.getEmail(), user.getProfileImageUrl());
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package project.backend.business.user.implement;

import java.util.Optional;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import project.backend.common.error.CustomException;
Expand All @@ -15,16 +14,7 @@ public class UserReader {
private final UserRepository userRepository;

public User readUserById(Long userId) {

return userRepository.findById(userId)
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
}

public User findUserById(Long userId) {
if (userId == null) {
return null;
}
Optional<User> user = userRepository.findById(userId);
return user.orElse(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

@Getter
@AllArgsConstructor
public class UserResponse {
public class UserInfoResponse {

private final String name;
private final String email;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
Expand All @@ -13,6 +14,7 @@
import project.backend.business.auth.AuthService;
import project.backend.business.auth.request.TokenServiceRequest;
import project.backend.business.auth.response.TokenServiceResponse;
import project.backend.presentation.auth.util.TokenCookieManager;
import project.backend.presentation.auth.util.TokenExtractor;

@RestController
Expand All @@ -22,23 +24,42 @@ public class AuthController {

private final AuthService authService;
private final TokenExtractor tokenExtractor;
private final TokenCookieManager tokenCookieManager;

@RequestMapping("/login/kakao")
public ResponseEntity<TokenServiceResponse> loginKakao(@RequestParam(name = "code") String code) throws JsonProcessingException {
@PostMapping("/login/kakao")
public ResponseEntity<TokenServiceResponse> loginKakao(
@RequestParam(name = "code") String code,
HttpServletResponse response) throws JsonProcessingException {
TokenServiceResponse tokenServiceResponse = authService.kakaoLogin(code);
return new ResponseEntity<>(tokenServiceResponse, HttpStatus.OK);

tokenCookieManager.addRefreshTokenCookie(response, tokenServiceResponse.getRefreshToken());

TokenServiceResponse tokenServiceResponseWithoutRefreshToken = tokenServiceResponse.withoutRefreshToken();
return new ResponseEntity<>(tokenServiceResponseWithoutRefreshToken, HttpStatus.OK);
}

@GetMapping("/reissue")
public ResponseEntity<TokenServiceResponse> reissueToken(HttpServletRequest request) {
public ResponseEntity<TokenServiceResponse> reissueToken(
HttpServletRequest request,
HttpServletResponse response) {
TokenServiceRequest tokenServiceRequest = tokenExtractor.extractTokenRequest(request);
return new ResponseEntity<>(authService.reissueAccessToken(tokenServiceRequest), HttpStatus.OK);
TokenServiceResponse tokenServiceResponse = authService.reissueAccessToken(tokenServiceRequest);

tokenCookieManager.addRefreshTokenCookie(response, tokenServiceResponse.getRefreshToken());

TokenServiceResponse tokenServiceResponseWithoutRefreshToken = tokenServiceResponse.withoutRefreshToken();
return new ResponseEntity<>(tokenServiceResponseWithoutRefreshToken, HttpStatus.OK);
}

@PostMapping("/logout")
public ResponseEntity<Void> logout(HttpServletRequest request) {
public ResponseEntity<Void> logout(
HttpServletRequest request,
HttpServletResponse response) {
TokenServiceRequest tokenServiceRequest = tokenExtractor.extractTokenRequest(request);
authService.logout(tokenServiceRequest);

tokenCookieManager.removeRefreshTokenCookie(response);

return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package project.backend.presentation.auth.util;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;

@Component
public class TokenCookieManager {

private static final int DEFAULT_EXPIRATION = 24 * 60 * 60; // 1일 기본 만료 시간
private static final String COOKIE_PATH = "/";

public void addRefreshTokenCookie(HttpServletResponse response, String refreshToken) {
addRefreshTokenCookie(response, refreshToken, DEFAULT_EXPIRATION);
}

public void addRefreshTokenCookie(HttpServletResponse response, String refreshToken, int maxAge) {
Cookie refreshTokenCookie = new Cookie("refreshToken", refreshToken);
refreshTokenCookie.setHttpOnly(true); // 클라이언트에서 접근 불가
refreshTokenCookie.setSecure(true); // HTTPS 환경에서만 전송
refreshTokenCookie.setPath(COOKIE_PATH); // 경로 설정
refreshTokenCookie.setMaxAge(maxAge); // 만료 시간 설정
response.addCookie(refreshTokenCookie);
}

public void removeRefreshTokenCookie(HttpServletResponse response) {
Cookie refreshTokenCookie = new Cookie("refreshToken", null);
refreshTokenCookie.setHttpOnly(true);
refreshTokenCookie.setSecure(true);
refreshTokenCookie.setPath(COOKIE_PATH);
refreshTokenCookie.setMaxAge(0); // 즉시 만료
response.addCookie(refreshTokenCookie);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

import static project.backend.business.auth.implement.KakaoLoginManager.BEARER;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Collections;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
Expand All @@ -16,13 +19,13 @@ public class TokenExtractor {
@Value("${jwt.access_header}")
private String accessTokenHeader;

@Value("${jwt.refresh_header}")
private String refreshTokenHeader;
@Value("${jwt.refresh_cookie_name}")
private String refreshTokenCookieName;

public TokenServiceRequest extractTokenRequest(HttpServletRequest request) {
String accessToken = extractAccessToken(request).orElse(null);
String refreshToken = extractRefreshToken(request).orElse(null);
log.info("Logout initiated with accessToken: {}, refreshToken: {}", accessToken, refreshToken);
log.info("Token extraction initiated with accessToken: {}, refreshToken: {}", accessToken, refreshToken);
return TokenServiceRequest.builder()
.accessToken(accessToken)
.refreshToken(refreshToken)
Expand All @@ -36,8 +39,12 @@ private Optional<String> extractAccessToken(HttpServletRequest request) {
}

private Optional<String> extractRefreshToken(HttpServletRequest request) {
return Optional.ofNullable(request.getHeader(refreshTokenHeader))
.filter(refreshToken -> refreshToken.startsWith(BEARER))
.map(refreshToken -> refreshToken.replace(BEARER, ""));
return Optional.ofNullable(request.getCookies())
.map(Arrays::asList)
.orElse(Collections.emptyList())
.stream()
.filter(cookie -> refreshTokenCookieName.equals(cookie.getName()))
.map(Cookie::getValue)
.findAny();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import project.backend.business.user.UserService;
import project.backend.business.user.response.UserResponse;
import project.backend.business.user.response.UserInfoResponse;
import project.backend.security.aop.AssignCurrentUserInfo;
import project.backend.security.aop.CurrentUserInfo;

Expand All @@ -19,8 +19,8 @@ public class UserController {

@GetMapping
@AssignCurrentUserInfo
public ResponseEntity<UserResponse> getUser(CurrentUserInfo userInfo) {
UserResponse response = userService.findUserById(userInfo.getUserId());
public ResponseEntity<UserInfoResponse> getUserInfo(CurrentUserInfo userInfo) {
UserInfoResponse response = userService.getUserInfo(userInfo.getUserId());
return ResponseEntity.ok(response);
}
}
Loading
Loading