diff --git a/member/src/main/java/com/ktds/hi/member/config/JwtAuthenticationFilter.java b/member/src/main/java/com/ktds/hi/member/config/JwtAuthenticationFilter.java index 347a1c1..49b28e6 100644 --- a/member/src/main/java/com/ktds/hi/member/config/JwtAuthenticationFilter.java +++ b/member/src/main/java/com/ktds/hi/member/config/JwtAuthenticationFilter.java @@ -1,57 +1,59 @@ -package com.ktds.hi.member.config; - -import com.ktds.hi.member.service.JwtTokenProvider; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.util.StringUtils; -import org.springframework.web.filter.OncePerRequestFilter; - -import java.io.IOException; - -/** - * JWT 인증 필터 클래스 - * 요청 헤더의 JWT 토큰을 검증하고 인증 정보를 설정 - */ -@RequiredArgsConstructor -@Slf4j -public class JwtAuthenticationFilter extends OncePerRequestFilter { - - private final JwtTokenProvider tokenProvider; - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, - FilterChain filterChain) throws ServletException, IOException { - - try { - String token = resolveToken(request); - - if (StringUtils.hasText(token) && tokenProvider.validateToken(token)) { - Authentication authentication = tokenProvider.getAuthentication(token); - SecurityContextHolder.getContext().setAuthentication(authentication); - log.debug("JWT 토큰 인증 성공: {}", authentication.getName()); - } - } catch (Exception e) { - log.error("JWT 토큰 인증 실패", e); - SecurityContextHolder.clearContext(); - } - - filterChain.doFilter(request, response); - } - - /** - * 요청 헤더에서 JWT 토큰 추출 - */ - private String resolveToken(HttpServletRequest request) { - String bearerToken = request.getHeader("Authorization"); - if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { - return bearerToken.substring(7); - } - return null; - } -} \ No newline at end of file +// package com.ktds.hi.member.config; +// +// +// import jakarta.servlet.FilterChain; +// import jakarta.servlet.ServletException; +// import jakarta.servlet.http.HttpServletRequest; +// import jakarta.servlet.http.HttpServletResponse; +// import lombok.RequiredArgsConstructor; +// import lombok.extern.slf4j.Slf4j; +// import org.springframework.security.core.Authentication; +// import org.springframework.security.core.context.SecurityContextHolder; +// import org.springframework.util.StringUtils; +// import org.springframework.web.filter.OncePerRequestFilter; +// +// import java.io.IOException; +// +// import com.ktds.hi.common.security.JwtTokenProvider; +// +// /** +// * JWT 인증 필터 클래스 +// * 요청 헤더의 JWT 토큰을 검증하고 인증 정보를 설정 +// */ +// @RequiredArgsConstructor +// @Slf4j +// public class JwtAuthenticationFilter extends OncePerRequestFilter { +// +// private final JwtTokenProvider tokenProvider; +// +// @Override +// protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, +// FilterChain filterChain) throws ServletException, IOException { +// +// try { +// String token = resolveToken(request); +// +// if (StringUtils.hasText(token) && tokenProvider.validateToken(token)) { +// Authentication authentication = tokenProvider.getAuthentication(token); +// SecurityContextHolder.getContext().setAuthentication(authentication); +// log.debug("JWT 토큰 인증 성공: {}", authentication.getName()); +// } +// } catch (Exception e) { +// log.error("JWT 토큰 인증 실패", e); +// SecurityContextHolder.clearContext(); +// } +// +// filterChain.doFilter(request, response); +// } +// +// /** +// * 요청 헤더에서 JWT 토큰 추출 +// */ +// private String resolveToken(HttpServletRequest request) { +// String bearerToken = request.getHeader("Authorization"); +// if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { +// return bearerToken.substring(7); +// } +// return null; +// } +// } \ No newline at end of file diff --git a/member/src/main/java/com/ktds/hi/member/config/SecurityConfig.java b/member/src/main/java/com/ktds/hi/member/config/SecurityConfig.java index 3e935a0..c20761f 100644 --- a/member/src/main/java/com/ktds/hi/member/config/SecurityConfig.java +++ b/member/src/main/java/com/ktds/hi/member/config/SecurityConfig.java @@ -1,8 +1,9 @@ package com.ktds.hi.member.config; -import com.ktds.hi.member.config.JwtAuthenticationFilter; -import com.ktds.hi.member.service.JwtTokenProvider; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ktds.hi.common.security.JwtTokenProvider; +import com.ktds.hi.common.security.JwtAuthenticationFilter; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; @@ -26,7 +27,6 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic @RequiredArgsConstructor public class SecurityConfig { - @Qualifier("memberJwtTokenProvider") private final JwtTokenProvider jwtTokenProvider; /** @@ -54,7 +54,7 @@ public class SecurityConfig { */ @Bean public JwtAuthenticationFilter jwtAuthenticationFilter() { - return new JwtAuthenticationFilter(jwtTokenProvider); + return new JwtAuthenticationFilter(jwtTokenProvider,new ObjectMapper()); } /** diff --git a/member/src/main/java/com/ktds/hi/member/service/AuthServiceImpl.java b/member/src/main/java/com/ktds/hi/member/service/AuthServiceImpl.java index 7026d6d..f89b51e 100644 --- a/member/src/main/java/com/ktds/hi/member/service/AuthServiceImpl.java +++ b/member/src/main/java/com/ktds/hi/member/service/AuthServiceImpl.java @@ -1,5 +1,6 @@ package com.ktds.hi.member.service; +import com.ktds.hi.common.security.JwtTokenProvider; import com.ktds.hi.member.dto.*; import com.ktds.hi.member.repository.entity.MemberEntity; import com.ktds.hi.member.repository.jpa.MemberRepository; @@ -11,6 +12,7 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import java.util.Collections; import java.util.concurrent.TimeUnit; /** @@ -24,7 +26,6 @@ public class AuthServiceImpl implements AuthService { private final MemberRepository memberRepository; private final PasswordEncoder passwordEncoder; - @Qualifier("memberJwtTokenProvider") private final JwtTokenProvider jwtTokenProvider; private final SmsService smsService; private final RedisTemplate redisTemplate; @@ -41,9 +42,13 @@ public class AuthServiceImpl implements AuthService { } // JWT 토큰 생성 - String accessToken = jwtTokenProvider.generateAccessToken(member.getId(), member.getRole()); - String refreshToken = jwtTokenProvider.generateRefreshToken(member.getId()); - + String accessToken = jwtTokenProvider.createAccessToken( + member.getId().toString(), + Collections.singletonList(member.getRole()) + ); + String refreshToken = jwtTokenProvider.createRefreshToken(member.getId().toString()); + + // 리프레시 토큰 Redis 저장 redisTemplate.opsForValue().set( "refresh_token:" + member.getId(), @@ -62,10 +67,12 @@ public class AuthServiceImpl implements AuthService { @Override public void logout(LogoutRequest request) { // 리프레시 토큰에서 사용자 ID 추출 - Long memberId = jwtTokenProvider.getMemberIdFromToken(request.getRefreshToken()); - - // Redis에서 리프레시 토큰 삭제 - redisTemplate.delete("refresh_token:" + memberId); + String userId = jwtTokenProvider.getUserIdFromToken(request.getRefreshToken()); + + if (userId != null) { + // Redis에서 리프레시 토큰 삭제 + redisTemplate.delete("refresh_token:" + userId); + } } @Override @@ -75,7 +82,7 @@ public class AuthServiceImpl implements AuthService { throw new BusinessException("유효하지 않은 리프레시 토큰입니다"); } - Long memberId = jwtTokenProvider.getMemberIdFromToken(refreshToken); + Long memberId = Long.parseLong(jwtTokenProvider.getUserIdFromToken(refreshToken)); // Redis에서 리프레시 토큰 확인 String storedToken = redisTemplate.opsForValue().get("refresh_token:" + memberId); @@ -88,8 +95,12 @@ public class AuthServiceImpl implements AuthService { .orElseThrow(() -> new BusinessException("존재하지 않는 사용자입니다")); // 새 토큰 생성 - String newAccessToken = jwtTokenProvider.generateAccessToken(member.getId(), member.getRole()); - String newRefreshToken = jwtTokenProvider.generateRefreshToken(member.getId()); + String newAccessToken = jwtTokenProvider.createAccessToken( + member.getId().toString(), + Collections.singletonList(member.getRole()) + ); + + String newRefreshToken = jwtTokenProvider.createRefreshToken(member.getId().toString()); // 새 리프레시 토큰 Redis 저장 redisTemplate.opsForValue().set( diff --git a/member/src/main/java/com/ktds/hi/member/service/JwtTokenProvider.java b/member/src/main/java/com/ktds/hi/member/service/JwtTokenProvider.java deleted file mode 100644 index f7d190f..0000000 --- a/member/src/main/java/com/ktds/hi/member/service/JwtTokenProvider.java +++ /dev/null @@ -1,139 +0,0 @@ -package com.ktds.hi.member.service; - -import io.jsonwebtoken.*; -import io.jsonwebtoken.security.Keys; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.stereotype.Component; - -import javax.crypto.SecretKey; -import java.util.Collections; -import java.util.Date; - -/** - * JWT 토큰 프로바이더 클래스 - * JWT 토큰 생성, 검증, 파싱 기능을 제공 - */ -@Component("memberJwtTokenProvider") -@Slf4j -public class JwtTokenProvider { - - private final SecretKey secretKey; - private final long accessTokenExpiration; - private final long refreshTokenExpiration; - - public JwtTokenProvider(@Value("${jwt.secret:mySecretKey123456789012345678901234567890}") String secret, - @Value("${jwt.access-token-expiration:1800000}") long accessTokenExpiration, - @Value("${jwt.refresh-token-expiration:604800000}") long refreshTokenExpiration) { - this.secretKey = Keys.hmacShaKeyFor(secret.getBytes()); - this.accessTokenExpiration = accessTokenExpiration; - this.refreshTokenExpiration = refreshTokenExpiration; - } - - /** - * 액세스 토큰 생성 - */ - public String generateAccessToken(Long memberId, String role) { - Date now = new Date(); - Date expiration = new Date(now.getTime() + accessTokenExpiration); - - return Jwts.builder() - .setSubject(memberId.toString()) - .claim("role", role) - .claim("type", "access") - .setIssuedAt(now) - .setExpiration(expiration) - .signWith(secretKey) - .compact(); - } - - /** - * 리프레시 토큰 생성 - */ - public String generateRefreshToken(Long memberId) { - Date now = new Date(); - Date expiration = new Date(now.getTime() + refreshTokenExpiration); - - return Jwts.builder() - .setSubject(memberId.toString()) - .claim("type", "refresh") - .setIssuedAt(now) - .setExpiration(expiration) - .signWith(secretKey) - .compact(); - } - - /** - * 토큰에서 인증 정보 추출 - */ - public Authentication getAuthentication(String token) { - Claims claims = getClaims(token); - String memberId = claims.getSubject(); - String role = (String) claims.get("role"); - - SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + (role != null ? role : "USER")); - - return new UsernamePasswordAuthenticationToken( - memberId, - null, - Collections.singletonList(authority) - ); - } - - /** - * 토큰 유효성 검증 - */ - public boolean validateToken(String token) { - try { - getClaims(token); - return true; - } catch (ExpiredJwtException e) { - log.warn("JWT 토큰 만료: {}", e.getMessage()); - } catch (UnsupportedJwtException e) { - log.warn("지원되지 않는 JWT 토큰: {}", e.getMessage()); - } catch (MalformedJwtException e) { - log.warn("잘못된 형식의 JWT 토큰: {}", e.getMessage()); - } catch (SecurityException | IllegalArgumentException e) { - log.warn("JWT 토큰 검증 실패: {}", e.getMessage()); - } - return false; - } - - /** - * 토큰에서 회원 ID 추출 - */ - public Long getMemberIdFromToken(String token) { - Claims claims = getClaims(token); - return Long.parseLong(claims.getSubject()); - } - - /** - * 토큰에서 클레임 추출 - */ - private Claims getClaims(String token) { - return Jwts.parser() - .setSigningKey(secretKey) - .build() - .parseClaimsJws(token) - .getBody(); - } - - /** - * 토큰 만료 시간 조회 - */ - public Date getExpirationDateFromToken(String token) { - Claims claims = getClaims(token); - return claims.getExpiration(); - } - - /** - * 토큰이 만료되었는지 확인 - */ - public boolean isTokenExpired(String token) { - Date expiration = getExpirationDateFromToken(token); - return expiration.before(new Date()); - } -} \ No newline at end of file diff --git a/recommend/build.gradle b/recommend/build.gradle index 1aec46a..5db4df7 100644 --- a/recommend/build.gradle +++ b/recommend/build.gradle @@ -1,6 +1,11 @@ dependencies { implementation project(':common') - - // File Storage + + // AI and Location Services implementation 'org.springframework.boot:spring-boot-starter-webflux' -} \ No newline at end of file + implementation 'org.springframework.cloud:spring-cloud-starter-openfeign:4.1.0' + + + implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j:' + implementation 'org.springframework.cloud:spring-cloud-starter-loadbalancer:latest' +}