Spring安全:零星的NullPointerException

时间:2018-04-20 07:25:34

标签: java spring spring-boot spring-security

我有简单的Web应用程序和REST API,我希望通过基于令牌的身份验证来保护它。 这是我的安全过滤器:

public class AuthFilter extends AbstractAuthenticationProcessingFilter {

  private static final String AUTH_HEADER = "Authorization";

  public AuthFilter(RequestMatcher reqMatcher) {
    super(reqMatcher);
  }

  @Override
  public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
      throws IOException, ServletException {

    //30 line: here is first place in my code where exception occurred
    this.setAuthenticationSuccessHandler((req1, res1, auth) -> chain.doFilter(req1, res1)); 

    //32 line: second place in my code where exception occurred
    super.doFilter(req, res, chain);
  }

  @Override
  public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
      throws AuthenticationException, IOException, ServletException {

    String token = req.getHeader(AUTH_HEADER);

    if (token == null || token.isEmpty()) {
      throw new BadCredentialsException("Token is not present!");
    }

    AuthToken authToken = new AuthToken(token);
    authToken.setDetails(authenticationDetailsSource.buildDetails(req));

    return this.getAuthenticationManager().authenticate(authToken);
  }
}

按令牌提取用户的身份验证提供程序:

@Component
public class AuthProvider implements AuthenticationProvider {

  @Autowired private UsersService usersService;
  @Autowired private TokensService tokensService;

  @Override
  public Authentication authenticate(Authentication auth) throws AuthenticationException {
    AuthToken authToken = (AuthToken) auth;

    String t = authToken.getToken();

    try {
      Token token = tokensService.get(t);

      if (token.isAdmin()) {
        User admin = User.createAdmin();

        return new AuthToken(t, admin);
      }

      User user = usersService.getBy(token);

      return new AuthToken(t, user);
    } catch (TokenNotFoundException e) {
      throw new BadCredentialsException("Token not found!");
    } catch (UserNotFoundException e) {
      throw new BadCredentialsException("User by token not found!");
    }
  }

  @Override
  public boolean supports(Class<?> auth) {
    return AuthToken.class.isAssignableFrom(auth);
  }
}

最后这是我的安全配置:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
      .csrf().disable()
        .addFilterBefore(filter(), AnonymousAuthenticationFilter.class)
          .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
  }

  protected AbstractAuthenticationProcessingFilter filter() throws Exception {
    AuthFilter filter = new AuthFilter(
      new RegexRequestMatcher("^(\\/rest)(?!(\\/settings)|(\\/security))(\\/?.*)", null));

    filter.setAuthenticationManager(authenticationManagerBean());

    return filter;
  }
}

一切正常,但是当我向端点发送请求时会定期引发NullPointerException。并非每个请求都以异常结束,它可以在10或20个请求之后,此处没有此异常的顺序。对相同端点的请求可以很好地工作,但是在引发了多个请求NullPointerException之后。此外,可以在第一次请求时抛出异常。

这是stacktrace:

2018-04-20 11:14:33.162 ERROR 3868 --- [p-nio-80-exec-9] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
java.lang.NullPointerException: null
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[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.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:317) ~[spring-security-web-4.2.2.RELEASE.jar:4.2.2.RELEASE]
    at ru.asu.edu.studyload.security.AuthFilter.lambda$0(AuthFilter.java:30) ~[bin/:na]
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.successfulAuthentication(AbstractAuthenticationProcessingFilter.java:326) ~[spring-security-web-4.2.2.RELEASE.jar:4.2.2.RELEASE]
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:240) ~[spring-security-web-4.2.2.RELEASE.jar:4.2.2.RELEASE]
    at ru.asu.edu.studyload.security.AuthFilter.doFilter(AuthFilter.java:32) ~[bin/:na]
    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.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:170) ~[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.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) ~[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.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116) ~[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.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.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:106) ~[spring-boot-actuator-1.5.3.RELEASE.jar:1.5.3.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(Unknown Source) [na:1.8.0_131]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) [na:1.8.0_131]
    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(Unknown Source) [na:1.8.0_131]

我做错了什么?

2 个答案:

答案 0 :(得分:2)

我刚刚遇到了同样的问题,并注意到问题出在

  

this.setAuthenticationSuccessHandler((req1,res1,auth)-> chain.doFilter(req1,res1));

doFilter方法内

doFilter不是线程安全的,我们在lambda函数中使用chain变量。当我们有多个并发请求访问此过滤器时,我们会遇到竞争状况。

[nio-8080-exec-6] => doFilter() with (chain 1285350683)
[nio-8080-exec-7] => doFilter() with (chain 1471502546)
[nio-8080-exec-6] => setAuthenticationSuccessHandler lambda (continue chain 1471502546)
[nio-8080-exec-7] => setAuthenticationSuccessHandler lambda (continue chain 1471502546)
[nio-8080-exec-7] => c.p.gbm.api.rest.RestEndpointsLogger : type=http method=GET endpoint=/test status-code=200
[nio-8080-exec-7] => setAuthenticationSuccessHandler lambda (release chain 1471502546)
2019-05-07 17:23:10.346 ERROR 68613 --- [nio-8080-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in 
context with path [] threw exception 
    java.lang.NullPointerException: null

如上所述,当线程6尝试运行servlet.service()来解决请求时,线程7已经释放了将servlet设置为null的链。

答案 1 :(得分:-1)

我不知道如何或为什么,但这个过滤器实现似乎有效:

public class AuthFilter extends AbstractAuthenticationProcessingFilter {

  private static final String AUTH_HEADER = "Authorization";

  public AuthFilter(RequestMatcher reqMatcher) {
    super(reqMatcher);

    this.setAuthenticationSuccessHandler((req1, res1, auth) -> { });
  }

  @Override
  public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
      throws AuthenticationException, IOException, ServletException {

    String token = req.getHeader(AUTH_HEADER);

    if (token == null || token.isEmpty()) {
      throw new BadCredentialsException("Token is not present!");
    }

    AuthToken authToken = new AuthToken(token);
    authToken.setDetails(authenticationDetailsSource.buildDetails(req));

    return this.getAuthenticationManager().authenticate(authToken);
  }

  @Override
  protected void successfulAuthentication(
    HttpServletRequest req, 
    HttpServletResponse res, 
    FilterChain chain, 
    Authentication auth
  ) throws IOException, ServletException {

    super.successfulAuthentication(req, res, chain, auth);

    chain.doFilter(req, res);
  }
}