Spring SseEmitter

时间:2017-06-19 07:42:17

标签: javascript spring-mvc spring-security server-sent-events

我有一个Spring Controller方法,它调用SseEmitter将服务器发送的事件推送到浏览器。在浏览器中,从主页运行用于为SSE ​​URL创建EventSource的javascript。主页本身需要使用基于Spring Security Form的身份验证进行身份验证。因此,成功进行身份验证后,登录页面将指向主页。但是,在Server-sent events方法中,身份验证对象为 null 。我理解我的错误的Spring Security配置导致了这一点,但不知道如何解决它!!

以下是我使用的安全配置

@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;
  }
}

创建SSE EventSource对象的Javascript

<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>

1 个答案:

答案 0 :(得分:3)

查看SecurityContextHolder.getContext()实施。在幕后,它使用ThreadLocalSecurityContextHolderStrategy策略来保存您当前的安全上下文。这意味着当前的安全上下文只能从请求线程中获得。并且您的userActivityFeed()实际上是在单独的帖子中执行的,这就是您在致电SecurityContextHolder.getContext().getAuthentication()时获得NPE的原因。

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