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看不见登录失败或者注销的提示