今週、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'
他にも 2 回実行されているものがあります... ディスパッチャー サーブレット コンテキストで ContextRefreshedEvents をリッスンするクラスがあります。Web アプリをデバッグすると、このコードが実行されているかのようにログ ステートメントが表示されますが、設定したブレークポイントには一度もヒットしません。そのミステリー コンテキストが完全に初期化されたように見えた後、明らかに破棄された後、再び初期化されます。今回は、ブレークポイントがヒットし、コードをステップ実行できます。私の構成ファイルはすべて、私が読んだすべてのものに基づいて正しく構成されていると思います.root/dispatcherコンポーネントは相互に排他的にスキャンし、同じパッケージをスキャンしません.
これは、BCryptPasswordEncoder をセットアップしようとしているため、より大きな問題になりつつあります。上記のような不気味な問題が発生しています。CustomUserDetailsService から取得したにもかかわらず、CustomAuthenticationProvider からのステートメントのログをデバッグしたり、表示したりできなくなりました。どういうわけか、作成された最初のゴーストコンテキストから CustomAuthenticationProvider のキャッシュされたインスタンスを使用していると思い始めています。これを理解するための助けをいただければ幸いです。
私のパッケージは非常にフラットに設定されています。つまり、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
奇妙に感じるかもしれないことの 1 つは、Initializer が @Configuration でアノテーションを付けられた ApplicationListener クラスを実装していることです。コンポーネントのスキャン中にルート ServiceConfig がそれを取得せず、ディスパッチャ サーブレット コンテキストでのみ作成されるように、単純にこれを行いました。
多くの苦痛と苦悩の後、問題は特に私のSpring構成ではなく、AbstractAnnotationConfigDispatcherServletInitializerクラスに欠けているものだと考えています...どういうわけか、何かがそれを倍増させています。protected WebApplicationContext createRootApplicationContext() または public void onStartup(ServletContext servletContext) throws ServletException またはコンテキストを手動で作成する何かをオーバーライドする必要がありますか?
アップデート
AbstractAnnotationConfigDispatcherServletInitializer の拡張元である WebApplicationInitializer と AbstractSecurityWebApplicationInitializer の拡張元である WebApplicationListener の両方を使用することが、この根本的な原因であるという疑いがありました。スプリング セキュリティ フィルター チェーンを登録していないため、スプリング セキュリティが失敗することはわかっていましたが、このクラスを削除して、問題が解決するかどうかを確認することにしました。違いはありません。
欲求不満から、グラスフィッシュをダウンロードしてそこに展開することにしました。初めてデプロイしたときは、1回しか実行されませんでした! 展開されましたが、明らかに、春のセキュリティが壊れているため、サインインできませんでした. テストとして、「public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {}」クラスを再度追加して、何が起こるかを確認しました。予想通り、問題が再発しました。すべてが2回実行されました。SO、もう一度削除しましたが、まったく同じ問題に悩まされています。それはまだコンテキストを構築しており、初期化を実行し、コンテキストを破棄してから、最初からやり直しています。2 番目に登録された WebApplicationInitializer が何らかの形で Web サーバーにスタックまたはキャッシュされているようです。それが私が考えることができる唯一の説明です....だから私は'