package com.takensoft.common.config; import com.takensoft.cms.accesCtrl.service.AccesCtrlService; import com.takensoft.cms.mber.service.LgnHstryService; import com.takensoft.cms.mber.service.RefreshTokenService; import com.takensoft.common.filter.AccesFilter; import com.takensoft.common.filter.JWTFilter; import com.takensoft.common.filter.LoginFilter; import com.takensoft.common.util.CommonUtils; import com.takensoft.common.exception.CustomAccessDenieHandler; import com.takensoft.common.exception.CustomAuthenticationEntryPoint; import com.takensoft.common.util.JWTUtil; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; 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.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import jakarta.servlet.http.HttpServletRequest; import java.util.Collections; /** * @author takensoft * @since 2024.04.01 * * 스프링 시큐리티 설정 */ @Configuration @EnableWebSecurity public class SecurityConfig { // AuthenticationManager가 인자로 받을 AuthenticationConfiguration 객체 생성자 주입 private final AuthenticationConfiguration authenticationConfiguration; private final JWTUtil jwtUtil; private final RefreshTokenService refreshTokenService; private final LgnHstryService lgnHstryService; private final AccesCtrlService accesCtrlService; private final CustomAuthenticationEntryPoint authenticationEntryPoint; private final CustomAccessDenieHandler accessDenieHandler; private final CommonUtils commonUtils; private final CommonConfig commonConfig; private static String FRONT_URL; // 프론트 접근 허용 URL private static long JWT_ACCESSTIME; // access 토큰 유지 시간 private static long JWT_REFRESHTIME; // refresh 토큰 유지 시간 private static int COOKIE_TIME; // 쿠키 유지 시간 public SecurityConfig(AuthenticationConfiguration authenticationConfiguration, JWTUtil jwtUtil, RefreshTokenService refreshTokenService, AccesCtrlService accesCtrlService, CommonConfig commonConfig, LgnHstryService lgnHstryService, CustomAuthenticationEntryPoint authenticationEntryPoint, CustomAccessDenieHandler accessDenieHandler, CommonUtils commonUtils, @Value("${front.url}")String fUrl,@Value("${jwt.accessTime}")long aTime, @Value("${jwt.refreshTime}")long rTime, @Value("${cookie.time}")int ctime) { this.authenticationConfiguration = authenticationConfiguration; this.refreshTokenService = refreshTokenService; this.lgnHstryService = lgnHstryService; this.accesCtrlService = accesCtrlService; this.authenticationEntryPoint = authenticationEntryPoint; this.accessDenieHandler = accessDenieHandler; this.jwtUtil = jwtUtil; this.commonUtils = commonUtils; this.commonConfig = commonConfig; this.FRONT_URL = fUrl; this.JWT_ACCESSTIME = aTime; this.JWT_REFRESHTIME = rTime; this.COOKIE_TIME = ctime; } // AuthenticationManager Bean 등록 @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { return configuration.getAuthenticationManager(); } // 해시 암호화 @Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.cors((cors) -> cors .configurationSource(new CorsConfigurationSource() { @Override public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Collections.singletonList(FRONT_URL)); // 허용할 프론트 포트 포함 경로 입력 configuration.setAllowedMethods(Collections.singletonList("*")); // 허용할 메소드(GET, POST, PUT 등) configuration.setAllowedHeaders(Collections.singletonList("*")); // 허용할 헤드 configuration.setAllowCredentials(true); // 프론트에서 credentials 설정하면 true configuration.setMaxAge(3600L); // 허용을 물고 있을 시간 configuration.setExposedHeaders(Collections.singletonList("Authorization")); // 서버에서 JWT를 Authorization에 담아 보내기 위해 허용을 함 return configuration; } }) ); // csrf disable http.csrf((auth) -> auth.disable()); // formLogin disable http.formLogin((auth) -> auth.disable()); // http basic 인증 방식 disable http.httpBasic((auth) -> auth.disable()); // 세션 설정 http.sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); http.exceptionHandling((exception) -> exception .authenticationEntryPoint(authenticationEntryPoint) // 접근 권한이 없는 경우에 호출 .accessDeniedHandler(accessDenieHandler) // 인증되지 않은 상태로 접근 했을 때 호출 ); http.authorizeHttpRequests((auth) -> auth .requestMatchers("/", "/mbr/**", "/company/**", "/refresh/**", "/sys/**").permitAll() // 회원관련, 시스템 제공 관련, 기업용페이지는 누구나 접근 가능 .requestMatchers("/admin/**").hasRole("ADMIN") // 관리자 페이지는 ADMIN 권한을 가진 사용자만 접근 가능 .requestMatchers("/editFileUpload/**", "/fileUpload/**").permitAll() // 에디터 파일 업로드 .anyRequest().authenticated() // 그 외에는 로그인한 사용자만 접근 가능 // .anyRequest().permitAll() // 모든 사용자 접근 가능 ); http.addFilterBefore(new JWTFilter(jwtUtil, commonConfig), LoginFilter.class); // 토큰 검증 필터 http.addFilterBefore(new AccesFilter(accesCtrlService, commonUtils, commonConfig), JWTFilter.class); // 아이피 검증 http.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil, refreshTokenService, lgnHstryService, commonUtils, commonConfig, JWT_ACCESSTIME, JWT_REFRESHTIME, COOKIE_TIME), UsernamePasswordAuthenticationFilter.class); // 로그인 필터 return http.build(); } }