From de2b197e8361f102760a84c25b4693dc3bccb612 Mon Sep 17 00:00:00 2001 From: can019 Date: Fri, 22 Aug 2025 13:57:12 +0900 Subject: [PATCH 1/2] feat: SecurityEndPoints set up --- .../icebang/config/SecurityConfig.java | 28 ------ .../config/security/SecurityConfig.java | 89 +++++++++++++++++++ 2 files changed, 89 insertions(+), 28 deletions(-) delete mode 100644 apps/user-service/src/main/java/com/gltkorea/icebang/config/SecurityConfig.java create mode 100644 apps/user-service/src/main/java/com/gltkorea/icebang/config/security/SecurityConfig.java diff --git a/apps/user-service/src/main/java/com/gltkorea/icebang/config/SecurityConfig.java b/apps/user-service/src/main/java/com/gltkorea/icebang/config/SecurityConfig.java deleted file mode 100644 index 75a5c6af..00000000 --- a/apps/user-service/src/main/java/com/gltkorea/icebang/config/SecurityConfig.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.gltkorea.icebang.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.Environment; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.NoOpPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; - -import lombok.RequiredArgsConstructor; - -@Configuration -@RequiredArgsConstructor -public class SecurityConfig { - private final Environment environment; - - @Bean - public PasswordEncoder bCryptPasswordEncoder() { - String[] activeProfiles = environment.getActiveProfiles(); - - for (String profile : activeProfiles) { - if ("dev".equals(profile) || "test".equals(profile)) { - return NoOpPasswordEncoder.getInstance(); - } - } - return new BCryptPasswordEncoder(); - } -} diff --git a/apps/user-service/src/main/java/com/gltkorea/icebang/config/security/SecurityConfig.java b/apps/user-service/src/main/java/com/gltkorea/icebang/config/security/SecurityConfig.java new file mode 100644 index 00000000..a7681a52 --- /dev/null +++ b/apps/user-service/src/main/java/com/gltkorea/icebang/config/security/SecurityConfig.java @@ -0,0 +1,89 @@ +package com.gltkorea.icebang.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.NoOpPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.web.SecurityFilterChain; + +import java.security.SecureRandom; + +@Configuration +@RequiredArgsConstructor +public class SecurityConfig { + private final Environment environment; + + @Bean + public SecureRandom secureRandom() { + return new SecureRandom(); + } + + @Bean + public PasswordEncoder bCryptPasswordEncoder() { + String[] activeProfiles = environment.getActiveProfiles(); + + for (String profile : activeProfiles) { + if ("dev".equals(profile) || "test".equals(profile)) { + return NoOpPasswordEncoder.getInstance(); + } + } + return new BCryptPasswordEncoder(); + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests(auth -> auth + .requestMatchers("/css/**", "/js/**", "/images/**", "/fonts/**", "/favicon.ico").permitAll() + + // 인증 관련 페이지들 허용 + .requestMatchers("/login", "/signup", "/reset-password").permitAll() + .requestMatchers("/api/v0/auth/**").permitAll() + + // 공개 페이지들 허용 + .requestMatchers("/", "/home").permitAll() + + // 에러 페이지 허용 + .requestMatchers("/error", "/error/**").permitAll() + + // 특정 보호가 필요한 경로만 인증 요구 + .requestMatchers("/mypage/**").authenticated() // 마이페이지 + .requestMatchers("/api/v0/user/**").authenticated() // 사용자 전용 API + .requestMatchers("/order/**").authenticated() // 주문 관련 + .requestMatchers("/api/v0/order/**").authenticated() // 주문 API + .requestMatchers("/cart/**").authenticated() // 장바구니 + .requestMatchers("/api/v0/cart/**").authenticated() // 장바구니 API + .requestMatchers("/review/write/**").authenticated() // 리뷰 작성 + .requestMatchers("/api/v0/review/write/**").authenticated() // 리뷰 작성 API + + // 나머지는 모두 허용 + .anyRequest().permitAll() + ) + .formLogin(form -> form + .loginPage("/login") // 로그인 페이지 +// .loginProcessingUrl("/api/v0/auth/login") // 로그인 처리 URL + .defaultSuccessUrl("/home", true) // 성공 시 이동 + .failureUrl("/login?error=true") // 실패 시 이동 + .permitAll() + ) + .logout(logout -> logout + .logoutUrl("/logout") + .logoutSuccessUrl("/login?logout=true") + .invalidateHttpSession(true) + .deleteCookies("JSESSIONID") + .permitAll() + ) + .sessionManagement(session -> session + .maximumSessions(1) // 동시 세션 1개만 + .maxSessionsPreventsLogin(false) // 새 로그인이 기존 세션 만료 + ) + .csrf(csrf -> csrf.disable()); + + return http.build(); + } +} From 7b60d5b9bcafb0eec298e248f7c6de4e8fe2e1d3 Mon Sep 17 00:00:00 2001 From: can019 Date: Fri, 22 Aug 2025 13:57:47 +0900 Subject: [PATCH 2/2] =?UTF-8?q?refactor:=20Enum=EC=9C=BC=EB=A1=9C=20reques?= =?UTF-8?q?t=20matcher=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/security/SecurityConfig.java | 79 ++++++------------- .../security/endpoints/SecurityEndpoints.java | 20 +++++ 2 files changed, 43 insertions(+), 56 deletions(-) create mode 100644 apps/user-service/src/main/java/com/gltkorea/icebang/config/security/endpoints/SecurityEndpoints.java diff --git a/apps/user-service/src/main/java/com/gltkorea/icebang/config/security/SecurityConfig.java b/apps/user-service/src/main/java/com/gltkorea/icebang/config/security/SecurityConfig.java index a7681a52..8a81b429 100644 --- a/apps/user-service/src/main/java/com/gltkorea/icebang/config/security/SecurityConfig.java +++ b/apps/user-service/src/main/java/com/gltkorea/icebang/config/security/SecurityConfig.java @@ -1,4 +1,6 @@ -package com.gltkorea.icebang.config; +package com.gltkorea.icebang.config.security; + +import java.security.SecureRandom; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -7,11 +9,11 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; - -import lombok.RequiredArgsConstructor; import org.springframework.security.web.SecurityFilterChain; -import java.security.SecureRandom; +import com.gltkorea.icebang.config.security.endpoints.SecurityEndpoints; + +import lombok.RequiredArgsConstructor; @Configuration @RequiredArgsConstructor @@ -23,6 +25,23 @@ public SecureRandom secureRandom() { return new SecureRandom(); } + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + return http.authorizeHttpRequests( + auth -> + auth.requestMatchers(SecurityEndpoints.PUBLIC.getMatchers()) + .permitAll() + .requestMatchers(SecurityEndpoints.ADMIN.getMatchers()) + .hasRole("ADMIN") + .requestMatchers(SecurityEndpoints.USER.getMatchers()) + .hasRole("USER") + .anyRequest() + .authenticated()) + .formLogin(form -> form.loginPage("/login").defaultSuccessUrl("/").permitAll()) + .logout(logout -> logout.logoutSuccessUrl("/login").permitAll()) + .build(); + } + @Bean public PasswordEncoder bCryptPasswordEncoder() { String[] activeProfiles = environment.getActiveProfiles(); @@ -34,56 +53,4 @@ public PasswordEncoder bCryptPasswordEncoder() { } return new BCryptPasswordEncoder(); } - - @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http - .authorizeHttpRequests(auth -> auth - .requestMatchers("/css/**", "/js/**", "/images/**", "/fonts/**", "/favicon.ico").permitAll() - - // 인증 관련 페이지들 허용 - .requestMatchers("/login", "/signup", "/reset-password").permitAll() - .requestMatchers("/api/v0/auth/**").permitAll() - - // 공개 페이지들 허용 - .requestMatchers("/", "/home").permitAll() - - // 에러 페이지 허용 - .requestMatchers("/error", "/error/**").permitAll() - - // 특정 보호가 필요한 경로만 인증 요구 - .requestMatchers("/mypage/**").authenticated() // 마이페이지 - .requestMatchers("/api/v0/user/**").authenticated() // 사용자 전용 API - .requestMatchers("/order/**").authenticated() // 주문 관련 - .requestMatchers("/api/v0/order/**").authenticated() // 주문 API - .requestMatchers("/cart/**").authenticated() // 장바구니 - .requestMatchers("/api/v0/cart/**").authenticated() // 장바구니 API - .requestMatchers("/review/write/**").authenticated() // 리뷰 작성 - .requestMatchers("/api/v0/review/write/**").authenticated() // 리뷰 작성 API - - // 나머지는 모두 허용 - .anyRequest().permitAll() - ) - .formLogin(form -> form - .loginPage("/login") // 로그인 페이지 -// .loginProcessingUrl("/api/v0/auth/login") // 로그인 처리 URL - .defaultSuccessUrl("/home", true) // 성공 시 이동 - .failureUrl("/login?error=true") // 실패 시 이동 - .permitAll() - ) - .logout(logout -> logout - .logoutUrl("/logout") - .logoutSuccessUrl("/login?logout=true") - .invalidateHttpSession(true) - .deleteCookies("JSESSIONID") - .permitAll() - ) - .sessionManagement(session -> session - .maximumSessions(1) // 동시 세션 1개만 - .maxSessionsPreventsLogin(false) // 새 로그인이 기존 세션 만료 - ) - .csrf(csrf -> csrf.disable()); - - return http.build(); - } } diff --git a/apps/user-service/src/main/java/com/gltkorea/icebang/config/security/endpoints/SecurityEndpoints.java b/apps/user-service/src/main/java/com/gltkorea/icebang/config/security/endpoints/SecurityEndpoints.java new file mode 100644 index 00000000..0a24605f --- /dev/null +++ b/apps/user-service/src/main/java/com/gltkorea/icebang/config/security/endpoints/SecurityEndpoints.java @@ -0,0 +1,20 @@ +package com.gltkorea.icebang.config.security.endpoints; + +public enum SecurityEndpoints { + PUBLIC( + "/", "/login", "/register", "/api/public/**", "/health", "/css/**", "/js/**", "/images/**"), + + ADMIN("/admin/**", "/api/admin/**", "/management/**", "/actuator/**"), + + USER("/user/**", "/api/user/**", "/profile/**", "/dashboard"); + + private final String[] patterns; + + SecurityEndpoints(String... patterns) { + this.patterns = patterns.clone(); + } + + public String[] getMatchers() { + return patterns.clone(); + } +}