SecurityAutoConfigurationRegistrar.java

package sprout.security.autoconfiguration;

import sprout.beans.BeanDefinition;
import sprout.beans.ConstructorBeanDefinition;
import sprout.beans.InfrastructureBean;
import sprout.beans.annotation.Component;
import sprout.beans.processor.BeanDefinitionRegistrar;
import sprout.config.AppConfig;
import sprout.security.authentication.*;
import sprout.security.authentication.exception.UsernameNotFoundException;
import sprout.security.authentication.password.BCryptPasswordEncoder;
import sprout.security.authentication.password.PasswordEncoder;
import sprout.security.autoconfiguration.annotation.EnableSproutSecurity;
import sprout.security.core.UserDetailsService;
import sprout.security.filter.AuthenticationFilter;
import sprout.security.web.util.matcher.AntPathRequestMatcher;
import sprout.security.web.util.matcher.RequestMatcher;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Component
public class SecurityAutoConfigurationRegistrar implements BeanDefinitionRegistrar {
    @Override
    public Collection<BeanDefinition> registerAdditionalBeanDefinitions(Collection<BeanDefinition> existingDefs) throws NoSuchMethodException {
        List<BeanDefinition> additionalDefs = new ArrayList<>();
        boolean hasEnabledSproutSecurity = false;
        EnableSproutSecurity enableSproutSecurity = null;
        for (BeanDefinition def : existingDefs) {
            if (def.getType().isAnnotationPresent(EnableSproutSecurity.class)) {
                hasEnabledSproutSecurity = true;
                enableSproutSecurity = def.getType().getAnnotation(EnableSproutSecurity.class);
            }
        }
        if (!hasEnabledSproutSecurity || (enableSproutSecurity != null && enableSproutSecurity.defaultSecurityDisabled())) {
            System.out.println("No @EnableSproutSecurity found. Registering default security beans.");

            if (existingDefs.stream().noneMatch(def -> PasswordEncoder.class.isAssignableFrom(def.getType()))) {
                Constructor<?> constructor = BCryptPasswordEncoder.class.getConstructor();
                Class<?>[] constructorArgumentsTypes = constructor.getParameterTypes();
                additionalDefs.add(new ConstructorBeanDefinition("passwordEncoder", BCryptPasswordEncoder.class, constructor, constructorArgumentsTypes));
            }

            if (existingDefs.stream().noneMatch(def -> UserDetailsService.class.isAssignableFrom(def.getType()))) {
                Constructor<?> constructor = DefaultUserDetailsService.class.getConstructor(AppConfig.class, PasswordEncoder.class);
                Class<?>[] constructorArgumentsTypes = constructor.getParameterTypes();
                additionalDefs.add(new ConstructorBeanDefinition("userDetailsService", DefaultUserDetailsService.class, constructor, constructorArgumentsTypes));
            }

            if (existingDefs.stream().noneMatch(def -> DaoAuthenticationProvider.class.isAssignableFrom(def.getType()))) {
                Constructor<?> constructor = DaoAuthenticationProvider.class.getConstructor(UserDetailsService.class, PasswordEncoder.class);
                Class<?>[] constructorArgumentsTypes = constructor.getParameterTypes();
                additionalDefs.add(new ConstructorBeanDefinition("daoAuthenticationProvider", DaoAuthenticationProvider.class, constructor, constructorArgumentsTypes));
            }

            if (existingDefs.stream().noneMatch(def -> RequestMatcher.class.isAssignableFrom(def.getType()))) {
                // '/' 경로에 대한 RequestMatcher 기본값
                Constructor<?> constructor = AntPathRequestMatcher.class.getConstructor(String.class);
                Class<?>[] constructorArgumentsTypes = constructor.getParameterTypes();
                Object[] constructorArguments = new Object[]{"/login"}; // <-- 실제 인자 값
                additionalDefs.add(new ConstructorBeanDefinition("defaultRequestMatcher", AntPathRequestMatcher.class, constructor, constructorArgumentsTypes, constructorArguments));
            }

            if (existingDefs.stream().noneMatch(def -> AuthenticationManager.class.isAssignableFrom(def.getType()))) {
                // List<AuthenticationProvider>는 컨테이너가 자동으로 찾아 주입할 것을 기대
                // 이 생성자는 List<AuthenticationProvider>만 필요로 함
                Constructor<?> constructor = ProviderManager.class.getConstructor(List.class);
                Class<?>[] constructorArgumentsTypes = constructor.getParameterTypes();
                // 여기에 실제 인자 값을 넘기지 않으면, 컨테이너가 의존성 주입 로직을 통해 List<AuthenticationProvider>를 채워줄 것임
                additionalDefs.add(new ConstructorBeanDefinition("authenticationManager", ProviderManager.class, constructor, constructorArgumentsTypes));
            }

            if (existingDefs.stream().noneMatch(def -> AuthenticationFilter.class.isAssignableFrom(def.getType()))) {
                Constructor<?> constructor = AuthenticationFilter.class.getConstructor(List.class, AuthenticationManager.class);
                Class<?>[] constructorArgumentsTypes = constructor.getParameterTypes();
                additionalDefs.add(new ConstructorBeanDefinition("authenticationFilter", AuthenticationFilter.class, constructor, constructorArgumentsTypes));
            }
        }
        return additionalDefs;
    }
}