2017-06-19 165 views
2

我有一个Spring Controller方法调用SseEmitter以将服务器发送的事件推送到浏览器。从浏览器中,为SSE网址创建EventSource的JavaScript从主页运行。首页本身需要使用Spring Security基于表单的身份验证完成身份验证。因此,成功认证时登录页面将直接指向主页。但是,在服务器发送的事件方法中,认证对象为null。我明白我的不正确的Spring安全配置是造成这种情况,但不知道如何解决它!Spring Security身份验证对象对于Spring SseEmitter为空

下面是安全配置我用

@Configuration 
@EnableWebMvcSecurity 
public class UCFSecurityConfig extends WebSecurityConfigurerAdapter{ 

@Autowired 
@Qualifier("epUser") 
private UserDetailsService userDetailsService; 

@Autowired 
private AuthenticationSuccessHandler successHandler; 
.... 
.... 
@Override 
protected void configure(HttpSecurity http) throws Exception { 
    http 
     .authorizeRequests() 
      .antMatchers("/rest/**").authenticated() 
      .anyRequest().authenticated() 
      .and() 
      .formLogin() 
      .loginPage("/login.htm").failureUrl("/error.htm") 
      .loginProcessingUrl("/spring_sec_auth.htm") 
      .usernameParameter("username").passwordParameter("password").permitAll() 
      .successHandler(successHandler) 
      .and() 
      .logout() 
      //.logoutSuccessUrl("/login.htm?logout") 
      .invalidateHttpSession(true) 
      .and().csrf().disable(); 

} 
.... 
.... 
} 

下面的SSE方法调用userActivityFeed()UserActivityService其使用认证对象从SecurityContextHolder.getContext().getAuthentication()其中引发NPE !!!

@Controller 
public class UserActivityController { 

@Autowired 
private UserActivityService userActivityService; 

@RequestMapping("/userActivity") 
public ResponseBodyEmitter activityFeed() { 

    final SseEmitter emitter = new SseEmitter(); 
    ExecutorService service = Executors.newSingleThreadExecutor(); 
    service.execute(() -> { 
     try { 
      emitter.send(userActivityService.userActivityFeed() , MediaType.TEXT_PLAIN); 
      Thread.sleep(UCFConstants.USR_ACTV_REFRESH_PERIOD); 

     } catch (Exception e) { 
      e.printStackTrace(); 
      emitter.completeWithError(e); 
      return; 
     } 
     emitter.complete(); 
    }); 
    service.shutdown(); 
    return emitter; 
    } 
} 

JavaScript来创建SSE的EventSource对象

<script type="text/javascript"> 
var source = new EventSource("/MyApp/rest/userActivity"); 
source.onmessage = function(event) { 
        document.getElementById("result").innerHTML += event.data     
        + "<br>"; 
        }; 
    </script> 

我的应用程序的web.xml

<servlet-mapping> 
    <servlet-name>MyApp</servlet-name> 
    <url-pattern>*.htm</url-pattern> 
</servlet-mapping> 
<servlet-mapping> 
    <servlet-name>MyApp</servlet-name> 
    <url-pattern>/rest/*</url-pattern> 
</servlet-mapping> 

回答

3

看一看的SecurityContextHolder.getContext()实施。在这种情况下,它使用ThreadLocalSecurityContextHolderStrategy策略来保存当前的安全上下文。这意味着当前的安全上下文只能从请求线程获得。而你的userActivityFeed()实际上是在单独的线程中执行的,这就是为什么你在拨打SecurityContextHolder.getContext().getAuthentication()时得到NPE的原因。

为了使所有这些工作,您可以在控制器方法中更早地获得当前用户,并将其传递给userActivityFeed()

+0

您的意思是'SecurityContextHolder.getContext()'不会在会话中的请求之间共享吗?如果春季安全相关对象(身份验证,主要等)不适用于受限制的URL的后续请求,则我无法理解单一入口点身份验证(例如登录URL)的问题 – wildthing81

+0

按照Spring文档https:/ /docs.spring.io/spring-security/site/docs/3.0.x/reference/technical-overview.html在一个接收单个会话中的并发请求的应用程序中,同一个SecurityContext实例将在线程之间共享。即使正在使用ThreadLocal,它也是从每个线程的HttpSession中检索的实例。如果您想临时更改线程正在运行的上下文,这会产生影响。 – wildthing81

+0

该文档说明了实际处理请求的容器线程。但是你的'userActivityFeed()'方法由执行器服务使用它自己的线程执行。这个线程对SecurityContext一无所知 – Leffchik

相关问题