2 Commits

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,52 +68,53 @@ public class JwtTokenValidationFilter extends OncePerRequestFilter {
filterChain.doFilter(request, response);
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으로 갱신 시도
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));
// 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.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;
}
// 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;
}
// 토큰이 없거나 모두 유효하지 않은 경우

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,10 +46,34 @@ public class JwtUtils {
.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 검증
public Boolean validateAccessToken(String token) {
try {
return isTokenExpired(token);
return isValidTokenExpired(token);
} catch (io.jsonwebtoken.ExpiredJwtException e) {
log.debug("Access Token 만료: {}", e.getMessage());
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 정보 추출
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()
@@ -167,27 +157,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) {