package com.takensoft.common.config; import com.takensoft.cms.accesCtrl.service.AccesCtrlService; import com.takensoft.cms.cntxtPth.service.CntxtPthService; import com.takensoft.cms.loginPolicy.service.Email2ndAuthService; import com.takensoft.cms.loginPolicy.service.LoginModeService; import com.takensoft.cms.loginPolicy.service.LoginPolicyService; import com.takensoft.cms.mber.service.UnifiedLoginService; import com.takensoft.common.filter.*; 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 com.takensoft.common.util.LoginUtil; import com.takensoft.common.verify.service.Impl.EmailServiceImpl; import com.takensoft.common.oauth.service.Impl.CustomOAuth2UserServiceImpl; import com.takensoft.common.oauth.handler.OAuth2AuthenticationSuccessHandler; import com.takensoft.common.oauth.handler.OAuth2AuthenticationFailureHandler; import org.springframework.beans.factory.annotation.Autowired; 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.security.web.context.SecurityContextPersistenceFilter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import jakarta.servlet.http.HttpServletRequest; import java.util.Collections; import java.util.List; /** * @author takensoft * @since 2025.01.22 * @modification * since | author | description * 2025.01.22 | takensoft | 최초 등록 * 2025.05.22 | takensoft | OAuth2 로그인 추가 * 2025.05.26 | 하석형 | 로그인 유틸 추가 * 2025.05.29 | takensoft | 통합 로그인 시스템 적용 * * Spring Security 설정을 위한 Config - 통합 로그인 시스템 적용 */ @Configuration @EnableWebSecurity public class SecurityConfig { // AuthenticationManager가 인자로 받을 AuthenticationConfiguration 객체 생성자 주입 private final AuthenticationConfiguration authenticationConfiguration; private final JWTUtil jwtUtil; private final CntxtPthService cntxtPthService; private final AccesCtrlService accesCtrlService; private final CustomAuthenticationEntryPoint authenticationEntryPoint; private final CustomAccessDenieHandler accessDenieHandler; private final HttpRequestUtil httpRequestUtil; private final AppConfig appConfig; private final LoginModeService loginModeService; private final LoginPolicyService loginPolicyService; private final EmailServiceImpl emailServiceImpl; private final LoginUtil loginUtil; private final Email2ndAuthService email2ndAuth; private final UnifiedLoginService unifiedLoginService; @Autowired private CustomOAuth2UserServiceImpl customOAuth2UserServiceImpl; @Autowired private OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler; @Autowired private OAuth2AuthenticationFailureHandler oAuth2AuthenticationFailureHandler; private static String FRONT_URL; // 프론트 접근 허용 URL private final RedisTemplate redisTemplate; /** * SecurityConfig 생성자 */ public SecurityConfig(AuthenticationConfiguration authenticationConfiguration, JWTUtil jwtUtil, CntxtPthService cntxtPthService, AccesCtrlService accesCtrlService, AppConfig appConfig, CustomAuthenticationEntryPoint authenticationEntryPoint, CustomAccessDenieHandler accessDenieHandler, HttpRequestUtil httpRequestUtil, LoginModeService loginModeService, LoginPolicyService loginPolicyService, EmailServiceImpl emailServiceImpl, @Value("${front.url}") String fUrl, RedisTemplate redisTemplate, LoginUtil loginUtil, Email2ndAuthService email2ndAuth, UnifiedLoginService unifiedLoginService) { this.authenticationConfiguration = authenticationConfiguration; this.cntxtPthService = cntxtPthService; this.accesCtrlService = accesCtrlService; this.authenticationEntryPoint = authenticationEntryPoint; this.accessDenieHandler = accessDenieHandler; this.jwtUtil = jwtUtil; this.httpRequestUtil = httpRequestUtil; this.appConfig = appConfig; this.loginModeService = loginModeService; this.loginPolicyService = loginPolicyService; this.FRONT_URL = fUrl; this.redisTemplate = redisTemplate; this.emailServiceImpl = emailServiceImpl; this.loginUtil = loginUtil; this.email2ndAuth = email2ndAuth; this.unifiedLoginService = unifiedLoginService; } /** * @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에 담아 보내기 위해 허용을 함 configuration.setExposedHeaders(List.of("Authorization", "loginMode", "policyMode")); 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/**", "/refresh/**", "/sys/**", "/editFileUpload/**", "/fileUpload/**", "/oauth2/**", "/login/oauth2/**").permitAll() // 회원, 토큰, 시스템 제공, 파일, OAuth2 접근 모두 허용 .requestMatchers("/admin/**").hasRole("ADMIN") // 관리자 페이지는 ADMIN 권한을 가진 사용자만 접근 가능 .anyRequest().authenticated() // 그 외에는 로그인한 사용자만 접근 가능 ); // OAuth2 로그인 설정 http.oauth2Login(oauth2 -> oauth2 .authorizationEndpoint(authorization -> authorization .baseUri("/oauth2/authorization") ) .redirectionEndpoint(redirection -> redirection .baseUri("/login/oauth2/code/*") ) .userInfoEndpoint(userInfo -> userInfo .userService(customOAuth2UserServiceImpl) ) .successHandler(oAuth2AuthenticationSuccessHandler) .failureHandler(oAuth2AuthenticationFailureHandler) ); // Context Path 검증 필터 http.addFilterBefore(new ContextPathFilter(cntxtPthService), SecurityContextPersistenceFilter.class); // 접근(아이피) 검증 필터 http.addFilterBefore(new AccesFilter(accesCtrlService, httpRequestUtil, appConfig), UsernamePasswordAuthenticationFilter.class); // 로그인 필터 http.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), emailServiceImpl, loginUtil, email2ndAuth), UsernamePasswordAuthenticationFilter.class); // JWT 토큰 검증 필터 http.addFilterAfter(new JWTFilter(jwtUtil, appConfig, loginModeService, loginPolicyService, redisTemplate), UsernamePasswordAuthenticationFilter.class); return http.build(); } }