[API 응답 개선] ApiResponseCode에 COMMON_TARGET_NOT_FOUND 추가 및 CommonCodeServiceImpl에서 예외 처리 메시지 수정. JwtTokenIssuanceFilter와 JwtTokenValidationFilter에서 Refresh Token 생성 시 클라이언트 IP 정보 포함. JwtUtils에서 토큰 생성 메서드 개선 및 불필요한 코드 제거.

This commit is contained in:
2025-08-27 15:11:29 +09:00
parent 8c5d7c6c3f
commit 438bfc3bc5
6 changed files with 79 additions and 106 deletions

View File

@@ -46,7 +46,7 @@ public class CommonCodeServiceImpl implements CommonCodeService {
@Transactional
public void updateGroupCode(String code, CommonGroupCodeDto groupCodeDto) {
CommonGroupCode existingGroupCode = commonGroupCodeRepository.findByCode(code)
.orElseThrow(() -> new ApiException(ApiResponseCode.COMMON_NOT_FOUND, "그룹 코드를 찾을 수 없습니다: " + code));
.orElseThrow(() -> new ApiException(ApiResponseCode.COMMON_TARGET_NOT_FOUND, "그룹 코드를 찾을 수 없습니다: " + code));
commonGroupCodeMapper.updateCommonGroupCodeFromDto(groupCodeDto, existingGroupCode);
commonGroupCodeRepository.save(existingGroupCode);

View File

@@ -33,6 +33,7 @@ public enum ApiResponseCode {
// 404 Not Found
COMMON_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "리소스를 찾을 수 없습니다"),
COMMON_TARGET_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "대상을 찾을 수 없습니다"),
// 405 Method Not Allowed
COMMON_METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED.value(), "허용되지 않는 메소드입니다"),

View File

@@ -65,7 +65,7 @@ public class JwtTokenIssuanceFilter extends UsernamePasswordAuthenticationFilter
// 토큰 생성
String accessToken = jwtUtils.createAccessToken(member.getUserId(), member.getRole().getValue());
String refreshToken = jwtUtils.createRefreshToken(member.getUserId(), member.getRole().getValue());
String refreshToken = jwtUtils.createRefreshToken(member.getUserId(), member.getRole().getValue(), httpUtils.getClientIp());
member.setRefreshToken(refreshToken);
member.setLoginIp(httpUtils.getClientIp());

View File

@@ -3,6 +3,7 @@ package com.bio.bio_backend.global.filter;
import java.io.IOException;
import java.util.Objects;
import com.bio.bio_backend.global.utils.HttpUtils;
import org.springframework.core.env.Environment;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@@ -30,6 +31,7 @@ import lombok.extern.slf4j.Slf4j;
public class JwtTokenValidationFilter extends OncePerRequestFilter {
private final JwtUtils jwtUtils;
private final HttpUtils httpUtils;
private final MemberService memberService;
private final Environment env;
private final SecurityPathConfig securityPathConfig;
@@ -66,8 +68,7 @@ public class JwtTokenValidationFilter extends OncePerRequestFilter {
filterChain.doFilter(request, response);
return;
}
}
} else {
// Access Token이 없거나 만료된 경우, Refresh Token으로 갱신 시도
if (refreshToken != null) {
// 1. Refresh Token 유효성 검증
@@ -97,7 +98,7 @@ public class JwtTokenValidationFilter extends OncePerRequestFilter {
response.setHeader("Authorization", "Bearer " + newAccessToken);
// Refresh Token 갱신
String newRefreshToken = jwtUtils.refreshTokens(username, role);
String newRefreshToken = jwtUtils.createRefreshToken(username, role, httpUtils.getClientIp());
jwtUtils.setRefreshTokenCookie(response, newRefreshToken);
// 인증 정보 설정
@@ -114,6 +115,8 @@ public class JwtTokenValidationFilter extends OncePerRequestFilter {
return;
}
}
// 토큰이 없거나 모두 유효하지 않은 경우
log.warn("유효한 JWT 토큰이 없습니다. URI: {}", request.getRequestURI());
sendJsonResponse(response, ApiResponseDto.fail(ApiResponseCode.JWT_TOKEN_NULL));

View File

@@ -46,7 +46,7 @@ public class WebSecurity {
}
private JwtTokenValidationFilter getJwtTokenValidationFilter() {
return new JwtTokenValidationFilter(jwtUtils, memberService, env, securityPathConfig);
return new JwtTokenValidationFilter(jwtUtils, httpUtils, memberService, env, securityPathConfig);
}

View File

@@ -37,7 +37,6 @@ public class JwtUtils {
// Token 생성
public String generateToken(String username, String role, long expirationTime) {
return Jwts.builder()
.subject(username)
.claim("role", role)
@@ -47,26 +46,7 @@ public class JwtUtils {
.compact();
}
// Token 검증
public Boolean validateAccessToken(String token) {
try {
return isTokenExpired(token);
} catch (io.jsonwebtoken.ExpiredJwtException e) {
log.debug("Access Token 만료: {}", e.getMessage());
return false;
} catch (Exception e) {
log.debug("Access Token 검증 실패: {}", e.getMessage());
return false;
}
}
// Refresh Token 생성 시 IP 정보 포함
public String createRefreshToken(String username, String role, String clientIp) {
return generateToken(username, role, clientIp,
Long.parseLong(Objects.requireNonNull(env.getProperty("token.expiration_time_refresh"))));
}
// IP 정보를 포함한 토큰 생성
// Token 생성(IP 정보 포함)
public String generateToken(String username, String role, String clientIp, long expirationTime) {
return Jwts.builder()
.subject(username)
@@ -78,32 +58,42 @@ public class JwtUtils {
.compact();
}
// Access Token 생성
public String createAccessToken(String username, String role) {
long expirationTime = Long.parseLong(Objects.requireNonNull(env.getProperty("token.expiration_time_access")));
return generateToken(username, role, expirationTime);
}
// Refresh Token 생성 시 IP 정보 포함
public String createRefreshToken(String username, String role, String clientIp) {
long expirationTime = Long.parseLong(Objects.requireNonNull(env.getProperty("token.expiration_time_refresh")));
return generateToken(username, role, clientIp, expirationTime);
}
// Token 검증
public Boolean validateAccessToken(String token) {
try {
return isValidTokenExpired(token);
} catch (io.jsonwebtoken.ExpiredJwtException e) {
log.debug("Access Token 만료: {}", e.getMessage());
return false;
} catch (Exception e) {
log.debug("Access Token 검증 실패: {}", e.getMessage());
return false;
}
}
// IP 정보 추출
public String extractClientIp(String token) {
Claims claims = extractAllClaims(token);
return claims.get("ip", String.class);
}
// Refresh Token 검증 시 IP도 함께 검증
public Boolean validateRefreshToken(String token, String clientIp) {
// 1. 토큰 유효성 검증
if (!isValidRefreshToken(token)) {
return false;
}
// 2. IP 주소 검증
if (!isValidClientIp(token, clientIp)) {
return false;
}
return true;
}
// Refresh Token 유효성 검증 (토큰 일치, 만료 여부)
public Boolean isValidRefreshToken(String token) {
try {
String savedToken = memberService.getRefreshToken(extractUsername(token));
return savedToken.equals(token) && !isTokenExpired(token);
return savedToken.equals(token) && isValidTokenExpired(token);
} catch (Exception e) {
log.debug("Refresh Token 검증 실패: {}", e.getMessage());
return false;
@@ -127,8 +117,9 @@ public class JwtUtils {
}
}
private boolean isTokenExpired(String token) {
return !extractAllClaims(token).getExpiration().before(new Date());
private boolean isValidTokenExpired(String token) {
Date expiration = extractAllClaims(token).getExpiration();
return !expiration.before(new Date());
}
public String extractUsername(String token) {
@@ -142,7 +133,6 @@ public class JwtUtils {
}
public Claims extractAllClaims(String token) {
return Jwts.parser()
.verifyWith(getSigningKey())
.build()
@@ -168,27 +158,6 @@ public class JwtUtils {
return null;
}
// Access Token 생성
public String createAccessToken(String username, String role) {
return generateToken(username, role,
Long.parseLong(Objects.requireNonNull(env.getProperty("token.expiration_time_access"))));
}
// Refresh Token 생성
public String createRefreshToken(String username, String role) {
return generateToken(username, role,
Long.parseLong(Objects.requireNonNull(env.getProperty("token.expiration_time_refresh"))));
}
// Refresh Token 갱신 (Access Token 갱신 시 함께)
public String refreshTokens(String username, String role) {
// 새로운 Refresh Token 생성 및 DB 저장
String newRefreshToken = createRefreshToken(username, role);
log.info("Refresh Token 갱신 완료: {}", username);
return newRefreshToken;
}
// Refresh Token 쿠키 설정
public void setRefreshTokenCookie(HttpServletResponse response, String refreshToken) {
Cookie refreshTokenCookie = new Cookie("RefreshToken", refreshToken);