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 12 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,7 +6,7 @@
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.auth.exception.RefreshTokenNotFoundException;
import org.donggle.backend.domain.member.Member;
import org.donggle.backend.domain.member.MemberName;
import org.donggle.backend.ui.response.TokenResponse;
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(RefreshTokenNotFoundException::new);
Copy link
Collaborator

Choose a reason for hiding this comment

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

이 부분은 MemberNotFoundException이 맞지 않을까 생각합니다. memberId로 memberRepository에서 Member를 찾아오는 걸로 보이네요!

Copy link
Member Author

Choose a reason for hiding this comment

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

어라.. 그렇네요?! 에코 말 듣고보니까 전형적인 MemberNotFoundException인데 시야가 좁아졌었나봅니다 ㅋㅋㅋ 땡큐 에코오

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.ExpiredTokenException;
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 ExpiredTokenException();
} 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 ExpiredTokenException extends AuthenticationException {
private static final String MESSAGE = "유효하지 않은 토큰입니다.";

public ExpiredTokenException() {
super(MESSAGE);
}

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

@Override
public String getHint() {
return "AccessToken이 만료되었습니다. 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.ExpiredTokenException;
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 ExpiredTokenException();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
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.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 +24,11 @@ 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) || jwtTokenProvider.inValidTokenUsage(refreshToken)) {
throw new InvalidRefreshTokenException();
}

return true;
Expand All @@ -36,7 +38,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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ public BusinessException(final String message) {
public BusinessException(final String message, final Throwable cause) {
super(message, cause);
}

public abstract String getHint();

public abstract int getErrorCode();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
public abstract int getErrorCode();
public final int getErrorCode() {
return HttpStatus.BAD_REQUEST.value();
}

는 어떨까요 ?!

Copy link
Member Author

Choose a reason for hiding this comment

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

BusinessException은 예외로 해당 메서드를 abstract로 두어 하위에서 재정의하도록 하였는데요,
다른 예외들과는 달리 추후에 우리가 비즈니스적으로 예외 코드를 프론트와 상의해서 줘야할 상황이 생길 것 같아서 이처럼 구현했습니다.
괜찮은 생각일까요?

Copy link
Collaborator

Choose a reason for hiding this comment

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

BusinessException을 상속받는 클래스들을 살펴보았는데 다 BadRequst를 ErrorCode로 가지고 있기에 통일해줘도 괜찮지않을까 생각했습니다 ! 헙크의 의견도 동의합니다 좋은데요 !?

Copy link
Member Author

Choose a reason for hiding this comment

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

넵! 언젠가 커스텀 예외 코드를 사용할 일이 생기길!!

}
Loading