Skip to content
This repository has been archived by the owner on Jul 29, 2024. It is now read-only.

[BE] refactor: 예외 메시지 포맷 수정 #263

Merged
merged 14 commits into from
Aug 15, 2023
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 @@ -6,9 +6,9 @@
import org.donggle.backend.application.service.oauth.kakao.dto.KakaoProfileResponse;
import org.donggle.backend.auth.JwtTokenProvider;
import org.donggle.backend.auth.JwtTokenService;
import org.donggle.backend.auth.exception.NoSuchTokenException;
import org.donggle.backend.domain.member.Member;
import org.donggle.backend.domain.member.MemberName;
import org.donggle.backend.exception.notfound.MemberNotFoundException;
import org.donggle.backend.ui.response.TokenResponse;
import org.springframework.stereotype.Service;

Expand All @@ -19,7 +19,7 @@ public class AuthService {
private final MemberRepository memberRepository;
private final JwtTokenProvider jwtTokenProvider;
private final JwtTokenService jwtTokenService;

public TokenResponse loginByKakao(final KakaoProfileResponse kakaoProfileResponse) {
final Member loginMember = memberRepository.findByKakaoId(kakaoProfileResponse.id())
.orElseGet(() -> memberRepository.save(Member.createByKakao(
Expand All @@ -28,20 +28,20 @@ public TokenResponse loginByKakao(final KakaoProfileResponse kakaoProfileRespons
));
return createTokens(loginMember);
}

public TokenResponse reissueAccessTokenAndRefreshToken(final Long memberId) {
final Member member = memberRepository.findById(memberId).
orElseThrow(NoSuchTokenException::new);

orElseThrow(() -> new MemberNotFoundException(memberId));
return createTokens(member);
}

private TokenResponse createTokens(final Member loginMember) {
final String accessToken = jwtTokenProvider.createAccessToken(loginMember.getId());
final String refreshToken = jwtTokenProvider.createRefreshToken(loginMember.getId());

jwtTokenService.synchronizeRefreshToken(loginMember, refreshToken);

return new TokenResponse(accessToken, refreshToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ private void addCategoryOrder(final Long nextCategoryId, final Category category

private void validateBasicCategory(final Category category) {
if (category.isBasic()) {
throw new InvalidBasicCategoryException();
throw new InvalidBasicCategoryException(category.getId());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public Long uploadMarkDownFile(final Long memberId, final MarkdownUploadRequest
//TODO: member checking
final String originalFilename = request.file().getOriginalFilename();
if (!Objects.requireNonNull(originalFilename).endsWith(MD_FORMAT)) {
throw new InvalidFileFormatException();
throw new InvalidFileFormatException(originalFilename);
}
final String originalFileText = new String(request.file().getBytes(), StandardCharsets.UTF_8);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.donggle.backend.auth.exception.NoSuchTokenException;
import org.donggle.backend.auth.exception.ExpiredAccessTokenException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

Expand Down Expand Up @@ -52,21 +52,26 @@ private String createToken(final Long payload, final long validityInMilliseconds
}

public Long getPayload(final String token) {
return getClaims(token).getBody().get(MEMBER_ID_KEY, Long.class);
return getClaims(token)
.getBody()
.get(MEMBER_ID_KEY, Long.class);
}

public boolean inValidTokenUsage(final String token) {
try {
final Jws<Claims> claims = getClaims(token);
return claims.getBody().getExpiration().before(new Date());
} catch (final ExpiredJwtException e) {
throw new NoSuchTokenException();
throw new ExpiredAccessTokenException();
} catch (final JwtException | IllegalArgumentException e) {
return true;
}
}

private Jws<Claims> getClaims(final String token) {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.donggle.backend.auth.exception;

import org.donggle.backend.exception.authentication.AuthenticationException;

public class AuthorizationHeaderNotFoundException extends AuthenticationException {
public AuthorizationHeaderNotFoundException() {
super(null);
}

public AuthorizationHeaderNotFoundException(final Throwable cause) {
super(null, cause);
}

@Override
public String getHint() {
return "Authorization 해더값이 존재하지 않습니다.";
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.donggle.backend.auth.exception;

import org.donggle.backend.exception.authentication.AuthenticationException;

public class ExpiredAccessTokenException extends AuthenticationException {
private static final String MESSAGE = "유효하지 않은 토큰입니다.";

public ExpiredAccessTokenException() {
super(MESSAGE);
}

public ExpiredAccessTokenException(final Throwable cause) {
super(MESSAGE, cause);
}

@Override
public String getHint() {
return "AccessToken이 만료되었습니다. RefreshToken값을 요청하세요.";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.donggle.backend.auth.exception;

import org.donggle.backend.exception.authentication.AuthenticationException;

public class ExpiredRefreshTokenException extends AuthenticationException {
private static final String MESSAGE = "유효하지 않은 토큰입니다.";

public ExpiredRefreshTokenException() {
super(MESSAGE);
}

public ExpiredRefreshTokenException(final Throwable cause) {
super(MESSAGE, cause);
}

@Override
public String getHint() {
return "RefreshToken이 만료되었습니다. 다시 로그인을 진행하세요.";
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.donggle.backend.auth.exception;

import org.donggle.backend.exception.authentication.AuthenticationException;

public class InvalidAuthorizationHeaderTypeException extends AuthenticationException {
private final String authorizationHeader;

public InvalidAuthorizationHeaderTypeException(final String authorizationHeader) {
super(null);
this.authorizationHeader = authorizationHeader;
}

public InvalidAuthorizationHeaderTypeException(final String authorizationHeader, final Throwable cause) {
super(null, cause);
this.authorizationHeader = authorizationHeader;
}

@Override
public String getHint() {
return "Authorization 헤더의 타입이 올바르지 않습니다. 입력한 헤더: " + authorizationHeader;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.donggle.backend.auth.exception;

import org.donggle.backend.exception.authentication.AuthenticationException;

public class InvalidRefreshTokenException extends AuthenticationException {
Copy link
Collaborator

Choose a reason for hiding this comment

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

유효하지 않은 RefreshToken 이라는게 만료되었다는 건가요 !?

Copy link
Member Author

Choose a reason for hiding this comment

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

현재 두 가지 상황에서 사용되고 있네요! Member로부터 찾은 RefreshToken이 다르거나, RefreshToken 자체의 만료 기간이 지났을 때에 InvalidRefreshTokenException을 뱉고 있어요.

두 가지 상황을 분리하여 예외를 던지는 것이 좋을까요?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Member로부터 찾은 RefreshToken이 다를 때가 만료기간이 지났거나, 새로운 토큰이 발급되었을 때 발생하는 상황이 맞을까요 ?!

그렇다면 ExpiredTokenException과 같이 ExpiredRefreshTokenException을 던져줘도 괜찮지 않을까 생각했습니다 !

Copy link
Member Author

Choose a reason for hiding this comment

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

Member로부터 찾은 RefreshToken이 다를 때가 만료기간이 지났거나, 새로운 토큰이 발급되었을 때 발생하는 상황이 맞을까요 ?!

맞아요! ExpiredRefreshTokenException을 던지도록 상황을 구분하여 수정해봤습니다!

public InvalidRefreshTokenException() {
super(null);
}

public InvalidRefreshTokenException(final Throwable cause) {
super(null, cause);
}

@Override
public String getHint() {
return "유효하지 않은 RefreshToken입니다. 다시 로그인을 진행하세요.";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.donggle.backend.auth.exception;

import org.donggle.backend.exception.authentication.AuthenticationException;

public class NoRefreshTokenInCookieException extends AuthenticationException {
public NoRefreshTokenInCookieException() {
super(null);
}

public NoRefreshTokenInCookieException(final Throwable cause) {
super(null, cause);
}

@Override
public String getHint() {
return "쿠키에 RefreshToken이 존재하지 않습니다.";
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.donggle.backend.auth.exception;

import org.donggle.backend.exception.authentication.AuthenticationException;

public class RefreshTokenNotFoundException extends AuthenticationException {
public RefreshTokenNotFoundException() {
super(null);
}

public RefreshTokenNotFoundException(final Throwable cause) {
super(null, cause);
}

@Override
public String getHint() {
return "존재하지 않는 토큰입니다. 다시 로그인을 진행하세요.";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.donggle.backend.auth.JwtTokenProvider;
import org.donggle.backend.auth.exception.InvalidAccessTokenException;
import org.donggle.backend.auth.exception.ExpiredAccessTokenException;
import org.donggle.backend.auth.support.AuthorizationExtractor;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.servlet.HandlerInterceptor;
Expand All @@ -26,7 +26,7 @@ public boolean preHandle(final HttpServletRequest request, final HttpServletResp
private void validateToken(final HttpServletRequest request) {
final String token = AuthorizationExtractor.extract(request);
if (jwtTokenProvider.inValidTokenUsage(token)) {
throw new InvalidAccessTokenException();
throw new ExpiredAccessTokenException();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
import org.donggle.backend.application.repository.TokenRepository;
import org.donggle.backend.auth.JwtToken;
import org.donggle.backend.auth.JwtTokenProvider;
import org.donggle.backend.auth.exception.NoSuchTokenException;
import org.donggle.backend.auth.exception.ExpiredRefreshTokenException;
import org.donggle.backend.auth.exception.InvalidRefreshTokenException;
import org.donggle.backend.auth.exception.NoRefreshTokenInCookieException;
import org.donggle.backend.auth.exception.RefreshTokenNotFoundException;
import org.springframework.web.servlet.HandlerInterceptor;

import java.util.Arrays;
Expand All @@ -22,11 +25,14 @@ public boolean preHandle(final HttpServletRequest request,
final Object handler) {
final String refreshToken = extract(request);
final Long memberId = jwtTokenProvider.getPayload(refreshToken);
final JwtToken jwtToken = tokenRepository.findByMemberId(memberId)
.orElseThrow(NoSuchTokenException::new);
final JwtToken findRefreshToken = tokenRepository.findByMemberId(memberId)
.orElseThrow(RefreshTokenNotFoundException::new);

if (jwtToken.isDifferentRefreshToken(refreshToken) || jwtTokenProvider.inValidTokenUsage(refreshToken)) {
throw new NoSuchTokenException();
if (findRefreshToken.isDifferentRefreshToken(refreshToken)) {
throw new InvalidRefreshTokenException();
}
if (jwtTokenProvider.inValidTokenUsage(refreshToken)) {
throw new ExpiredRefreshTokenException();
}

return true;
Expand All @@ -36,7 +42,7 @@ private String extract(final HttpServletRequest request) {
return Arrays.stream(request.getCookies())
.filter(cookie -> "refreshToken".equals(cookie.getName()))
.findFirst()
.orElseThrow(NoSuchTokenException::new)
.orElseThrow(NoRefreshTokenInCookieException::new)
.getValue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import jakarta.servlet.http.HttpServletRequest;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.donggle.backend.auth.exception.EmptyAuthorizationHeaderException;
import org.donggle.backend.auth.exception.NoSuchTokenException;
import org.donggle.backend.auth.exception.AuthorizationHeaderNotFoundException;
import org.donggle.backend.auth.exception.InvalidAuthorizationHeaderTypeException;
import org.springframework.http.HttpHeaders;

import java.util.Objects;
Expand All @@ -16,7 +16,7 @@ public class AuthorizationExtractor {
public static String extract(final HttpServletRequest request) {
final String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
if (Objects.isNull(authorizationHeader)) {
throw new EmptyAuthorizationHeaderException();
throw new AuthorizationHeaderNotFoundException();
}

validateAuthorizationFormat(authorizationHeader);
Expand All @@ -25,7 +25,7 @@ public static String extract(final HttpServletRequest request) {

private static void validateAuthorizationFormat(final String authorizationHeader) {
if (!authorizationHeader.toLowerCase().startsWith(BEARER_TYPE.toLowerCase())) {
throw new NoSuchTokenException();
throw new InvalidAuthorizationHeaderTypeException(authorizationHeader);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
package org.donggle.backend.exception.authentication;

import org.springframework.http.HttpStatus;

public abstract class AuthenticationException extends RuntimeException {
public AuthenticationException(final String message) {
super(message);
}

public AuthenticationException(final String message, final Throwable cause) {
super(message, cause);
}

public abstract String getHint();

public final int getErrorCode() {
return HttpStatus.UNAUTHORIZED.value();
}
}
Loading