Skip to content
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 @@ -12,17 +12,19 @@
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

import static com.jobnote.global.common.Constants.*;
import static com.jobnote.global.common.ResponseCode.INVALID_HEADER;
import static com.jobnote.global.common.ResponseCode.INVALID_TOKEN;
import static com.jobnote.global.util.CookieUtil.getTokenFromCookie;

@Slf4j
@RequiredArgsConstructor
Expand All @@ -47,7 +49,8 @@ protected void doFilterInternal(final HttpServletRequest request, final HttpServ
}

private void authenticate(final HttpServletRequest request) {
final String email = tokenProvider.getEmailFromPayload(getTokenFromCookie(request.getCookies(), COOKIE_NAME_ACCESS_TOKEN))
final String accessToken = validateAuthorizationHeader(request);
final String email = tokenProvider.getEmailFromPayload(accessToken)
.orElseThrow(() -> new JobNoteException(INVALID_TOKEN));

setAuthentication(email);
Expand All @@ -59,6 +62,15 @@ private void setAuthentication(final String email) {
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}

private String validateAuthorizationHeader(final HttpServletRequest request) {
final String header = request.getHeader(HttpHeaders.AUTHORIZATION);
if (!StringUtils.hasText(header) || !header.startsWith(BEARER)) {
throw new JobNoteException(INVALID_HEADER);
}

return header.substring(BEARER.length());
}

@Override
protected boolean shouldNotFilter(final HttpServletRequest request) {
return TOKEN_FILTER_WHITELIST.contains(request.getRequestURI());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo
}

final Token token = authTokenService.saveAndGetToken(principal.getUserId());
tokenProvider.addTokenToCookie(response, token);
tokenProvider.responseToken(response, token);

response.sendRedirect(memberRedirectUrl());
}
Expand Down
12 changes: 4 additions & 8 deletions src/main/java/com/jobnote/auth/token/TokenProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

import static com.jobnote.global.common.Constants.*;
import static com.jobnote.global.common.ResponseCode.EXPIRED_TOKEN;
import static com.jobnote.global.util.CookieUtil.createResponseCookie;

@RequiredArgsConstructor
@Component
Expand Down Expand Up @@ -54,16 +53,13 @@ public void validateExpired(final String token, final String tokenType) {
}
}

public void addTokenToCookie(final HttpServletResponse response, final Token token) {
ResponseCookie accessTokenCookie = createResponseCookie(COOKIE_NAME_ACCESS_TOKEN, token.accessToken(), COOKIE_PATH_ACCESS_TOKEN, Duration.ofMillis(jwtProvider.getJwtProperties().accessToken().expirationTime()));
ResponseCookie refreshTokenCookie = createResponseCookie(COOKIE_NAME_REFRESH_TOKEN, token.refreshToken(), COOKIE_PATH_REFRESH_TOKEN, Duration.ofMillis(jwtProvider.getJwtProperties().refreshToken().expirationTime()));

response.addHeader(HttpHeaders.SET_COOKIE, accessTokenCookie.toString());
public void responseToken(final HttpServletResponse response, final Token token) {
final ResponseCookie refreshTokenCookie = CookieUtil.createResponseCookie(COOKIE_NAME_REFRESH_TOKEN, token.refreshToken(), COOKIE_PATH_REFRESH_TOKEN, Duration.ofMillis(jwtProvider.getJwtProperties().refreshToken().expirationTime()));
response.setHeader(HttpHeaders.AUTHORIZATION, token.accessToken());
response.addHeader(HttpHeaders.SET_COOKIE, refreshTokenCookie.toString());
}

public void addInvalidateCookie(final HttpServletResponse response) {
response.addHeader(HttpHeaders.SET_COOKIE, CookieUtil.invalidateCookie(COOKIE_NAME_ACCESS_TOKEN, COOKIE_PATH_ACCESS_TOKEN).toString());
public void responseInvalidatedToken(final HttpServletResponse response) {
response.addHeader(HttpHeaders.SET_COOKIE, CookieUtil.invalidateCookie(COOKIE_NAME_REFRESH_TOKEN, COOKIE_PATH_REFRESH_TOKEN).toString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import org.springframework.web.bind.annotation.*;

import static com.jobnote.global.common.Constants.COOKIE_NAME_REFRESH_TOKEN;
import static com.jobnote.global.util.CookieUtil.getTokenFromCookie;
import static com.jobnote.global.util.CookieUtil.getValueFromCookie;

@RequiredArgsConstructor
@RequestMapping("/api/v1/users")
Expand All @@ -40,31 +40,31 @@ public ResponseEntity<ApiResponse<Void>> signUp(@RequestBody @Valid final UserSi
@PostMapping("/signup/social")
public ResponseEntity<ApiResponse<Void>> socialSignUp(@RequestBody @Valid final SocialSignUpRequest request, final HttpServletResponse response) {
final Token token = userService.socialSignUp(request);
tokenProvider.addTokenToCookie(response, token);
tokenProvider.responseToken(response, token);
return ResponseEntity.ok(ApiResponse.ofSuccess(ResponseCode.OK));
}

/* LOGIN */
@PostMapping("/login")
public ResponseEntity<ApiResponse<Void>> login(@RequestBody @Valid final UserLoginRequest request, final HttpServletResponse response) {
final Token token = loginService.login(request);
tokenProvider.addTokenToCookie(response, token);
tokenProvider.responseToken(response, token);
return ResponseEntity.ok(ApiResponse.ofSuccess(ResponseCode.OK));
}

/* LOGOUT */
@PostMapping("/logout")
public ResponseEntity<ApiResponse<Void>> logout(final HttpServletRequest request, final HttpServletResponse response) {
authTokenService.invalidate(getTokenFromCookie(request.getCookies(), COOKIE_NAME_REFRESH_TOKEN));
tokenProvider.addInvalidateCookie(response);
authTokenService.invalidate(getValueFromCookie(request.getCookies(), COOKIE_NAME_REFRESH_TOKEN));
tokenProvider.responseInvalidatedToken(response);
return ResponseEntity.ok(ApiResponse.ofSuccess(ResponseCode.OK));
}

/* TOKEN REISSUE */
@PostMapping("/reissue")
public ResponseEntity<ApiResponse<Void>> tokenReissue(@LoginUser CustomUserDetails principal, final HttpServletRequest request, final HttpServletResponse response) {
final Token token = authTokenService.reissue(principal.getUserId(), getTokenFromCookie(request.getCookies(), COOKIE_NAME_REFRESH_TOKEN));
tokenProvider.addTokenToCookie(response, token);
final Token token = authTokenService.reissue(principal.getUserId(), getValueFromCookie(request.getCookies(), COOKIE_NAME_REFRESH_TOKEN));
tokenProvider.responseToken(response, token);
return ResponseEntity.ok(ApiResponse.ofSuccess(ResponseCode.OK));
}

Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/jobnote/global/common/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

public abstract class Constants {

public static final String BEARER = "Bearer ";

// claim
public static final String CLAIM_NAME_TOKEN_TYPE = "token";
public static final String CLAIM_NAME_EMAIL = "email";
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/jobnote/global/common/ResponseCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public enum ResponseCode {
NOT_SUPPORTED_SOCIAL_PROVIDER(HttpStatus.BAD_REQUEST, "4003", "해당 소셜 로그인은 지원되지 않습니다."),
INVALID_TOKEN_TYPE(HttpStatus.BAD_REQUEST, "4004", "올바르지 않은 토큰 타입입니다."),
VERIFICATION_EMAIL_ALREADY_VERIFIED(HttpStatus.BAD_REQUEST, "4005", "이미 인증이 완료된 인증 이메일입니다."),
INVALID_HEADER(HttpStatus.BAD_REQUEST, "4006", "헤더가 올바르지 않습니다."),
INVALID_COOKIE(HttpStatus.BAD_REQUEST, "4007", "쿠키가 올바르지 않습니다."),

// 401 Unauthorized
UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "4010", "요청 리소스에 대한 액세스 권한이 없습니다."),
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/com/jobnote/global/util/CookieUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@
import java.time.Duration;
import java.util.Arrays;

import static com.jobnote.global.common.ResponseCode.INVALID_TOKEN;
import static com.jobnote.global.common.ResponseCode.INVALID_COOKIE;

public class CookieUtil {

public static String getTokenFromCookie(final Cookie[] cookies, final String name) {
public static String getValueFromCookie(final Cookie[] cookies, final String name) {
if (cookies == null) {
throw new JobNoteException(INVALID_TOKEN);
throw new JobNoteException(INVALID_COOKIE);
}

return Arrays.stream(cookies)
.filter(cookie -> name.equals(cookie.getName()))
.findFirst()
.orElseThrow(() -> new JobNoteException(INVALID_TOKEN))
.orElseThrow(() -> new JobNoteException(INVALID_COOKIE))
.getValue();
}

Expand Down
10 changes: 5 additions & 5 deletions src/test/java/com/jobnote/auth/LoginApplicationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
import com.jobnote.domain.user.repository.UserRepository;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.test.web.servlet.ResultActions;

import static com.jobnote.global.common.Constants.COOKIE_NAME_ACCESS_TOKEN;
import static com.jobnote.global.common.Constants.COOKIE_NAME_REFRESH_TOKEN;
import static com.jobnote.global.common.ResponseCode.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
Expand Down Expand Up @@ -51,7 +51,7 @@ void success_GUEST_NotIssueToken_PENDING_EMAIL_VERIFICATION() throws Exception {
// then
resultActions
.andExpect(status().isForbidden())
.andExpect(cookie().doesNotExist(COOKIE_NAME_ACCESS_TOKEN))
.andExpect(header().doesNotExist(HttpHeaders.AUTHORIZATION))
.andExpect(cookie().doesNotExist(COOKIE_NAME_REFRESH_TOKEN))
.andExpect(jsonPath("$.data").isEmpty())
.andExpect(jsonPath("$.code").value(PENDING_EMAIL_VERIFICATION.getCode()));
Expand All @@ -72,7 +72,7 @@ void success_NotGUEST_IssueToken() throws Exception {
// then
resultActions
.andExpect(status().isOk())
.andExpect(cookie().exists(COOKIE_NAME_ACCESS_TOKEN))
.andExpect(header().exists(HttpHeaders.AUTHORIZATION))
.andExpect(cookie().exists(COOKIE_NAME_REFRESH_TOKEN));
}
}
Expand Down Expand Up @@ -100,7 +100,7 @@ void fail_NotIssueToken_InvalidEmail() throws Exception {
// then
resultActions
.andExpect(status().isUnauthorized())
.andExpect(cookie().doesNotExist(COOKIE_NAME_ACCESS_TOKEN))
.andExpect(header().doesNotExist(HttpHeaders.AUTHORIZATION))
.andExpect(cookie().doesNotExist(COOKIE_NAME_REFRESH_TOKEN))
.andExpect(jsonPath("$.data").isEmpty())
.andExpect(jsonPath("$.code").value(INVALID_USERNAME_PASSWORD.getCode()));
Expand All @@ -122,7 +122,7 @@ void fail_NotIssueToken_InvalidPassword() throws Exception {
// then
resultActions
.andExpect(status().isUnauthorized())
.andExpect(cookie().doesNotExist(COOKIE_NAME_ACCESS_TOKEN))
.andExpect(header().doesNotExist(HttpHeaders.AUTHORIZATION))
.andExpect(cookie().doesNotExist(COOKIE_NAME_REFRESH_TOKEN))
.andExpect(jsonPath("$.data").isEmpty())
.andExpect(jsonPath("$.code").value(INVALID_USERNAME_PASSWORD.getCode()));
Expand Down
8 changes: 4 additions & 4 deletions src/test/java/com/jobnote/global/util/CookieUtilTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import static com.jobnote.global.common.Constants.COOKIE_NAME_ACCESS_TOKEN;
import static com.jobnote.global.common.Constants.COOKIE_PATH_ACCESS_TOKEN;
import static com.jobnote.global.common.ResponseCode.INVALID_TOKEN;
import static com.jobnote.global.common.ResponseCode.INVALID_COOKIE;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

Expand All @@ -30,7 +30,7 @@ void success() {
Cookie[] cookies = new Cookie[]{new Cookie(COOKIE_NAME_ACCESS_TOKEN, accessToken)};

// when
String result = CookieUtil.getTokenFromCookie(cookies, COOKIE_NAME_ACCESS_TOKEN);
String result = CookieUtil.getValueFromCookie(cookies, COOKIE_NAME_ACCESS_TOKEN);

// then
assertThat(result).isEqualTo(accessToken);
Expand All @@ -43,9 +43,9 @@ void fail_INVALID_TOKEN() {
Cookie[] cookies = new Cookie[]{};

// when & then
assertThatThrownBy(() -> CookieUtil.getTokenFromCookie(cookies, COOKIE_NAME_ACCESS_TOKEN))
assertThatThrownBy(() -> CookieUtil.getValueFromCookie(cookies, COOKIE_NAME_ACCESS_TOKEN))
.isInstanceOf(JobNoteException.class)
.hasMessage(INVALID_TOKEN.getMessage());
.hasMessage(INVALID_COOKIE.getMessage());
}
}

Expand Down
Loading