diff --git a/src/main/java/com/diareat/diareat/auth/component/JwtAuthFilter.java b/src/main/java/com/diareat/diareat/auth/component/JwtAuthFilter.java new file mode 100644 index 0000000..e90b21a --- /dev/null +++ b/src/main/java/com/diareat/diareat/auth/component/JwtAuthFilter.java @@ -0,0 +1,36 @@ +package com.diareat.diareat.auth.component; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.GenericFilterBean; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +@RequiredArgsConstructor +public class JwtAuthFilter extends GenericFilterBean { + + private final JwtTokenProvider jwtTokenProvider; + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + // 헤더에서 토큰 받아오기 + String token = jwtTokenProvider.resolveToken((HttpServletRequest) request); + + // 토큰이 유효하다면 + if (token != null && jwtTokenProvider.validateToken(token)) { + // 토큰으로부터 유저 정보를 받아 + Authentication authentication = jwtTokenProvider.getAuthentication(token); + // SecurityContext 에 객체 저장 + SecurityContextHolder.getContext().setAuthentication(authentication); + } + + // 다음 Filter 실행 + chain.doFilter(request, response); + } +} diff --git a/src/main/java/com/diareat/diareat/auth/component/JwtTokenProvider.java b/src/main/java/com/diareat/diareat/auth/component/JwtTokenProvider.java new file mode 100644 index 0000000..76ff826 --- /dev/null +++ b/src/main/java/com/diareat/diareat/auth/component/JwtTokenProvider.java @@ -0,0 +1,72 @@ +package com.diareat.diareat.auth.component; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.servlet.http.HttpServletRequest; +import java.util.Base64; +import java.util.Date; + +@RequiredArgsConstructor +@Component +public class JwtTokenProvider { + + @Value("${jwt.secret}") + private String secretKey; + + private final UserDetailsService userDetailsService; + + // 객체 초기화, secretKey를 Base64로 인코딩 + @PostConstruct + protected void init() { + secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes()); + } + + // 토큰 생성 + public String createToken(String userPk) { + Claims claims = Jwts.claims().setSubject(userPk); // JWT payload 에 저장되는 정보단위 + Date now = new Date(); + return Jwts.builder() + .setClaims(claims) // 정보 저장 + .setIssuedAt(now) // 토큰 발행 시간 정보 + .setExpiration(new Date(now.getTime() + (30 * 60 * 1000L))) // 토큰 유효시각 설정 (30분) + .signWith(SignatureAlgorithm.HS256, secretKey) // 암호화 알고리즘과, secret 값 + .compact(); + } + + // 인증 정보 조회 + public Authentication getAuthentication(String token) { + UserDetails userDetails = userDetailsService.loadUserByUsername(this.getUserPk(token)); + return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); + } + + // 토큰에서 회원 정보 추출 + public String getUserPk(String token) { + return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject(); + } + + // 토큰 유효성, 만료일자 확인 + public boolean validateToken(String jwtToken) { + try { + Jws claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwtToken); + return !claims.getBody().getExpiration().before(new Date()); + } catch (Exception e) { + return false; + } + } + + // Request의 Header에서 token 값 가져오기 + public String resolveToken(HttpServletRequest request) { + return request.getHeader("X-AUTH-TOKEN"); + } +} diff --git a/src/main/java/com/diareat/diareat/config/WebSecurityConfig.java b/src/main/java/com/diareat/diareat/config/WebSecurityConfig.java new file mode 100644 index 0000000..401c31b --- /dev/null +++ b/src/main/java/com/diareat/diareat/config/WebSecurityConfig.java @@ -0,0 +1,36 @@ +package com.diareat.diareat.config; + +import com.diareat.diareat.auth.component.JwtAuthFilter; +import com.diareat.diareat.auth.component.JwtTokenProvider; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@RequiredArgsConstructor +@EnableWebSecurity +public class WebSecurityConfig { + + private final JwtTokenProvider jwtTokenProvider; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .csrf().disable() + //세션 사용 안함 + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + //URL 관리 + .authorizeRequests() + .antMatchers("/api/auth/**").permitAll() // 이 API 는 누구나 접근 가능 + .anyRequest().authenticated() + .and() + // JwtAuthenticationFilter를 먼저 적용 + .addFilterBefore(new JwtAuthFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } +}