Skip to content

Commit 146f16a

Browse files
authored
Merge pull request #167 from DevKor-github/mod/jwt2
[Mod] 토큰 비유효시 반환값 변경 및 토큰 검증 로직 최적화
2 parents 6af29ae + aefe12a commit 146f16a

6 files changed

+116
-36
lines changed

ontime-back/src/main/java/devkor/ontime_back/global/jwt/JwtAuthenticationFilter.java

+58-35
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
import com.fasterxml.jackson.databind.ObjectMapper;
44
import devkor.ontime_back.entity.User;
55
import devkor.ontime_back.repository.UserRepository;
6-
import devkor.ontime_back.response.ApiResponseForm;
7-
import devkor.ontime_back.response.ErrorCode;
8-
import devkor.ontime_back.response.InvalidTokenException;
6+
import devkor.ontime_back.response.*;
97
import jakarta.servlet.DispatcherType;
108
import jakarta.servlet.FilterChain;
119
import jakarta.servlet.http.HttpServletRequest;
@@ -33,7 +31,7 @@
3331
@Slf4j
3432
public class JwtAuthenticationFilter extends OncePerRequestFilter {
3533

36-
private static final List<String> NO_CHECK_URLS = List.of("/login", "/swagger-ui", "/sign-up", "/v3/api-docs"); // "/login"으로 들어오는 요청은 Filter 작동 X
34+
private static final List<String> NO_CHECK_URLS = List.of("/login", "/swagger-ui", "/sign-up", "/v3/api-docs", "/oauth2/google/registerOrLogin", "/oauth2/kakao/registerOrLogin", "/oauth2/apple/registerOrLogin");
3735

3836
private final JwtTokenProvider jwtTokenProvider;
3937
private final UserRepository userRepository;
@@ -55,19 +53,29 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
5553
String refreshToken = jwtTokenProvider.extractRefreshToken(request)
5654
.orElse(null);
5755

58-
if (refreshToken != null) {
59-
checkRefreshTokenAndReIssueAccessToken(response, refreshToken);
56+
// 리프레시 토큰이 있고, 유효할 경우 엑세스토큰 재발급
57+
// 이때 조건절의 isRefreshTokenValid에서 토큰이 유효하지 않으면 InvalidRefreshTokenException 발생
58+
if (refreshToken != null && jwtTokenProvider.isRefreshTokenValid(refreshToken)) {
59+
// 리프레시토큰의 경우 토큰의 유효성 뿐만 아니라 DB에 등록되어 있는지도 확인해야 함
60+
// reIssueAccessToken 메소드에서 DB를 확인해 등록된 리프레시 토큰이면 엑세스 토큰 재발급
61+
// 이때 reIssueAccessToken 메소드에서 DB에 등록된 리프레시 토큰이 아니면 InvalidRefreshTokenException 발생
62+
log.info("리프레시 토큰이 있고 유효한데 DB에 있는지는 아직 모름");
63+
reIssueAccessToken(response, refreshToken);
6064
return;
6165
}
6266

63-
if (accessToken != null && jwtTokenProvider.isTokenValid(accessToken)) {
67+
// 리프레시 토큰이 있을 때의 처리는 위의 if문에서 처리하였음.
68+
// 이제부터는 엑세스 토큰'만' 헤더에 담긴 요청만 생각하면 됨
69+
70+
// 엑세스 토큰이 있고, 유효할 경우 checkAccessTokenAndAuthentication 메서드 호출해 권한정보 저장하고 스프링 시큐리티 필터체인 계속 진행
71+
if (accessToken != null && jwtTokenProvider.isAccessTokenValid(accessToken)) {
6472
checkAccessTokenAndAuthentication(request, response, filterChain);
65-
return;
6673
}
6774

68-
if (accessToken != null && !jwtTokenProvider.isTokenValid(accessToken)) {
69-
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid Access token.");
70-
return;
75+
// 엑세스 토큰이 없는 경우 EmptyAccessTokenException 발생
76+
// 엑세스 토큰 없고 리프레시 토큰 있는 경우는 첫번째 if문에서 처리하여서 고려하지 않아도 됨.
77+
if (accessToken == null) {
78+
throw new EmptyAccessTokenException("Empty Access token!~!");
7179
}
7280

7381

@@ -78,27 +86,21 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
7886

7987
}
8088

81-
// refreshToken로 검색 후 accessToken 재발급 후 전송
82-
public void checkRefreshTokenAndReIssueAccessToken(HttpServletResponse response, String refreshToken) throws IOException {
83-
if (!jwtTokenProvider.isTokenValid(refreshToken)) {
84-
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid Refresh token.");
85-
}
86-
87-
userRepository.findByRefreshToken(refreshToken) // refreshToken으로 유저 찾기
88-
.ifPresent(user -> {
89-
String newAccessToken = jwtTokenProvider.createAccessToken(user.getEmail(), user.getId()); // accessToken 생성
90-
log.info("New accessToken issued: " + newAccessToken); // 재발급된 accessToken 출력
91-
jwtTokenProvider.sendAccessToken(response, newAccessToken); // accessToken 전송
92-
// jwtTokenProvider.sendAccessToken(response, jwtTokenProvider.createAccessToken(user.getEmail(), user.getId())); // accessToken 생성 후 전송
93-
});
89+
// 리프레시 토큰이 DB에 있으면 엑세스 토큰을 재발급
90+
// DB에 없으면 InvalidRefreshTokenException 발생
91+
public void reIssueAccessToken(HttpServletResponse response, String refreshToken) throws IOException {
92+
log.info("리프레시토큰이 유효하나 DB에 있는지는 모름. DB에서 찾아봐서 없으면 예외 발생할 것임.");
93+
User user = userRepository.findByRefreshToken(refreshToken)
94+
.orElseThrow(() -> new InvalidRefreshTokenException("Invalid Refresh token!~!"));
95+
log.info("리프레시토큰이 DB에도 있음");
96+
jwtTokenProvider.sendAccessToken(response, jwtTokenProvider.createAccessToken(user.getEmail(), user.getId()));
9497
}
9598

96-
// accessToken 확인 후 인증 확인
99+
// accessToken으로 유저의 권한정보만 저장하고 인증 허가(스프링 시큐리티 필터체인 中 인증체인 통과해 다음 체인으로 이동)
97100
public void checkAccessTokenAndAuthentication(HttpServletRequest request, HttpServletResponse response,
98101
FilterChain filterChain) throws ServletException, IOException {
99102
log.info("checkAccessTokenAndAuthentication() 호출");
100103
jwtTokenProvider.extractAccessToken(request)
101-
.filter(jwtTokenProvider::isTokenValid)
102104
.ifPresent(accessToken -> {
103105
jwtTokenProvider.extractEmail(accessToken)
104106
.ifPresent(email -> userRepository.findByEmail(email)
@@ -150,17 +152,38 @@ private void handleInvalidTokenException(HttpServletResponse response, InvalidTo
150152
response.setContentType("application/json;charset=UTF-8");
151153
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
152154

155+
log.info("InvalidTokenException 발생");
156+
153157
// ErrorCode에서 정보를 가져옴
154158
ErrorCode errorCode = ErrorCode.UNAUTHORIZED;
155159

156-
// ErrorCode를 사용하여 ApiResponseForm 생성
157-
ApiResponseForm<Void> errorResponse = ApiResponseForm.error(
158-
errorCode.getCode(),
159-
errorCode.getMessage()
160-
);
161-
162-
// ObjectMapper를 사용하여 JSON 변환 후 응답에 기록
163-
ObjectMapper objectMapper = new ObjectMapper();
164-
response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
160+
if (ex instanceof InvalidRefreshTokenException) {
161+
ApiResponseForm<Void> errorResponse = ApiResponseForm.refreshTokenInvalid(
162+
errorCode.getCode(),
163+
errorCode.getMessage()
164+
);
165+
166+
// ObjectMapper를 사용하여 JSON 변환 후 응답에 기록
167+
ObjectMapper objectMapper = new ObjectMapper();
168+
response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
169+
} else if (ex instanceof InvalidAccessTokenException) {
170+
ApiResponseForm<Void> errorResponse = ApiResponseForm.accessTokenInvalid(
171+
errorCode.getCode(),
172+
errorCode.getMessage()
173+
);
174+
175+
// ObjectMapper를 사용하여 JSON 변환 후 응답에 기록
176+
ObjectMapper objectMapper = new ObjectMapper();
177+
response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
178+
} else if (ex instanceof EmptyAccessTokenException) {
179+
ApiResponseForm<Void> errorResponse = ApiResponseForm.accessTokenEmpty(
180+
errorCode.getCode(),
181+
errorCode.getMessage()
182+
);
183+
184+
// ObjectMapper를 사용하여 JSON 변환 후 응답에 기록
185+
ObjectMapper objectMapper = new ObjectMapper();
186+
response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
187+
}
165188
}
166189
}

ontime-back/src/main/java/devkor/ontime_back/global/jwt/JwtTokenProvider.java

+25-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import com.auth0.jwt.algorithms.Algorithm;
55
import devkor.ontime_back.entity.User;
66
import devkor.ontime_back.repository.UserRepository;
7+
import devkor.ontime_back.response.InvalidAccessTokenException;
8+
import devkor.ontime_back.response.InvalidRefreshTokenException;
79
import devkor.ontime_back.response.InvalidTokenException;
810
import jakarta.servlet.http.HttpServletRequest;
911
import jakarta.servlet.http.HttpServletResponse;
@@ -158,7 +160,29 @@ public boolean isTokenValid(String token) {
158160
return true;
159161
} catch (Exception e) {
160162
log.error("유효하지 않은 토큰입니다. {}", e.getMessage());
161-
return false;
163+
throw new InvalidTokenException("유효하지 않은 토큰입니다.");
164+
}
165+
}
166+
167+
public boolean isAccessTokenValid(String token) {
168+
try {
169+
JWT.require(Algorithm.HMAC512(secretKey)).build().verify(token);
170+
log.info("유효한 엑세스 토큰입니다.");
171+
return true;
172+
} catch (Exception e) {
173+
log.error("유효하지 않은 엑세스 토큰입니다. {}", e.getMessage());
174+
throw new InvalidAccessTokenException("유효하지 않은 엑세스 토큰입니다.");
175+
}
176+
}
177+
178+
public boolean isRefreshTokenValid(String token) {
179+
try {
180+
JWT.require(Algorithm.HMAC512(secretKey)).build().verify(token);
181+
log.info("유효한 리프레시 토큰입니다.");
182+
return true;
183+
} catch (Exception e) {
184+
log.error("유효하지 않은 리프레시 토큰입니다. {}", e.getMessage());
185+
throw new InvalidRefreshTokenException("유효하지 않은 리프레시 토큰입니다.");
162186
}
163187
}
164188

ontime-back/src/main/java/devkor/ontime_back/response/ApiResponseForm.java

+12
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ public static <T> ApiResponseForm<T> fail(String code, String message) {
3333
return new ApiResponseForm<>("fail", code, message, null); // 실패의 경우 data는 null로 처리
3434
}
3535

36+
public static <T> ApiResponseForm<T> accessTokenEmpty(String code, String message) {
37+
return new ApiResponseForm<>("accessTokenEmpty", code, message, null); // 실패의 경우 data는 null로 처리
38+
}
39+
40+
public static <T> ApiResponseForm<T> accessTokenInvalid(String code, String message) {
41+
return new ApiResponseForm<>("accessTokenInvalid", code, message, null); // 실패의 경우 data는 null로 처리
42+
}
43+
44+
public static <T> ApiResponseForm<T> refreshTokenInvalid(String code, String message) {
45+
return new ApiResponseForm<>("refreshTokenInvalid", code, message, null); // 실패의 경우 data는 null로 처리
46+
}
47+
3648
// 오류 응답을 위한 메서드
3749
public static <T> ApiResponseForm<T> error(String code, String message) {
3850
return new ApiResponseForm<>("error", code, message, null); // 오류의 경우 data는 null로 처리
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package devkor.ontime_back.response;
2+
3+
public class EmptyAccessTokenException extends InvalidTokenException {
4+
public EmptyAccessTokenException(String message) {
5+
super(message);
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package devkor.ontime_back.response;
2+
3+
public class InvalidAccessTokenException extends InvalidTokenException{
4+
public InvalidAccessTokenException(String message) {
5+
super(message);
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package devkor.ontime_back.response;
2+
3+
public class InvalidRefreshTokenException extends InvalidTokenException{
4+
public InvalidRefreshTokenException(String message) {
5+
super(message);
6+
}
7+
}

0 commit comments

Comments
 (0)