Spring Security 自定义注销空指针异常

Posted

技术标签:

【中文标题】Spring Security 自定义注销空指针异常【英文标题】:Spring security custom logout null pointer exception 【发布时间】:2018-04-28 07:16:54 【问题描述】:

我希望我的代码做什么:当用户登录时,我想使用默认的 Spring Security 让他登录。然后在 LoginSuccessHandler 中,我正在创建一个令牌并将其保存到数据库中.使用默认注销,在 LogoutSuccessHandler 中我需要从数据库中删除令牌。

我的问题:我正在使用 Spring Security 进行登录和注销。登录工作正常。注销过去也可以正常工作,但几天前我开始收到NullPointerException,这是由LogoutSuccessHandler 中的空身份验证引起的。另外,我希望我的 API 尽可能无状态。但我想知道;我应该禁用为 Spring Security 创建会话将登录/注销仍然有效还是需要创建一些会话之王?我用谷歌搜索了几个关于它的问题,但大多数都认为配置有问题。我尝试了几个不同的,不幸的是没有一个成功。

我的代码: WebSecurityConfig.java

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter 
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private RESTAuthenticationEntryPoint restAuthenticationEntryPoint;
    @Autowired
    private RESTAuthenticationSuccessHandler restAuthenticationSuccessHandler;
    @Autowired
    private RESTAuthenticationFailureHandler restAuthenticationFailureHandler;
    @Autowired
    private RESTLogoutSuccessHandler restLogoutSuccessHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception
        http
                .cors()
                .and()
                .csrf().disable()
                .authorizeRequests()
                .antMatchers("**/anna/**").authenticated()
                .anyRequest().permitAll();

        http
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessHandler(restLogoutSuccessHandler);

//        http.sessionManagement()
//                .sessionCreationPolicy(SessionCreationPolicy.NEVER);
    

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

    @Bean
    public BCryptPasswordEncoder passwordEncoder() 
        return new BCryptPasswordEncoder();
    

    @Bean
    public DaoAuthenticationProvider authenticationProvider() 
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(userDetailsService);
        authenticationProvider.setPasswordEncoder(passwordEncoder());
        return authenticationProvider;
    

RESTLogoutSuccessHandler.java

@Component
public class RESTLogoutSuccessHandler implements LogoutSuccessHandler 
    @Autowired
    private UserRepository userRepository;

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
                                Authentication authentication) throws IOException, ServletException 

        User authUser = (User) authentication.getPrincipal();
        String userName = authUser.getUsername();
        com.shareabook.model.User user = userRepository.findByUserName(userName);
        user.setToken("");
        userRepository.save(user);
    

堆栈跟踪

java.lang.NullPointerException: null
    at com.shareabook.security.RESTLogoutSuccessHandler.onLogoutSuccess(RESTLogoutSuccessHandler.java:28) ~[classes/:na]
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:111) ~[spring-security-web-4.2.2.RELEASE.jar:4.2.2.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) ~[spring-security-web-4.2.2.RELEASE.jar:4.2.2.RELEASE]
    at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:96) ~[spring-web-4.3.8.RELEASE.jar:4.3.8.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.8.RELEASE.jar:4.3.8.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) ~[spring-security-web-4.2.2.RELEASE.jar:4.2.2.RELEASE]
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64) ~[spring-security-web-4.2.2.RELEASE.jar:4.2.2.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.8.RELEASE.jar:4.3.8.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) ~[spring-security-web-4.2.2.RELEASE.jar:4.2.2.RELEASE]
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105) ~[spring-security-web-4.2.2.RELEASE.jar:4.2.2.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) ~[spring-security-web-4.2.2.RELEASE.jar:4.2.2.RELEASE]
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56) ~[spring-security-web-4.2.2.RELEASE.jar:4.2.2.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.8.RELEASE.jar:4.3.8.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) ~[spring-security-web-4.2.2.RELEASE.jar:4.2.2.RELEASE]
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:214) ~[spring-security-web-4.2.2.RELEASE.jar:4.2.2.RELEASE]
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:177) ~[spring-security-web-4.2.2.RELEASE.jar:4.2.2.RELEASE]
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346) ~[spring-web-4.3.8.RELEASE.jar:4.3.8.RELEASE]
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262) ~[spring-web-4.3.8.RELEASE.jar:4.3.8.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.14.jar:8.5.14]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.14.jar:8.5.14]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) ~[spring-web-4.3.8.RELEASE.jar:4.3.8.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.8.RELEASE.jar:4.3.8.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.14.jar:8.5.14]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.14.jar:8.5.14]
    at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:105) ~[spring-web-4.3.8.RELEASE.jar:4.3.8.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.8.RELEASE.jar:4.3.8.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.14.jar:8.5.14]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.14.jar:8.5.14]
    at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81) ~[spring-web-4.3.8.RELEASE.jar:4.3.8.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.8.RELEASE.jar:4.3.8.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.14.jar:8.5.14]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.14.jar:8.5.14]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197) ~[spring-web-4.3.8.RELEASE.jar:4.3.8.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.8.RELEASE.jar:4.3.8.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.14.jar:8.5.14]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.14.jar:8.5.14]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) ~[tomcat-embed-core-8.5.14.jar:8.5.14]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-8.5.14.jar:8.5.14]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:478) [tomcat-embed-core-8.5.14.jar:8.5.14]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) [tomcat-embed-core-8.5.14.jar:8.5.14]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:80) [tomcat-embed-core-8.5.14.jar:8.5.14]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) [tomcat-embed-core-8.5.14.jar:8.5.14]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) [tomcat-embed-core-8.5.14.jar:8.5.14]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:799) [tomcat-embed-core-8.5.14.jar:8.5.14]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-8.5.14.jar:8.5.14]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861) [tomcat-embed-core-8.5.14.jar:8.5.14]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1455) [tomcat-embed-core-8.5.14.jar:8.5.14]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-8.5.14.jar:8.5.14]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_77]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_77]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.14.jar:8.5.14]
    at java.lang.Thread.run(Thread.java:745) [na:1.8.0_77]

【问题讨论】:

LogoutSuccessHandler 顾名思义发生在成功注销之后(因此用户不再被认证) @RC,所以您建议身份验证应该为空?如果是这样,为什么大多数关于如何编写 LogoutSuccessHandler 的示例都与检查if(authentication != null) 一致?例如。这里:concretepage.com/spring-4/… 另外,这不是doFilter() 的问题吗? RESTLogoutSuccessHandler.java 的第 28 行是哪一行? @Ralph,它是`User authUser = (User) authentication.getPrincipal();` 【参考方案1】:

authUser 为 null,这是导致异常的原因。

authUser 为空,因为LogoutFilter 在调用logoutSuccessHandler.onLogoutSuccess 之前调用了LogoutHandler

LogoutFilter:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
        throws IOException, ServletException 
    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) res;

    if (requiresLogout(request, response)) 
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();

        if (logger.isDebugEnabled()) 
            logger.debug("Logging out user '" + auth
                    + "' and transferring to logout destination");
        

        this.handler.logout(request, response, auth);

        logoutSuccessHandler.onLogoutSuccess(request, response, auth);

        return;
    

    chain.doFilter(request, response);

SpringContextLogoutHander 这样的典型LogoutHander 实现会清除身份验证。但是您可以通过将clearAuthentication 设置为false 来禁用此行为

SecurityContextLogoutHandler:

public void logout(HttpServletRequest request, HttpServletResponse response,
        Authentication authentication) 
    Assert.notNull(request, "HttpServletRequest required");
    if (invalidateHttpSession) 
        HttpSession session = request.getSession(false);
        if (session != null) 
            logger.debug("Invalidating session: " + session.getId());
            session.invalidate();
        
    

    if (clearAuthentication) 
        SecurityContext context = SecurityContextHolder.getContext();
        context.setAuthentication(null);
    

    SecurityContextHolder.clearContext();

【讨论】:

感谢您的回答。我确实将我的配置更改为logout().clearAuthentication(false) .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) .logoutSuccessHandler(restLogoutSuccessHandler); 不幸的是它没有帮助,我仍然有相同堆栈跟踪的空指针异常。你碰巧还有什么想法吗? @werwwerw:您是否在日志中看到来自行 logger.debug("Logging out user '" + auth + "' and transferring to logout destination"); 的日志消息? auth 在你的日志中有什么值? 尝试在注销过滤器中设置断点并查看auth 变量。 - 也许你发现了 Authentication.principal 变为空的地方 @Ralph:像 SpringContextLogoutHander 这样的典型 LogoutHander 实现会清除身份验证。 没错,但是 auth 是一个局部变量,所以在 LogoutSuccessHandler 中它应该是非 null对吧? @dur:你是对的——我的错。所以尝试调试方式。也许这个答案为您提供了另一个很好的调试起点:***.com/a/6408377/280244

以上是关于Spring Security 自定义注销空指针异常的主要内容,如果未能解决你的问题,请参考以下文章

如何自定义 Grails Spring Security Core 2 登录/注销控制器和视图?

为什么Spring Security看不见登录失败或者注销的提示

Spring Autowired空指针异常

Spring Security 无法注销

使用 Spring MVC 和 Spring Security 进行 404 注销

如何在 Spring Security 中编辑定时注销