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 153bc7c..347a1c1 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,12 +1,12 @@ package com.ktds.hi.member.config; import com.ktds.hi.member.service.JwtTokenProvider; -import com.ktds.hi.member.service.AuthService; 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; @@ -19,25 +19,31 @@ import java.io.IOException; * 요청 헤더의 JWT 토큰을 검증하고 인증 정보를 설정 */ @RequiredArgsConstructor +@Slf4j public class JwtAuthenticationFilter extends OncePerRequestFilter { - + private final JwtTokenProvider tokenProvider; - private final AuthService authService; - + @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, - FilterChain filterChain) throws ServletException, IOException { - - String token = resolveToken(request); - - if (StringUtils.hasText(token) && tokenProvider.validateToken(token)) { - Authentication authentication = tokenProvider.getAuthentication(token); - SecurityContextHolder.getContext().setAuthentication(authentication); + 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 토큰 추출 */ @@ -48,4 +54,4 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { } 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 791569f..3e935a0 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,7 +1,8 @@ package com.ktds.hi.member.config; + +import com.ktds.hi.member.config.JwtAuthenticationFilter; import com.ktds.hi.member.service.JwtTokenProvider; -import com.ktds.hi.member.service.AuthService; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; @@ -27,8 +28,7 @@ public class SecurityConfig { @Qualifier("memberJwtTokenProvider") private final JwtTokenProvider jwtTokenProvider; - private final AuthService authService; - + /** * 보안 필터 체인 설정 * JWT 인증 방식을 사용하고 세션은 무상태로 관리 @@ -40,16 +40,23 @@ public class SecurityConfig { .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(authz -> authz .requestMatchers("/api/auth/**", "/api/members/register").permitAll() - .requestMatchers("/swagger-ui/**", "/api-docs/**").permitAll() + .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll() .requestMatchers("/actuator/**").permitAll() .anyRequest().authenticated() ) - .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider, authService), - UsernamePasswordAuthenticationFilter.class); - + .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); + return http.build(); } - + + /** + * JWT 인증 필터 빈 + */ + @Bean + public JwtAuthenticationFilter jwtAuthenticationFilter() { + return new JwtAuthenticationFilter(jwtTokenProvider); + } + /** * 비밀번호 암호화 빈 */ @@ -57,7 +64,7 @@ public class SecurityConfig { public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } - + /** * 인증 매니저 빈 */ @@ -65,4 +72,45 @@ public class SecurityConfig { public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); } + + // @Qualifier("memberJwtTokenProvider") + // private final JwtTokenProvider jwtTokenProvider; + // private final AuthService authService; + // + // /** + // * 보안 필터 체인 설정 + // * JWT 인증 방식을 사용하고 세션은 무상태로 관리 + // */ + // @Bean + // public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + // http + // .csrf(csrf -> csrf.disable()) + // .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + // .authorizeHttpRequests(authz -> authz + // .requestMatchers("/api/auth/**", "/api/members/register").permitAll() + // .requestMatchers("/swagger-ui/**", "/api-docs/**").permitAll() + // .requestMatchers("/actuator/**").permitAll() + // .anyRequest().authenticated() + // ) + // .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider, authService), + // UsernamePasswordAuthenticationFilter.class); + // + // return http.build(); + // } + // + // /** + // * 비밀번호 암호화 빈 + // */ + // @Bean + // public PasswordEncoder passwordEncoder() { + // return new BCryptPasswordEncoder(); + // } + // + // /** + // * 인증 매니저 빈 + // */ + // @Bean + // public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { + // return config.getAuthenticationManager(); + // } } 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 index ac58680..f7d190f 100644 --- a/member/src/main/java/com/ktds/hi/member/service/JwtTokenProvider.java +++ b/member/src/main/java/com/ktds/hi/member/service/JwtTokenProvider.java @@ -17,98 +17,123 @@ import java.util.Date; * JWT 토큰 프로바이더 클래스 * JWT 토큰 생성, 검증, 파싱 기능을 제공 */ -@Component("memberJwtTokenProvider") // 기존: @Component +@Component("memberJwtTokenProvider") @Slf4j public class JwtTokenProvider { - + private final SecretKey secretKey; private final long accessTokenExpiration; private final long refreshTokenExpiration; - - public JwtTokenProvider(@Value("${jwt.secret}") String secret, - @Value("${jwt.access-token-expiration}") long accessTokenExpiration, - @Value("${jwt.refresh-token-expiration}") 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) - .setIssuedAt(now) - .setExpiration(expiration) - .signWith(secretKey) - .compact(); + .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()) - .setIssuedAt(now) - .setExpiration(expiration) - .signWith(secretKey) - .compact(); + .setSubject(memberId.toString()) + .claim("type", "refresh") + .setIssuedAt(now) + .setExpiration(expiration) + .signWith(secretKey) + .compact(); } - + /** * 토큰에서 인증 정보 추출 */ public Authentication getAuthentication(String token) { - Claims claims = parseClaims(token); - + Claims claims = getClaims(token); String memberId = claims.getSubject(); - String role = claims.get("role", String.class); - + String role = (String) claims.get("role"); + + SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + (role != null ? role : "USER")); + return new UsernamePasswordAuthenticationToken( - memberId, - null, - Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + role)) + memberId, + null, + Collections.singletonList(authority) ); } - - /** - * 토큰에서 회원 ID 추출 - */ - public Long getMemberIdFromToken(String token) { - Claims claims = parseClaims(token); - return Long.valueOf(claims.getSubject()); - } - + /** * 토큰 유효성 검증 */ public boolean validateToken(String token) { try { - parseClaims(token); + getClaims(token); return true; - } catch (JwtException | IllegalArgumentException e) { - log.warn("Invalid JWT token: {}", e.getMessage()); - return false; + } 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 추출 */ - private Claims parseClaims(String token) { - return Jwts.parser() - .setSigningKey(secretKey) - .build() - .parseClaimsJws(token) - .getBody(); + 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