[API 응답 개선] ApiResponseCode에 COMMON_TARGET_NOT_FOUND 추가 및 CommonCodeServiceImpl에서 예외 처리 메시지 수정. JwtTokenIssuanceFilter와 JwtTokenValidationFilter에서 Refresh Token 생성 시 클라이언트 IP 정보 포함. JwtUtils에서 토큰 생성 메서드 개선 및 불필요한 코드 제거.
This commit is contained in:
@@ -46,7 +46,7 @@ public class CommonCodeServiceImpl implements CommonCodeService {
|
|||||||
@Transactional
|
@Transactional
|
||||||
public void updateGroupCode(String code, CommonGroupCodeDto groupCodeDto) {
|
public void updateGroupCode(String code, CommonGroupCodeDto groupCodeDto) {
|
||||||
CommonGroupCode existingGroupCode = commonGroupCodeRepository.findByCode(code)
|
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);
|
commonGroupCodeMapper.updateCommonGroupCodeFromDto(groupCodeDto, existingGroupCode);
|
||||||
commonGroupCodeRepository.save(existingGroupCode);
|
commonGroupCodeRepository.save(existingGroupCode);
|
||||||
|
@@ -33,6 +33,7 @@ public enum ApiResponseCode {
|
|||||||
|
|
||||||
// 404 Not Found
|
// 404 Not Found
|
||||||
COMMON_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "리소스를 찾을 수 없습니다"),
|
COMMON_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "리소스를 찾을 수 없습니다"),
|
||||||
|
COMMON_TARGET_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "대상을 찾을 수 없습니다"),
|
||||||
|
|
||||||
// 405 Method Not Allowed
|
// 405 Method Not Allowed
|
||||||
COMMON_METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED.value(), "허용되지 않는 메소드입니다"),
|
COMMON_METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED.value(), "허용되지 않는 메소드입니다"),
|
||||||
|
@@ -65,7 +65,7 @@ public class JwtTokenIssuanceFilter extends UsernamePasswordAuthenticationFilter
|
|||||||
|
|
||||||
// 토큰 생성
|
// 토큰 생성
|
||||||
String accessToken = jwtUtils.createAccessToken(member.getUserId(), member.getRole().getValue());
|
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.setRefreshToken(refreshToken);
|
||||||
member.setLoginIp(httpUtils.getClientIp());
|
member.setLoginIp(httpUtils.getClientIp());
|
||||||
|
@@ -3,6 +3,7 @@ package com.bio.bio_backend.global.filter;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import com.bio.bio_backend.global.utils.HttpUtils;
|
||||||
import org.springframework.core.env.Environment;
|
import org.springframework.core.env.Environment;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
@@ -30,6 +31,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
public class JwtTokenValidationFilter extends OncePerRequestFilter {
|
public class JwtTokenValidationFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
private final JwtUtils jwtUtils;
|
private final JwtUtils jwtUtils;
|
||||||
|
private final HttpUtils httpUtils;
|
||||||
private final MemberService memberService;
|
private final MemberService memberService;
|
||||||
private final Environment env;
|
private final Environment env;
|
||||||
private final SecurityPathConfig securityPathConfig;
|
private final SecurityPathConfig securityPathConfig;
|
||||||
@@ -66,52 +68,53 @@ public class JwtTokenValidationFilter extends OncePerRequestFilter {
|
|||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
// Access Token이 없거나 만료된 경우, Refresh Token으로 갱신 시도
|
||||||
|
if (refreshToken != null) {
|
||||||
|
// 1. Refresh Token 유효성 검증
|
||||||
|
if (!jwtUtils.isValidRefreshToken(refreshToken)) {
|
||||||
|
log.warn("Refresh Token이 유효하지 않습니다. URI: {}", request.getRequestURI());
|
||||||
|
sendJsonResponse(response, ApiResponseDto.fail(ApiResponseCode.JWT_TOKEN_EXPIRED));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Access Token이 없거나 만료된 경우, Refresh Token으로 갱신 시도
|
// 2. IP 주소 검증
|
||||||
if (refreshToken != null) {
|
if (!jwtUtils.isValidClientIp(refreshToken, request.getRemoteAddr())) {
|
||||||
// 1. Refresh Token 유효성 검증
|
log.warn("클라이언트 IP 주소가 일치하지 않습니다. URI: {}, IP: {}",
|
||||||
if (!jwtUtils.isValidRefreshToken(refreshToken)) {
|
request.getRequestURI(), request.getRemoteAddr());
|
||||||
log.warn("Refresh Token이 유효하지 않습니다. URI: {}", request.getRequestURI());
|
sendJsonResponse(response, ApiResponseDto.fail(ApiResponseCode.INVALID_CLIENT_IP));
|
||||||
sendJsonResponse(response, ApiResponseDto.fail(ApiResponseCode.JWT_TOKEN_EXPIRED));
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 모든 검증을 통과한 경우 토큰 갱신 진행
|
||||||
|
String username = jwtUtils.extractUsername(refreshToken);
|
||||||
|
String role = jwtUtils.extractRole(refreshToken);
|
||||||
|
|
||||||
|
// 새로운 Access Token 생성
|
||||||
|
String newAccessToken = jwtUtils.generateToken(username, role,
|
||||||
|
Long.parseLong(Objects.requireNonNull(env.getProperty("token.expiration_time_access"))));
|
||||||
|
|
||||||
|
// 새로운 Access Token을 응답 헤더에 설정
|
||||||
|
response.setHeader("Authorization", "Bearer " + newAccessToken);
|
||||||
|
|
||||||
|
// Refresh Token 갱신
|
||||||
|
String newRefreshToken = jwtUtils.createRefreshToken(username, role, httpUtils.getClientIp());
|
||||||
|
jwtUtils.setRefreshTokenCookie(response, newRefreshToken);
|
||||||
|
|
||||||
|
// 인증 정보 설정
|
||||||
|
UserDetails userDetails = memberService.loadUserByUsername(username);
|
||||||
|
if (userDetails != null) {
|
||||||
|
UsernamePasswordAuthenticationToken authentication =
|
||||||
|
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
|
||||||
|
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("토큰 자동 갱신 성공: {}", username);
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. IP 주소 검증
|
|
||||||
if (!jwtUtils.isValidClientIp(refreshToken, request.getRemoteAddr())) {
|
|
||||||
log.warn("클라이언트 IP 주소가 일치하지 않습니다. URI: {}, IP: {}",
|
|
||||||
request.getRequestURI(), request.getRemoteAddr());
|
|
||||||
sendJsonResponse(response, ApiResponseDto.fail(ApiResponseCode.INVALID_CLIENT_IP));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 모든 검증을 통과한 경우 토큰 갱신 진행
|
|
||||||
String username = jwtUtils.extractUsername(refreshToken);
|
|
||||||
String role = jwtUtils.extractRole(refreshToken);
|
|
||||||
|
|
||||||
// 새로운 Access Token 생성
|
|
||||||
String newAccessToken = jwtUtils.generateToken(username, role,
|
|
||||||
Long.parseLong(Objects.requireNonNull(env.getProperty("token.expiration_time_access"))));
|
|
||||||
|
|
||||||
// 새로운 Access Token을 응답 헤더에 설정
|
|
||||||
response.setHeader("Authorization", "Bearer " + newAccessToken);
|
|
||||||
|
|
||||||
// Refresh Token 갱신
|
|
||||||
String newRefreshToken = jwtUtils.refreshTokens(username, role);
|
|
||||||
jwtUtils.setRefreshTokenCookie(response, newRefreshToken);
|
|
||||||
|
|
||||||
// 인증 정보 설정
|
|
||||||
UserDetails userDetails = memberService.loadUserByUsername(username);
|
|
||||||
if (userDetails != null) {
|
|
||||||
UsernamePasswordAuthenticationToken authentication =
|
|
||||||
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
|
|
||||||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
|
||||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("토큰 자동 갱신 성공: {}", username);
|
|
||||||
filterChain.doFilter(request, response);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 토큰이 없거나 모두 유효하지 않은 경우
|
// 토큰이 없거나 모두 유효하지 않은 경우
|
||||||
|
@@ -46,7 +46,7 @@ public class WebSecurity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private JwtTokenValidationFilter getJwtTokenValidationFilter() {
|
private JwtTokenValidationFilter getJwtTokenValidationFilter() {
|
||||||
return new JwtTokenValidationFilter(jwtUtils, memberService, env, securityPathConfig);
|
return new JwtTokenValidationFilter(jwtUtils, httpUtils, memberService, env, securityPathConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -37,7 +37,6 @@ public class JwtUtils {
|
|||||||
|
|
||||||
// Token 생성
|
// Token 생성
|
||||||
public String generateToken(String username, String role, long expirationTime) {
|
public String generateToken(String username, String role, long expirationTime) {
|
||||||
|
|
||||||
return Jwts.builder()
|
return Jwts.builder()
|
||||||
.subject(username)
|
.subject(username)
|
||||||
.claim("role", role)
|
.claim("role", role)
|
||||||
@@ -47,10 +46,34 @@ public class JwtUtils {
|
|||||||
.compact();
|
.compact();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Token 생성(IP 정보 포함)
|
||||||
|
public String generateToken(String username, String role, String clientIp, long expirationTime) {
|
||||||
|
return Jwts.builder()
|
||||||
|
.subject(username)
|
||||||
|
.claim("role", role)
|
||||||
|
.claim("ip", clientIp) // IP 정보 추가
|
||||||
|
.issuedAt(new Date(System.currentTimeMillis()))
|
||||||
|
.expiration(new Date(System.currentTimeMillis() + expirationTime))
|
||||||
|
.signWith(getSigningKey())
|
||||||
|
.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 검증
|
// Token 검증
|
||||||
public Boolean validateAccessToken(String token) {
|
public Boolean validateAccessToken(String token) {
|
||||||
try {
|
try {
|
||||||
return isTokenExpired(token);
|
return isValidTokenExpired(token);
|
||||||
} catch (io.jsonwebtoken.ExpiredJwtException e) {
|
} catch (io.jsonwebtoken.ExpiredJwtException e) {
|
||||||
log.debug("Access Token 만료: {}", e.getMessage());
|
log.debug("Access Token 만료: {}", e.getMessage());
|
||||||
return false;
|
return false;
|
||||||
@@ -60,50 +83,17 @@ public class JwtUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 정보를 포함한 토큰 생성
|
|
||||||
public String generateToken(String username, String role, String clientIp, long expirationTime) {
|
|
||||||
return Jwts.builder()
|
|
||||||
.subject(username)
|
|
||||||
.claim("role", role)
|
|
||||||
.claim("ip", clientIp) // IP 정보 추가
|
|
||||||
.issuedAt(new Date(System.currentTimeMillis()))
|
|
||||||
.expiration(new Date(System.currentTimeMillis() + expirationTime))
|
|
||||||
.signWith(getSigningKey())
|
|
||||||
.compact();
|
|
||||||
}
|
|
||||||
|
|
||||||
// IP 정보 추출
|
// IP 정보 추출
|
||||||
public String extractClientIp(String token) {
|
public String extractClientIp(String token) {
|
||||||
Claims claims = extractAllClaims(token);
|
Claims claims = extractAllClaims(token);
|
||||||
return claims.get("ip", String.class);
|
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 유효성 검증 (토큰 일치, 만료 여부)
|
// Refresh Token 유효성 검증 (토큰 일치, 만료 여부)
|
||||||
public Boolean isValidRefreshToken(String token) {
|
public Boolean isValidRefreshToken(String token) {
|
||||||
try {
|
try {
|
||||||
String savedToken = memberService.getRefreshToken(extractUsername(token));
|
String savedToken = memberService.getRefreshToken(extractUsername(token));
|
||||||
return savedToken.equals(token) && !isTokenExpired(token);
|
return savedToken.equals(token) && isValidTokenExpired(token);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.debug("Refresh Token 검증 실패: {}", e.getMessage());
|
log.debug("Refresh Token 검증 실패: {}", e.getMessage());
|
||||||
return false;
|
return false;
|
||||||
@@ -127,8 +117,9 @@ public class JwtUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isTokenExpired(String token) {
|
private boolean isValidTokenExpired(String token) {
|
||||||
return !extractAllClaims(token).getExpiration().before(new Date());
|
Date expiration = extractAllClaims(token).getExpiration();
|
||||||
|
return !expiration.before(new Date());
|
||||||
}
|
}
|
||||||
|
|
||||||
public String extractUsername(String token) {
|
public String extractUsername(String token) {
|
||||||
@@ -142,7 +133,6 @@ public class JwtUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Claims extractAllClaims(String token) {
|
public Claims extractAllClaims(String token) {
|
||||||
|
|
||||||
return Jwts.parser()
|
return Jwts.parser()
|
||||||
.verifyWith(getSigningKey())
|
.verifyWith(getSigningKey())
|
||||||
.build()
|
.build()
|
||||||
@@ -167,27 +157,6 @@ public class JwtUtils {
|
|||||||
}
|
}
|
||||||
return null;
|
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 쿠키 설정
|
// Refresh Token 쿠키 설정
|
||||||
public void setRefreshTokenCookie(HttpServletResponse response, String refreshToken) {
|
public void setRefreshTokenCookie(HttpServletResponse response, String refreshToken) {
|
||||||
|
Reference in New Issue
Block a user