인프런 커뮤니티 질문&답변

김동운님의 프로필 이미지
김동운

작성한 질문수

스프링 시큐리티

2) 인증 필터 - AjaxAuthenticationFilter

[질문] SecurityFilterChain 방식에서 질문드립니다.

작성

·

3K

0

안녕하세요 강사님. 저는 아래와 같이 하였는데 null 에러가 나서 질문드려요. 소스도 공유합니다.

깃헙 : https://github.com/DongWoonKim/core-spring-security-ajax

@Bean
AuthenticationManager authenticationManager1 (HttpSecurity http) throws Exception {
    return http.getSharedObject(AuthenticationManager.class);
}
http
        .addFilterBefore(new AjaxLoginProcessingFilter() , UsernamePasswordAuthenticationFilter.class );

error message
{

"timestamp": "2022-09-28T12:58:47.088+00:00",

"status": 500,

"error": "Internal Server Error",

"trace": "java.lang.NullPointerException: Cannot invoke \"org.springframework.security.authentication.AuthenticationManager.authenticate(org.springframework.security.core.Authentication)\" because the return value of \"com.example.corespringsecurityajax.security.filter.AjaxLoginProcessingFilter.getAuthenticationManager()\" is null\n\tat com.example.corespringsecurityajax.security.filter.AjaxLoginProcessingFilter.attemptAuthentication(AjaxLoginProcessingFilter.java:39)\n\tat org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:227)\n\tat org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:217)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)\n\tat org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103)\n\tat org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)\n\tat org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90)\n\tat org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)\n\tat org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:112)\n\tat org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:82)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)\n\tat org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)\n\tat org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346)\n\tat org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:221)\n\tat org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:186)\n\tat org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354)\n\tat org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360)\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399)\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:890)\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1789)\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\n\tat java.base/java.lang.Thread.run(Thread.java:833)\n",

"message": "Cannot invoke \"org.springframework.security.authentication.AuthenticationManager.authenticate(org.springframework.security.core.Authentication)\" because the return value of \"com.example.corespringsecurityajax.security.filter.AjaxLoginProcessingFilter.getAuthenticationManager()\" is null",

"path": "/api/login"

}

답변 2

1

정수원님의 프로필 이미지
정수원
지식공유자

그건 HttpSecurity 빈으로 생성되는 시점에서는 AuthenticationManager 가 아직 HttpSecurity 의 공유객체로 저장이 되지 않았기 때문입니다.

@Bean(HTTPSECURITY_BEAN_NAME)
@Scope("prototype")
HttpSecurity httpSecurity() throws Exception {
   WebSecurityConfigurerAdapter.LazyPasswordEncoder passwordEncoder = new WebSecurityConfigurerAdapter.LazyPasswordEncoder(
         this.context);
   AuthenticationManagerBuilder authenticationBuilder = new WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder(
         this.objectPostProcessor, passwordEncoder);
   authenticationBuilder.parentAuthenticationManager(authenticationManager());
   HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
   // @formatter:off
   http
      .csrf(withDefaults())
      .addFilter(new WebAsyncManagerIntegrationFilter())
      .exceptionHandling(withDefaults())
      .headers(withDefaults())
      .sessionManagement(withDefaults())
      .securityContext(withDefaults())
      .requestCache(withDefaults())
      .anonymous(withDefaults())
      .servletApi(withDefaults())
      .apply(new DefaultLoginPageConfigurer<>());
   http.logout(withDefaults());
   // @formatter:on
   applyDefaultConfigurers(http);
   return http;
}

위의 코드는 HttpSecurity 가 빈으로 생성되는 구문인데

AuthenticationManagerBuilder authenticationBuilder = new WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder(
         this.objectPostProcessor, passwordEncoder);
   authenticationBuilder.parentAuthenticationManager(authenticationManager());

코드를 보시면 AuthenticationManagerBuilder 를 생성하고 공유객체로 설정하고 있습니다.

그런데 AuthenticationManager 를 별도로 HttpSecurity 의 공유객체로 설정하는 구문은 없고

AuthenticationManagerBuilder 의 parent 속성에만 저장하고 있습니다.

그리고 AuthenticationManagerBuilder 클래스도 AuthenticationManager 를 참조할 수 있는 메소드를 제공하고 있지 않습니다.

그렇기 때문에 http.getSharedObject(AuthenticationManager.class); 를 실행하면 AuthenticationManager 가 null 값으로 나오게 됩니다.

저도 HttpSecurity 에서 초기화시 생성된 AuthenticationManager 객체와 동일한 객체를 참조할 수 있는 방법이 있는지 다각도로 살펴 보았지만 아직 발견하지 못했습니다.

그런데 AuthenticationManager 는 아예 새롭게 생성해서 사용해도 크게 문제될 것은 없습니다.

가령 예를 들어서

ProviderManager providerManager = new ProviderManager();
List<AuthenticationProvider> providers = providerManager.getProviders();
providers.add(new DaoAuthenticationProvider());
providers.add(new AnonymousAuthenticationProvider("key"));
providers.add(new CustomAuthenticationProvider());

CutomAuthenticationFilter cutomAuthenticationFilter = new CustomAuthenticationFilter();
cutomAuthenticationFilter.setAuthenticationManager(providerManager);

http.addFilterBefore(cutomAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

이렇게 해도 실행하는데는 문제가 없습니다.

다만 스프링 시큐리티가 생성한 ProviderManager 객체와 커스텀하게 생성한 ProviderManager 를 어떤 필터에서 사용할 것인지를 잘 결정해야 하고 설정해 주어야 합니다.

일단

@Bean AuthenticationManager authenticationManager(AuthenticationConfiguration authConfiguration) throws Exception { return authConfiguration.getAuthenticationManager(); }

이렇게 사용하시는 것을 권해 드립니다.

0

어떤 식으로 해결하셨나요? 공유된 깃 허브에 @Bean AuthenticationManager authenticationManager1 (HttpSecurity http) throws Exception { return http.getSharedObject(AuthenticationManager.class); } 부분을 넣어서 실행했는데 똑같이 매니저 null 오류가 나네요..

김동운님의 프로필 이미지
김동운

작성한 질문수

질문하기