diff --git a/src/main/java/backend/receipt/global/config/SecurityConfig.java b/src/main/java/backend/receipt/global/config/SecurityConfig.java index d95dcd4..4574f87 100644 --- a/src/main/java/backend/receipt/global/config/SecurityConfig.java +++ b/src/main/java/backend/receipt/global/config/SecurityConfig.java @@ -1,27 +1,55 @@ package backend.receipt.global.config; +import backend.receipt.member.security.JwtAuthenticationFilter; +import backend.receipt.member.service.JwtTokenProvider; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @Configuration +@RequiredArgsConstructor public class SecurityConfig { + private final JwtTokenProvider jwtTokenProvider; + + @Bean + public JwtAuthenticationFilter jwtAuthenticationFilter() { + return new JwtAuthenticationFilter(jwtTokenProvider); + } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) + .sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers( "/swagger-ui/**", "/v3/api-docs/**", - "/swagger-resources/**" + "/swagger-resources/**", + "/auth/login", + "/stores/**", + "/member/**", + "/reviews/**" ).permitAll() - .anyRequest().permitAll() - ); + .requestMatchers( + "/reviews", + "/auth/logout", + "/love/**", + "/points/**", + "reward/**", + "/total", + "history", + "/receipts/**" + ).authenticated() + .anyRequest().denyAll() + ) + .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); return http.build(); } -} +} \ No newline at end of file diff --git a/src/main/java/backend/receipt/global/config/SwaggerConfig.java b/src/main/java/backend/receipt/global/config/SwaggerConfig.java index b0cf418..1f0a21e 100644 --- a/src/main/java/backend/receipt/global/config/SwaggerConfig.java +++ b/src/main/java/backend/receipt/global/config/SwaggerConfig.java @@ -1,8 +1,11 @@ package backend.receipt.global.config; + import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -12,7 +15,15 @@ public class SwaggerConfig { @Bean public OpenAPI openAPI() { return new OpenAPI() - .components(new Components()) + .components(new Components() + .addSecuritySchemes("BearerAuth", + new SecurityScheme() + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") + )) + .addSecurityItem(new SecurityRequirement() + .addList("BearerAuth")) .info(apiInfo()); } @@ -22,4 +33,5 @@ private Info apiInfo() { .description("Receipt API 문서") .version("v1"); } + } \ No newline at end of file diff --git a/src/main/java/backend/receipt/member/security/JwtAuthenticationFilter.java b/src/main/java/backend/receipt/member/security/JwtAuthenticationFilter.java new file mode 100644 index 0000000..a27e957 --- /dev/null +++ b/src/main/java/backend/receipt/member/security/JwtAuthenticationFilter.java @@ -0,0 +1,50 @@ +package backend.receipt.member.security; + +import backend.receipt.member.service.JwtTokenProvider; +import io.jsonwebtoken.JwtException; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Component +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtTokenProvider jwtTokenProvider; + + @Autowired + public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) { + this.jwtTokenProvider = jwtTokenProvider; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) + throws ServletException, IOException { + + String header = request.getHeader("Authorization"); + + if (header != null && header.startsWith("Bearer ")) { + String token = header.substring(7); + try { + if (jwtTokenProvider.validateToken(token)) { + Authentication authentication = jwtTokenProvider.getAuthentication(token); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } catch (JwtException e) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return; + } + } + + filterChain.doFilter(request, response); + } +} \ No newline at end of file diff --git a/src/main/java/backend/receipt/member/service/JwtTokenProvider.java b/src/main/java/backend/receipt/member/service/JwtTokenProvider.java index 7abfb9e..de377d5 100644 --- a/src/main/java/backend/receipt/member/service/JwtTokenProvider.java +++ b/src/main/java/backend/receipt/member/service/JwtTokenProvider.java @@ -4,9 +4,12 @@ import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.stereotype.Component; import java.security.Key; +import java.util.Collections; import java.util.Date; @Component @@ -47,5 +50,19 @@ public boolean validateToken(String token) { throw new JwtException("유효하지 않은 JWT 토큰입니다."); } } + public Authentication getAuthentication(String token) { + Claims claims = getClaims(token); + String email = claims.getSubject(); + return new UsernamePasswordAuthenticationToken(email, "", Collections.emptyList()); + } + + private Claims getClaims(String token) { + return Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token) + .getBody(); + } + } diff --git a/src/main/java/backend/receipt/store/service/StoreService.java b/src/main/java/backend/receipt/store/service/StoreService.java index ed0d191..cde4349 100644 --- a/src/main/java/backend/receipt/store/service/StoreService.java +++ b/src/main/java/backend/receipt/store/service/StoreService.java @@ -5,6 +5,7 @@ import backend.receipt.store.dto.response.KakaoPlaceDetail; import backend.receipt.store.dto.response.StoreResponse; import backend.receipt.store.repository.StoreRepository; +import jakarta.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Value; import org.json.JSONArray; import org.json.JSONObject; @@ -113,8 +114,30 @@ public StoreResponse getStoreDetail(Long storeId) { return response; } - - + //초기데이터 + @PostConstruct + public void initStoresOnStartup() { + try { + StoreRequest store1 = new StoreRequest(); + store1.setQuery("베이커리"); + store1.setName("파리바게뜨 시흥장현점"); + searchAndSaveStoreByName(store1); + + StoreRequest store2 = new StoreRequest(); + store2.setQuery("카페"); + store2.setName("일리카페 시흥시청점"); + searchAndSaveStoreByName(store2); + + StoreRequest store3 = new StoreRequest(); + store3.setQuery("편의점"); + store3.setName("CU 시흥시청점"); + searchAndSaveStoreByName(store3); + + System.out.println("[초기 가맹점 등록 완료]"); + } catch (Exception e) { + System.err.println("[초기 가맹점 등록 실패] " + e.getMessage()); + } + } // kakao api 관련 // kakao api 호출 private String fetchKakaoApiResponse(String query, double x, double y, int radius) {