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

[BE] 토큰 확인하는 API를 구현한다. #767

Merged
merged 7 commits into from
Oct 10, 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 @@ -34,7 +34,7 @@ public User resolveArgument(MethodParameter parameter, ModelAndViewContainer mav
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();

cookieResolver.checkLoginRequired(request);
String token = cookieResolver.extractAccessToken(request.getCookies());
String token = cookieResolver.extractAccessToken(request);
return authService.getAuthUser(token);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer m
return authService.assignGuestUser();
}

String token = cookieResolver.extractAccessToken(request.getCookies());
String token = cookieResolver.extractAccessToken(request);
return authService.getAuthUser(token);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
import com.bang_ggood.auth.controller.cookie.CookieResolver;
import com.bang_ggood.auth.dto.request.OauthLoginRequest;
import com.bang_ggood.auth.dto.response.AuthTokenResponse;
import com.bang_ggood.auth.dto.response.RefreshTokenCheckResponse;
import com.bang_ggood.auth.service.AuthService;
import com.bang_ggood.user.domain.User;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
Expand Down Expand Up @@ -45,8 +47,8 @@ public ResponseEntity<Void> login(@Valid @RequestBody OauthLoginRequest request)
@PostMapping("/oauth/logout")
public ResponseEntity<Void> logout(@AuthRequiredPrincipal User user,
HttpServletRequest httpServletRequest) {
String accessToken = cookieResolver.extractAccessToken(httpServletRequest.getCookies());
String refreshToken = cookieResolver.extractRefreshToken(httpServletRequest.getCookies());
String accessToken = cookieResolver.extractAccessToken(httpServletRequest);
String refreshToken = cookieResolver.extractRefreshToken(httpServletRequest);

authService.logout(accessToken, refreshToken, user);

Expand All @@ -60,15 +62,23 @@ public ResponseEntity<Void> logout(@AuthRequiredPrincipal User user,
}

@PostMapping("/accessToken/reissue")
public ResponseEntity<Void> reIssueAccessToken(HttpServletRequest httpServletRequest) {
public ResponseEntity<Void> reissueAccessToken(HttpServletRequest httpServletRequest) {
cookieResolver.checkLoginRequired(httpServletRequest);

String refreshToken = cookieResolver.extractRefreshToken(httpServletRequest.getCookies());
String accessToken = authService.reIssueAccessToken(refreshToken);
String refreshToken = cookieResolver.extractRefreshToken(httpServletRequest);
String accessToken = authService.reissueAccessToken(refreshToken);

ResponseCookie accessTokenCookie = cookieProvider.createAccessTokenCookie(accessToken);
return ResponseEntity.ok()
.header(HttpHeaders.SET_COOKIE, accessTokenCookie.toString())
.build();
}

@GetMapping("/refreshToken-check")
public ResponseEntity<RefreshTokenCheckResponse> check(HttpServletRequest httpServletRequest) {
boolean isRefreshTokenExist = !cookieResolver.isRefreshTokenEmpty(httpServletRequest);

RefreshTokenCheckResponse refreshTokenCheckResponse = RefreshTokenCheckResponse.from(isRefreshTokenExist);
return ResponseEntity.ok(refreshTokenCheckResponse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,47 +16,50 @@ public void checkLoginRequired(HttpServletRequest request) {
throw new BangggoodException(ExceptionCode.AUTHENTICATION_TOKEN_EMPTY);
}

if (isRefreshTokenEmpty(request.getCookies())) {
if (isRefreshTokenEmpty(request)) {
throw new BangggoodException(ExceptionCode.AUTHENTICATION_REFRESH_TOKEN_EMPTY);
}
}

private boolean isAccessTokenEmpty(Cookie[] cookies) {
return isTokenEmpty(cookies, CookieProvider.ACCESS_TOKEN_COOKIE_NAME);
private boolean isAccessTokenEmpty(HttpServletRequest request) {
return isTokenEmpty(request, CookieProvider.ACCESS_TOKEN_COOKIE_NAME);
}

private boolean isRefreshTokenEmpty(Cookie[] cookies) {
return isTokenEmpty(cookies, CookieProvider.REFRESH_TOKEN_COOKIE_NAME);
public boolean isRefreshTokenEmpty(HttpServletRequest request) {
return isTokenEmpty(request, CookieProvider.REFRESH_TOKEN_COOKIE_NAME);
}

private boolean isTokenEmpty(Cookie[] cookies, String cookieName) {
return Arrays.stream(cookies)
private boolean isTokenEmpty(HttpServletRequest request, String cookieName) {
if (request.getCookies() == null) {
return true;
}

return Arrays.stream(request.getCookies())
.noneMatch(cookie -> cookie.getName().equals(cookieName));
}

public String extractAccessToken(Cookie[] cookies) {
return extractToken(cookies, CookieProvider.ACCESS_TOKEN_COOKIE_NAME)
public String extractAccessToken(HttpServletRequest request) {
return extractToken(request, CookieProvider.ACCESS_TOKEN_COOKIE_NAME)
.orElseThrow(() -> new BangggoodException(ExceptionCode.AUTHENTICATION_ACCESS_TOKEN_EMPTY));
}

public String extractRefreshToken(Cookie[] cookies) {
return extractToken(cookies, CookieProvider.REFRESH_TOKEN_COOKIE_NAME)
public String extractRefreshToken(HttpServletRequest request) {
return extractToken(request, CookieProvider.REFRESH_TOKEN_COOKIE_NAME)
.orElseThrow(() -> new BangggoodException(ExceptionCode.AUTHENTICATION_REFRESH_TOKEN_EMPTY));
}

private Optional<String> extractToken(Cookie[] cookies, String cookieName) {
return Arrays.stream(cookies)
private Optional<String> extractToken(HttpServletRequest request, String cookieName) {
if (isTokenEmpty(request)) {
return Optional.empty();
}

return Arrays.stream(request.getCookies())
.filter(cookie -> cookie.getName().equals(cookieName))
.findAny()
.map(Cookie::getValue);
}

public boolean isTokenEmpty(HttpServletRequest request) {
if (request.getCookies() == null) {
return true;
}

Cookie[] cookies = request.getCookies();
return isAccessTokenEmpty(cookies) && isRefreshTokenEmpty(cookies);
return isAccessTokenEmpty(request) && isRefreshTokenEmpty(request);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.bang_ggood.auth.dto.response;

public record RefreshTokenCheckResponse(boolean isRefreshTokenExist) {

public static RefreshTokenCheckResponse from(boolean isRefreshTokenExist) {
return new RefreshTokenCheckResponse(isRefreshTokenExist);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public User getAuthUser(String token) {
}

@Transactional(readOnly = true)
public String reIssueAccessToken(String refreshToken) {
public String reissueAccessToken(String refreshToken) {
AuthUser authUser = jwtTokenResolver.resolveRefreshToken(refreshToken);
User user = userRepository.getUserById(authUser.id());
return jwtTokenProvider.createAccessToken(user);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
import com.bang_ggood.station.domain.ChecklistStation;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;

public interface ChecklistStationRepository extends JpaRepository<ChecklistStation, Long> {

@Query("SELECT cs FROM ChecklistStation cs " +
"where cs.checklist = :checklist " +
"and cs.deleted = false")
List<ChecklistStation> findByChecklist(Checklist checklist);
List<ChecklistStation> findByChecklist(@Param("checklist") Checklist checklist);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저희 id로 빼기로 했던 것 같아요!
일괄적으로 수정하는 일정이 있으면 따로 수정하지는 않아도 될 것 같지만 수정한 파트이길래 리뷰 남깁니다 👀

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테스트가 깨져서 추가한 부분이여서 제가 바꾸는 것보다 나중에 함께 수정하면 좋을 것 같아요~

}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.bang_ggood.auth.controller;

import com.bang_ggood.AcceptanceTest;
import com.bang_ggood.auth.dto.response.RefreshTokenCheckResponse;
import com.bang_ggood.auth.service.AuthService;
import com.bang_ggood.global.exception.ExceptionCode;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import io.restassured.http.Header;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -56,4 +58,33 @@ void authentication_invalid_cookie_exception() {
.statusCode(401)
.body("message", containsString(ExceptionCode.AUTHENTICATION_TOKEN_EMPTY.getMessage()));
}

@DisplayName("리프레시 토큰 체크 성공 : 쿠키가 존재하지 않는 경우")
@Test
void checkRefreshToken_returnFalse() {
RefreshTokenCheckResponse refreshTokenCheckResponse = RestAssured.given().log().all()
.contentType(ContentType.JSON)
.when().get("/refreshToken-check")
.then().log().all()
.statusCode(200)
.extract()
.as(RefreshTokenCheckResponse.class);

Assertions.assertThat(refreshTokenCheckResponse.isRefreshTokenExist()).isFalse();
}

@DisplayName("리프레시 토큰 체크 성공 : 리프레시 토큰이 존재하는 경우")
@Test
void checkRefreshToken_returnTrue() {
RefreshTokenCheckResponse refreshTokenCheckResponse = RestAssured.given().log().all()
.contentType(ContentType.JSON)
.headers(this.headers)
.when().get("/refreshToken-check")
.then().log().all()
.statusCode(200)
.extract()
.as(RefreshTokenCheckResponse.class);

Assertions.assertThat(refreshTokenCheckResponse.isRefreshTokenExist()).isTrue();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.bang_ggood.auth.controller.cookie;

import com.bang_ggood.auth.controller.cookie.CookieProvider;
import com.bang_ggood.auth.controller.cookie.CookieResolver;
import com.bang_ggood.global.exception.BangggoodException;
import com.bang_ggood.global.exception.ExceptionCode;
import jakarta.servlet.http.Cookie;
Expand All @@ -19,12 +17,14 @@ class CookieResolverTest {
@Test
void extractAccessToken() {
// given
HttpServletRequest request = mock(HttpServletRequest.class);
String expectedToken = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiI2IiwiaWF0Ijox";
CookieResolver cookieResolver = new CookieResolver();
Cookie[] cookies = { new Cookie(CookieProvider.ACCESS_TOKEN_COOKIE_NAME, expectedToken) };

// when
String token = cookieResolver.extractAccessToken(cookies);
when(request.getCookies()).thenReturn(cookies);
String token = cookieResolver.extractAccessToken(request);

// then
Assertions.assertThat(token).isEqualTo(expectedToken);
Expand All @@ -34,12 +34,14 @@ void extractAccessToken() {
@Test
void extractRefreshToken() {
// given
HttpServletRequest request = mock(HttpServletRequest.class);
String expectedToken = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiI2IiwiaWF0Ijox";
CookieResolver cookieResolver = new CookieResolver();
Cookie[] cookies = { new Cookie(CookieProvider.REFRESH_TOKEN_COOKIE_NAME, expectedToken) };

// when
String token = cookieResolver.extractRefreshToken(cookies);
when(request.getCookies()).thenReturn(cookies);
String token = cookieResolver.extractRefreshToken(request);

// then
Assertions.assertThat(token).isEqualTo(expectedToken);
Expand All @@ -49,12 +51,14 @@ void extractRefreshToken() {
@Test
void tokenValueNotExist() {
// given
HttpServletRequest request = mock(HttpServletRequest.class);
CookieResolver cookieResolver = new CookieResolver();
Cookie[] cookies = new Cookie[1];
cookies[0] = new Cookie("testName", "testValue");

// when & then
Assertions.assertThatThrownBy(() -> cookieResolver.extractAccessToken(cookies))
when(request.getCookies()).thenReturn(cookies);
Assertions.assertThatThrownBy(() -> cookieResolver.extractAccessToken(request))
.isInstanceOf(BangggoodException.class)
.hasMessage(ExceptionCode.AUTHENTICATION_ACCESS_TOKEN_EMPTY.getMessage());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class AuthServiceTest extends IntegrationTestSupport {
@Autowired
private JwtTokenProvider jwtTokenProvider;

@DisplayName("로그인 성공 : 존재하지 않는 회원이면 데이터베이스에 새로운 유저를 추가하고 토큰을 반환한다.")
@DisplayName("로그인 성공 : 존재하지 않는 회원이면 데이터베이스에 새로운 유저를 추가하고 토큰 반환")
@Test
void login_signup() {
// given
Expand All @@ -61,7 +61,7 @@ void login_signup() {
assertThat(token.refreshToken()).isNotBlank();
}

@DisplayName("로그인 성공 : 존재하는 회원이면 데이터베이스에 새로운 유저를 추가하지않고 토큰을 바로 반환한다.")
@DisplayName("로그인 성공 : 존재하는 회원이면 데이터베이스에 새로운 유저를 추가하지않고 토큰 반환")
@Test
void login() {
// given
Expand All @@ -77,7 +77,7 @@ void login() {
assertThat(token.refreshToken()).isNotBlank();
}

@DisplayName("로그인 성공 : 회원 가입시 디폴트 체크리스트 질문을 추가한다.")
@DisplayName("로그인 성공 : 회원 가입시 디폴트 체크리스트 질문 추가")
@Test
void login_default_checklist_question() {
// given
Expand All @@ -100,7 +100,7 @@ void login_default_checklist_question() {
assertThat(sum).isEqualTo(Question.findDefaultQuestions().size());
}

@DisplayName("로그인 성공 : 회원 가입시 디폴트 체크리스트를 추가한다.")
@DisplayName("로그인 성공 : 회원 가입시 디폴트 체크리스트 추가")
@Test
void login_default_checklist() {
// given
Expand All @@ -116,7 +116,7 @@ void login_default_checklist() {
assertThat(response.checklists()).hasSize(1);
}

@DisplayName("게스트 유저 할당 실패 : 게스트 유저의 수가 2명이면 예외를 발생시킨다.")
@DisplayName("게스트 유저 할당 실패 : 게스트 유저의 수가 2명이면 예외 발생")
@Test
void assignGuestUser_UnexpectedGuestUserExist() {
// given
Expand All @@ -129,7 +129,7 @@ void assignGuestUser_UnexpectedGuestUserExist() {
.hasMessage(ExceptionCode.GUEST_USER_UNEXPECTED_EXIST.getMessage());
}

@DisplayName("게스트 유저 할당 실패 : 게스트 유저가 존재하지 않으면 예외를 발생시킨다.")
@DisplayName("게스트 유저 할당 실패 : 게스트 유저가 존재하지 않으면 예외 발생")
@Test
void assingGuestUser_GuestUserNotExist() {
// when & then
Expand Down Expand Up @@ -169,15 +169,15 @@ void logout_invalid_ownership_exception() {

@DisplayName("액세스 토큰 재발행 성공")
@Test
void reIssueAccessToken() {
void reissueAccessToken() {
// given
userRepository.save(UserFixture.USER1());
Mockito.when(oauthClient.requestOauthInfo(any(OauthLoginRequest.class)))
.thenReturn(UserFixture.OAUTH_INFO_RESPONSE_USER1());
AuthTokenResponse tokenResponse = authService.login(OAUTH_LOGIN_REQUEST);

// when & then
assertThatCode(() -> authService.reIssueAccessToken(tokenResponse.refreshToken()))
assertThatCode(() -> authService.reissueAccessToken(tokenResponse.refreshToken()))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 pr에서 AuthServiceTest에 문장형으로 작성된 DisplayName 수정해도 편할 것 같아요~

.doesNotThrowAnyException();

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ void createChecklist() {
void createChecklistV1() {
RestAssured.given().log().all()
.contentType(ContentType.JSON)
.header(new Header(HttpHeaders.COOKIE, this.responseCookie.toString()))
.headers(this.headers)
.body(ChecklistFixture.CHECKLIST_CREATE_REQUEST_V1())
.when().post("v1/checklists")
.then().log().all()
Expand Down Expand Up @@ -130,7 +130,7 @@ void readChecklistV1() {

RestAssured.given().log().all()
.contentType(ContentType.JSON)
.header(new Header(HttpHeaders.COOKIE, this.responseCookie.toString()))
.headers(this.headers)
.when().get("v1/checklists/" + checklistId)
.then().log().all()
.statusCode(200);
Expand Down
Loading