package com.takensoft.common.config; import com.takensoft.cms.accesCtrl.service.AccesCtrlService; import com.takensoft.cms.mber.service.LgnHstryService; import com.takensoft.cms.token.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.HttpRequestUtil; 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.data.redis.core.RedisTemplate; 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 2025.01.22 * @modification * since | author | description * 2025.01.22 | takensoft | 최초 등록 * * Spring Security 설정을 위한 Config */ @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 HttpRequestUtil httpRequestUtil; private final AppConfig appConfig; private final RedisConfig redisConfig; private static String FRONT_URL; // 프론트 접근 허용 URL private static long JWT_ACCESSTIME; // access 토큰 유지 시간 private static long JWT_REFRESHTIME; // refresh 토큰 유지 시간 private static int COOKIE_TIME; // 쿠키 유지 시간 private final RedisTemplate redisTemplate; /** * @param fUrl - 프론트엔드 URL (application.yml에서 값을 읽어 옴) * @param aTime - JWT 접근 토큰 유효 시간 (application.yml에서 값을 읽어 옴) * @param rTime - JWT 리프레시 토큰 유효 시간 (application.yml에서 값을 읽어 옴) * @param ctime - 쿠키 유효 시간 (application.yml에서 값을 읽어 옴) * @param authenticationConfiguration - 인증 구성 객체 * @param authenticationEntryPoint - 인증 실패 시 처리 엔트리 포인트 * @param accessDenieHandler - 접근 거부 처리 핸들러 * @param jwtUtil - JWT 유틸리티 객체 * @param redisTemplate * * SecurityConfig 생성자 */ public SecurityConfig(AuthenticationConfiguration authenticationConfiguration, JWTUtil jwtUtil, RefreshTokenService refreshTokenService, AccesCtrlService accesCtrlService, AppConfig appConfig, RedisConfig redisConfig, LgnHstryService lgnHstryService, CustomAuthenticationEntryPoint authenticationEntryPoint, CustomAccessDenieHandler accessDenieHandler, HttpRequestUtil httpRequestUtil, @Value("${front.url}")String fUrl, @Value("${jwt.accessTime}")long aTime, @Value("${jwt.refreshTime}")long rTime, @Value("${cookie.time}")int ctime, RedisTemplate redisTemplate) { this.authenticationConfiguration = authenticationConfiguration; this.refreshTokenService = refreshTokenService; this.lgnHstryService = lgnHstryService; this.accesCtrlService = accesCtrlService; this.authenticationEntryPoint = authenticationEntryPoint; this.accessDenieHandler = accessDenieHandler; this.jwtUtil = jwtUtil; this.httpRequestUtil = httpRequestUtil; this.appConfig = appConfig; this.redisConfig = redisConfig; this.FRONT_URL = fUrl; this.JWT_ACCESSTIME = aTime; this.JWT_REFRESHTIME = rTime; this.COOKIE_TIME = ctime; this.redisTemplate = redisTemplate; } /** * @param configuration - 인증 구성 객체 * @return AuthenticationManager 객체 * @throws Exception - 인증 관리자 초기화 실패 시 * * AuthenticationManager Bean 등록 */ @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { return configuration.getAuthenticationManager(); } /** * @return BCryptPasswordEncoder 객체 * * 비밀번호 해시 암호화를 위한 Bean 등록 */ @Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } /** * @param http - HTTP 보안 설정 객체 * @return SecurityFilterChain 객체 * @throws Exception - 보안 필터 체인 초기화 실패 시 * * Spring Security 필터 체인 설정 */ @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() // 모든 사용자 접근 가능 ); // 로그인 방식에 따라 필터 적용 (JWT vs 세션) /* if ("SESSION".equals(authConfig.getLoginType())) { http.addFilterBefore(sessionAuthFilter, LoginFilter.class); // 세션 인증 필터 추가 } else { http.addFilterBefore(new JWTFilter(jwtUtil, commonConfig, redisConfig, redisTemplate), LoginFilter.class); // JWT 인증 필터 추가 }*/ http.addFilterBefore(new JWTFilter(jwtUtil, appConfig, redisConfig, redisTemplate), LoginFilter.class); // 토큰 검증 필터 http.addFilterBefore(new AccesFilter(accesCtrlService, httpRequestUtil, appConfig), JWTFilter.class); // 아이피 검증 http.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil, refreshTokenService, lgnHstryService, httpRequestUtil, appConfig,redisConfig, JWT_ACCESSTIME, JWT_REFRESHTIME, COOKIE_TIME, redisTemplate), UsernamePasswordAuthenticationFilter.class); // 로그인 필터 return http.build(); } }