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 @@ -59,12 +59,12 @@ protected void successfulAuthentication(HttpServletRequest request, HttpServletR

String accessToken = jwtUtil.createAccessToken(principal, role);
String refreshToken = jwtUtil.createRefreshToken(principal, role);
redisUtil.setData("refresh:" + jwtUtil.getPrincipal(refreshToken), refreshToken, Constant.REFRESH_TOKEN_EXPIRATION_TIME);
redisUtil.setData("refresh:" + principal, refreshToken, Constant.REFRESH_TOKEN_EXPIRATION_TIME);

setBody(role, response);
ResponseCookie accessCookie = createCookie("accessToken", accessToken, Constant.ACCESS_TOKEN_EXPIRATION_TIME, "/");
ResponseCookie accessCookie = createCookie("accessToken", accessToken,"/");
response.addHeader(HttpHeaders.SET_COOKIE, accessCookie.toString());
ResponseCookie refreshCookie = createCookie("refreshToken", refreshToken, Constant.REFRESH_TOKEN_EXPIRATION_TIME, "/auth");
ResponseCookie refreshCookie = createCookie("refreshToken", refreshToken, "/auth");
response.addHeader(HttpHeaders.SET_COOKIE, refreshCookie.toString());
}

Expand All @@ -84,13 +84,13 @@ private void setBody(String role, HttpServletResponse response) throws IOExcepti
""", role));
}

private ResponseCookie createCookie(String name, String token, long expirationTime, String path) {
private ResponseCookie createCookie(String name, String token, String path) {
return ResponseCookie.from(name, token)
.httpOnly(true)
.secure(true)
.sameSite("None")
.path(path)
.maxAge(Duration.ofMillis(expirationTime).getSeconds())
.maxAge(Duration.ofMillis(Constant.COOKIE_EXPIRATION).getSeconds())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.pickyfy.pickyfy.common.util.JwtUtil;
import com.pickyfy.pickyfy.auth.details.CustomUserDetailsServiceImpl;
import com.pickyfy.pickyfy.web.dto.response.TokenValidationResult;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
Expand All @@ -28,7 +29,14 @@ public class JwtAuthFilter extends OncePerRequestFilter {
protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException {
String token = getAccessTokenFromCookies(request);

if (token == null || !jwtUtil.validateToken(token)) {
if(token == null){
filterChain.doFilter(request, response);
return;
}

TokenValidationResult result = jwtUtil.validateTokenWithoutException(token);
if (!result.isValid()) {
request.setAttribute("errorMessage", result.message());
filterChain.doFilter(request, response);
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,35 @@
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Map;

@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
String message = "인증 정보가 없습니다. 로그인 후 다시 시도하세요";
String defaultMessage = "인증 정보가 없습니다. 로그인 후 다시 시도하세요";
String defaultCode = "401";

Map<String, String> errorCodeMapping = Map.of(
"유효하지 않은 토큰입니다.", "4012",
"토큰 만료", "4013"
);
String message = (request.getAttribute("errorMessage") != null)
? request.getAttribute("errorMessage").toString()
: defaultMessage;

String code = errorCodeMapping.getOrDefault(message, defaultCode);

if(request.getAttribute("errorMessage") != null){
message = request.getAttribute("errorMessage").toString();
}
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(String.format("""
{
"isSuccess": false,
"code": "401",
"code": "%s",
"message": "%s"
}
""", message));
""", code, message));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,22 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo

redisUtil.setData("refresh:" + email, refreshToken, Constant.REFRESH_TOKEN_EXPIRATION_TIME);

ResponseCookie accessCookie = createCookie("accessToken", accessToken, Constant.ACCESS_TOKEN_EXPIRATION_TIME, "/");
ResponseCookie accessCookie = createCookie("accessToken", accessToken, "/");
response.addHeader(HttpHeaders.SET_COOKIE, accessCookie.toString());
ResponseCookie refreshCookie = createCookie("refreshToken", refreshToken, Constant.REFRESH_TOKEN_EXPIRATION_TIME, "/auth");
ResponseCookie refreshCookie = createCookie("refreshToken", refreshToken, "/auth");
response.addHeader(HttpHeaders.SET_COOKIE, refreshCookie.toString());

response.sendRedirect(REDIRECT_URL);
SecurityContextHolder.getContext().setAuthentication(authentication);
}

private ResponseCookie createCookie(String name, String token, long expirationTime, String path) {
private ResponseCookie createCookie(String name, String token, String path) {
return ResponseCookie.from(name, token)
.httpOnly(true)
.secure(true)
.sameSite("None")
.path(path)
.maxAge(Duration.ofMillis(expirationTime).getSeconds())
.maxAge(Duration.ofMillis(Constant.COOKIE_EXPIRATION).getSeconds())
.build();
}
}
2 changes: 2 additions & 0 deletions src/main/java/com/pickyfy/pickyfy/common/Constant.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,7 @@ public class Constant {
public static final long PLACES_EXPIRATION_TIME = 30 * 60 * 1000;
public static final long PLACE_EXPIRATION_TIME = 30 * 60 * 1000;

public static final long COOKIE_EXPIRATION = 4 * 24 * 60 * 60 * 1000;

public static final String REDIS_KEY_PREFIX = "refresh:";
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/", "/swagger-ui/**", "/v3/api-docs/**").permitAll()
.requestMatchers("/users/signup", "/auth/login", "/email-auth/**", "/auth/reissue", "/users/verify-by-email", "/users/reset-password").permitAll()
.requestMatchers("/auth/oauth2/**", "/oauth2/callback", "/auth/me").permitAll()
.requestMatchers("/auth/oauth2/**", "/oauth2/callback", "/auth/me", "/auth/logout").permitAll()
.requestMatchers("/actuator/**", "/actuator/prometheus").permitAll()
.requestMatchers("/admin/**").hasAuthority("ADMIN")
.anyRequest().authenticated()
Expand Down
49 changes: 36 additions & 13 deletions src/main/java/com/pickyfy/pickyfy/common/util/JwtUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.pickyfy.pickyfy.web.apiResponse.error.ErrorStatus;
import com.pickyfy.pickyfy.common.Constant;
import com.pickyfy.pickyfy.exception.ExceptionHandler;
import com.pickyfy.pickyfy.web.dto.response.TokenValidationResult;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
Expand All @@ -14,6 +15,8 @@
import javax.crypto.SecretKey;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Component
Expand Down Expand Up @@ -72,21 +75,42 @@ private String createToken(String principal, String role, long expireTime, Strin
.compact();
}

public boolean validateToken(String token) { // 예외처리 추가
public void validateToken(String token) {
try {
Jwts.parser().verifyWith((SecretKey) key).build().parseSignedClaims(token);
return true;
} catch (SecurityException | MalformedJwtException e) {
log.info("잘못된 서명 혹은 JWT 형식 오류", e);
} catch (ExpiredJwtException e) {
log.info("토큰 만료", e);
throw new ExceptionHandler(ErrorStatus.TOKEN_EXPIRATION);
} catch (UnsupportedJwtException e) {
log.info("지원하지 않는 서명 알고리즘", e);
} catch (IllegalArgumentException e) {
log.info("올바르지 않은 값 입력(토큰 문자열 null)", e);
TokenValidationResult.success("검증 완료.");
} catch (Exception e) {
handleJwtException(e, true);
}
}

public TokenValidationResult validateTokenWithoutException(String token) {
try {
Jwts.parser().verifyWith((SecretKey) key).build().parseSignedClaims(token);
return TokenValidationResult.success("검증 완료.");
} catch (Exception e) {
return handleJwtException(e, false);
}
}

private TokenValidationResult handleJwtException(Exception e, boolean throwException) {
Map<Class<? extends Exception>, String> errorMessages = new HashMap<>();
errorMessages.put(SecurityException.class, "유효하지 않은 토큰입니다.");
errorMessages.put(MalformedJwtException.class, "유효하지 않은 토큰입니다.");
errorMessages.put(UnsupportedJwtException.class, "유효하지 않은 토큰입니다.");
errorMessages.put(IllegalArgumentException.class, "유효하지 않은 토큰입니다.");
errorMessages.put(ExpiredJwtException.class, "토큰 만료");

String message = errorMessages.getOrDefault(e.getClass(), "알 수 없는 JWT 오류");
log.info(message, e);

if (throwException) {
throw new ExceptionHandler(
e instanceof ExpiredJwtException ? ErrorStatus.TOKEN_EXPIRATION : ErrorStatus.TOKEN_INVALID
);
}
return false;

return TokenValidationResult.failure(message);
}

public Claims parseClaims(String token) {
Expand All @@ -108,5 +132,4 @@ public String getPrincipal(String token) {
public String getRole(String token) {
return parseClaims(token).get(ROLE, String.class);
}

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.pickyfy.pickyfy.exception;

import com.pickyfy.pickyfy.web.apiResponse.common.BaseErrorCode;
import com.pickyfy.pickyfy.web.apiResponse.error.ErrorStatus;

public class DuplicateResourceException extends GeneralException {
public DuplicateResourceException(BaseErrorCode baseErrorCode) {
super(baseErrorCode);
public DuplicateResourceException(ErrorStatus errorStatus) {
super(errorStatus);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

BaseErrorCode 추상화 로직 다 ErrorStatus로 바꿨던데 이유가 뭔가요?

지금 구현이면 BaseErrorCode 삭제해도 될 것 같습니다.

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.pickyfy.pickyfy.exception;

import com.pickyfy.pickyfy.web.apiResponse.common.BaseErrorCode;
import com.pickyfy.pickyfy.web.apiResponse.error.ErrorStatus;

public class ExceptionHandler extends GeneralException {
public ExceptionHandler(BaseErrorCode errorCode){
super(errorCode);
public ExceptionHandler(ErrorStatus errorStatus){
super(errorStatus);
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
package com.pickyfy.pickyfy.exception;

import com.pickyfy.pickyfy.web.apiResponse.common.BaseErrorCode;
import com.pickyfy.pickyfy.web.apiResponse.error.ErrorResponse;
import com.pickyfy.pickyfy.web.apiResponse.error.ErrorStatus;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class GeneralException extends RuntimeException {

private BaseErrorCode code;

public ErrorResponse getErrorReason(){
return this.code.getReason();
}
private final ErrorStatus errorStatus;
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.pickyfy.pickyfy.exception;

import com.pickyfy.pickyfy.web.apiResponse.common.ApiResponse;
import com.pickyfy.pickyfy.web.apiResponse.error.ErrorResponse;
import com.pickyfy.pickyfy.web.apiResponse.error.ErrorStatus;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolation;
import lombok.extern.slf4j.Slf4j;
import jakarta.validation.ConstraintViolationException;
import org.springframework.http.HttpHeaders;
Expand All @@ -29,7 +29,7 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler
public ResponseEntity<Object> handleConstraintViolation(ConstraintViolationException e, WebRequest request){
String errorMessage = e.getConstraintViolations().stream()
.map(constraintViolation -> constraintViolation.getMessage())
.map(ConstraintViolation::getMessage)
.findFirst()
.orElseThrow(() -> new RuntimeException("ConstraintViolationException 추출 도중 에러 발생"));

Expand All @@ -41,7 +41,7 @@ public ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotVali

Map<String, String> errors = new LinkedHashMap<>();

e.getBindingResult().getFieldErrors().stream()
e.getBindingResult().getFieldErrors()
.forEach(fieldError -> {
String fieldName = fieldError.getField();
String errorMessage = Optional.ofNullable(fieldError.getDefaultMessage()).orElse("");
Expand All @@ -52,9 +52,8 @@ public ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotVali
}

@ExceptionHandler(value = GeneralException.class)
public ResponseEntity handleOnThrowException(GeneralException generalException, HttpServletRequest request) {
ErrorResponse errorReasonHttpStatus = generalException.getErrorReason();
return buildOnThrowExceptionResponse(generalException,errorReasonHttpStatus,null,request);
public ResponseEntity<Object> handleOnThrowException(GeneralException generalException, HttpServletRequest request) {
return buildOnThrowExceptionResponse(generalException, generalException.getErrorStatus(),null, request);
}

@ExceptionHandler
Expand All @@ -63,33 +62,34 @@ public ResponseEntity<Object> handleUnexpectedException(Exception e, WebRequest
return buildUnexpectedExceptionResponse(e, ErrorStatus._INTERNAL_SERVER_ERROR, HttpHeaders.EMPTY, ErrorStatus._INTERNAL_SERVER_ERROR.getHttpStatus(),request, e.getMessage());
}

private ResponseEntity<Object> buildConstraintViolationResponse(Exception e, ErrorStatus errorCommonStatus,
HttpHeaders headers, WebRequest request) {
ApiResponse<Object> body = ApiResponse.onFailure(errorCommonStatus.getCode(), errorCommonStatus.getMessage(), null);
private ResponseEntity<Object> buildConstraintViolationResponse(Exception e, ErrorStatus errorStatus,
HttpHeaders headers, WebRequest request) {
ApiResponse<Object> body = ApiResponse.onFailure(errorStatus, null);
return super.handleExceptionInternal(
e,
body,
headers,
errorCommonStatus.getHttpStatus(),
errorStatus.getHttpStatus(),
request
);
}

private ResponseEntity<Object> buildInvalidMethodArgumentResponse(Exception e, HttpHeaders headers, ErrorStatus errorCommonStatus,
WebRequest request, Map<String, String> errorArgs) {
ApiResponse<Object> body = ApiResponse.onFailure(errorCommonStatus.getCode(),errorCommonStatus.getMessage(),errorArgs);
private ResponseEntity<Object> buildInvalidMethodArgumentResponse(Exception e, HttpHeaders headers, ErrorStatus errorStatus,
WebRequest request, Map<String, String> errorArgs) {

ApiResponse<Object> body = ApiResponse.onFailure(errorStatus, errorArgs);
return super.handleExceptionInternal(
e,
body,
headers,
errorCommonStatus.getHttpStatus(),
errorStatus.getHttpStatus(),
request
);
}

private ResponseEntity<Object> buildUnexpectedExceptionResponse(Exception e, ErrorStatus errorCommonStatus,
HttpHeaders headers, HttpStatus status, WebRequest request, String errorPoint) {
ApiResponse<Object> body = ApiResponse.onFailure(errorCommonStatus.getCode(),errorCommonStatus.getMessage(),errorPoint);
private ResponseEntity<Object> buildUnexpectedExceptionResponse(Exception e, ErrorStatus errorStatus,
HttpHeaders headers, HttpStatus status, WebRequest request, String errorPoint) {
ApiResponse<Object> body = ApiResponse.onFailure(errorStatus, errorPoint);
return super.handleExceptionInternal(
e,
body,
Expand All @@ -99,18 +99,18 @@ private ResponseEntity<Object> buildUnexpectedExceptionResponse(Exception e, Err
);
}

private ResponseEntity<Object> buildOnThrowExceptionResponse(Exception e, ErrorResponse reason,
HttpHeaders headers, HttpServletRequest request) {
private ResponseEntity<Object> buildOnThrowExceptionResponse(Exception e, ErrorStatus errorStatus,
HttpHeaders headers, HttpServletRequest request) {

ApiResponse<Object> body = ApiResponse.onFailure(reason.getCode(),reason.getMessage(),null);
ApiResponse<Object> body = ApiResponse.onFailure(errorStatus, null);
WebRequest webRequest = new ServletWebRequest(request);

return super.handleExceptionInternal(
e,
body,
headers,
null,
errorStatus.getHttpStatus(),
webRequest
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.pickyfy.pickyfy.exception;

import com.pickyfy.pickyfy.web.apiResponse.error.ErrorStatus;

public class InvalidRefreshTokenException extends GeneralException {
public InvalidRefreshTokenException(ErrorStatus errorStatus) {
super(errorStatus);
}
}
Loading