Spring MVC&带有JavaConfig的Security 3.2 - WebApplicationContext被初始化,销毁,再次初始化

时间:2014-03-14 14:01:56

标签: java spring spring-mvc spring-security spring-java-config

本周我将整个webapp从xml config切换到JavaConfig。我注意到了一些奇怪的行为。我正在使用集成了tomcat的Netbeans。当我部署我的应用程序时,我会在日志中注意到这一点:

Mar 13, 2014 7:55:48 AM org.apache.catalina.core.ApplicationContext log
INFO: Spring WebApplicationInitializers detected on classpath: [x.configuration.SecurityInitializer@703f4126, x.configuration.WebAppInitializer@48ce5f5c]
Mar 13, 2014 7:55:49 AM org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring root WebApplicationContext
Mar 13, 2014 7:56:20 AM org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring FrameworkServlet 'dispatcher'
Mar 13, 2014 7:56:55 AM org.apache.catalina.core.ApplicationContext log
INFO: Destroying Spring FrameworkServlet 'dispatcher'
Mar 13, 2014 7:56:55 AM org.apache.catalina.core.ApplicationContext log
INFO: Closing Spring root WebApplicationContext
Mar 13, 2014 7:57:04 AM org.apache.catalina.core.ApplicationContext log
INFO: Spring WebApplicationInitializers detected on classpath: [x.configuration.SecurityInitializer@5379ce53, x.configuration.WebAppInitializer@61d8d5f5]
Mar 13, 2014 7:57:05 AM org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring root WebApplicationContext
Mar 13, 2014 7:57:33 AM org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring FrameworkServlet 'dispatcher'

还有其他东西运行两次......我有一个类在我的调度程序servlet上下文中侦听ContextRefreshedEvents。当我调试Web应用程序时,我看到日志语句就像正在执行此代码一样,但它从未遇到过我设置的任何断点......第一次。在那个神秘的上下文似乎完全初始化然后显然被破坏之后,它再次被初始化。这一次,我的断点被击中,我可以逐步完成我的代码。我 THINK 根据我读过的所有内容配置了所有配置文件,并且我的root / dispatcher组件扫描互斥而不扫描相同的软件包。

这已成为一个更大的问题,因为现在我正在尝试设置BCryptPasswordEncoder。我有类似于上面描述的问题 - 我不再能够调试或查看来自CustomAuthenticationProvider的日志语句,尽管我来自CustomUserDetailsS​​ervice。我开始认为它正在使用我的CustomAuthenticationProvider的缓存实例来自已创建的初始ghost上下文或其他东西。任何帮助解决这个问题将不胜感激。

我的软件包设置得非常扁平,即:x.controllers,x.services,x.domain,x.dao,x.configuration等,所以我必须使用一些excludeFilters来确保正确的上下文正在扫描正确的组件。无论如何,这是我的配置:

AbstractAnnotationConfigDispatcherServletInitializer:

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{ SecurityConfig.class, ServiceConfig.class, PersistenceConfig.class, Log4jConfig.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMvcConfig.class, Initializer.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{
            "/rest/*",
            "/index.html",
            "/login.html",
            "/admin.html",
            "/index/*",
            "/login/*",
            "/admin/*"
        };
    }

    @Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("UTF-8");
        characterEncodingFilter.setForceEncoding(true);

        OpenEntityManagerInViewFilter openEntityManagerInViewFilter = new OpenEntityManagerInViewFilter();
        openEntityManagerInViewFilter.setBeanName("openEntityManagerInViewFilter");
        openEntityManagerInViewFilter.setPersistenceUnitName("HSQL");

        return new javax.servlet.Filter[]{characterEncodingFilter, openEntityManagerInViewFilter};
    }
}

服务配置:

@Configuration
@ComponentScan(basePackages = "x",
        excludeFilters = {
            @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class),
            @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Configuration.class)
        }
)
public class ServiceConfig {

    @Bean
    public DefaultAnnotationHandlerMapping defaultAnnotationHandlerMapping() {
        DefaultAnnotationHandlerMapping handlerMapping = new DefaultAnnotationHandlerMapping();
        handlerMapping.setAlwaysUseFullPath(true);
        handlerMapping.setDetectHandlersInAncestorContexts(true);
        return handlerMapping;
    }
}

安全初始化程序:

@Order(1)
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {}

安全配置:

@Configuration
@EnableWebMvcSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailsService userDetailsService;
    @Autowired
    private CustomAuthenticationProvider customAuthenticationProvider;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        AuthenticationProvider rememberMeAuthenticationProvider = rememberMeAuthenticationProvider();
        TokenBasedRememberMeServices tokenBasedRememberMeServices = tokenBasedRememberMeServices();

        List<AuthenticationProvider> authenticationProviders = new ArrayList<AuthenticationProvider>(2);
        authenticationProviders.add(rememberMeAuthenticationProvider);
        authenticationProviders.add(customAuthenticationProvider);
        AuthenticationManager authenticationManager = authenticationManager(authenticationProviders);

        http
                .csrf().disable()
                .headers().disable()
                .addFilter(new RememberMeAuthenticationFilter(authenticationManager, tokenBasedRememberMeServices))
                .rememberMe().rememberMeServices(tokenBasedRememberMeServices)
                .and()
                .authorizeRequests()
                .antMatchers("/js/**", "/css/**", "/img/**", "/login", "/processLogin").permitAll()
                .antMatchers("/index.jsp", "/index.html", "/index").hasRole("USER")
                .antMatchers("/admin", "/admin.html", "/admin.jsp", "/js/saic/jswe/admin/**").hasRole("ADMIN")
                .and()
                .formLogin().loginProcessingUrl("/processLogin").loginPage("/login").permitAll()
                .and().exceptionHandling().accessDeniedPage("/login").and()
                .logout().permitAll();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/js/**", "/css/**", "/img/**");
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(List<AuthenticationProvider> authenticationProviders) {
        return new ProviderManager(authenticationProviders);
    }

    @Bean
    public TokenBasedRememberMeServices tokenBasedRememberMeServices() {
        return new TokenBasedRememberMeServices("testKey", userDetailsService);
    }

    @Bean
    public AuthenticationProvider rememberMeAuthenticationProvider() {
        return new org.springframework.security.authentication.RememberMeAuthenticationProvider("testKey");
    }

    protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }
}

MVC配置:

@Configuration
@EnableWebMvc
@EnableTransactionManagement
@ComponentScan(basePackages = "x.controllers")
public class SpringMvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(mappingJacksonHttpMessageConverter());
        converters.add(marshallingMessageConverter());
        super.configureMessageConverters(converters);
    }

    @Bean
    public InternalResourceViewResolver setupViewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setViewClass(JstlView.class);
        viewResolver.setPrefix("/WEB-INF/jsp/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }

    @Bean
    public JacksonAnnotationIntrospector jacksonAnnotationIntrospector() {
        return new JacksonAnnotationIntrospector();
    }

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setAnnotationIntrospector(jacksonAnnotationIntrospector());
        mapper.registerModule(new JodaModule());
        mapper.registerModule(new Hibernate4Module());
        return mapper;
    }

    @Bean
    public MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter() {
        MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
        messageConverter.setObjectMapper(objectMapper());
        return messageConverter;
    }

    @Bean(name = "marshaller")
    public Jaxb2Marshaller jaxb2Marshaller() {
        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        marshaller.setContextPath("some.path");
        return marshaller;
    }

    @Bean
    public MarshallingHttpMessageConverter marshallingMessageConverter() {
        return new MarshallingHttpMessageConverter(
                jaxb2Marshaller(),
                jaxb2Marshaller()
        );
    }
}

我的上下文刷新了听众:

@Component
@Configuration
public class Initializer implements ApplicationListener<ContextRefreshedEvent>
{

    @Override
    public void onApplicationEvent(ContextRefreshedEvent e) {
        <do some intialization stuff between several services/daos after spring is done building everything>
    }
}

我会省略我的持久性和日志配置,因为我认为它不相关......我的PersistenceConfig类注释为:

@Configuration
@EnableTransactionManagement
@PropertySource("classpath:database.properties")

和Log4jConfig注释为:

@Order(2)
@Configuration

你可能会注意到一件事奇怪的是,我的Initializer实现了使用@Configuration注释的ApplicationListener类。我之所以这么做,只是因为根ServiceConfig在组件扫描期间不会选择它,它只会在调度程序servlet上下文中创建。

经过多次痛苦和痛苦之后,我认为问题不是我的Spring配置,而是我在AbstractAnnotationConfigDispatcherServletInitializer类中缺少的东西...不知何故,某种程度上,它正在使它的工作量减少一倍。我是否应该重写受保护的WebApplicationContext createRootApplicationContext()或public void onStartup(ServletContext servletContext)是否会抛出ServletException或手动创建上下文?

更新

我有一种怀疑,即使用AbstractAnnotationConfigDispatcherServletInitializer扩展的WebApplicationInitializer和AbstractSecurityWebApplicationInitializer扩展的WebApplicationListener是其根本原因。即使我知道它会导致spring安全性失败,因为它没有注册spring安全过滤器链,我决定删除这个类,看它是否能解决问题 - 没有区别。

出于沮丧,我决定下载glassfish并将其部署在那里。我第一次部署它,它只运行一次!它部署了,但很明显,我无法登录,因为春天的安全措施现在被打破了。作为测试,我重新添加类“公共类SecurityInitializer扩展AbstractSecurityWebApplicationInitializer {}”以查看会发生什么。正如我所料,这个问题重新出现了。一切都跑了两次。所以,我再次删除它,现在我遇到了完全相同的问题。它仍在构建上下文,在初始化过程中运行,破坏上下文,然后重新执行。这就像第二个注册的WebApplicationInitializer以某种方式在Web服务器中卡住或缓存。这是我能想到的唯一解释......所以我现在接近答案,但我仍然不确定如何解决这个问题......

0 个答案:

没有答案