【问题标题】:Spring Security Authentication object is null for Spring SseEmitterSpring SseEmitter 的 Spring Security Authentication 对象为 null
【发布时间】:2017-11-21 07:57:36
【问题描述】:

我有一个 Spring Controller 方法,它调用 SseEmitter 将服务器发送的事件推送到浏览器。在浏览器中,用于为 SSE url 创建 EventSource 的 javascript 从主页运行。主页本身需要使用基于 Spring Security Form 的身份验证完成的身份验证。所以一个登录页面,在认证成功后,将直接指向主页。但是,在服务器发送的事件方法中,认证对象是 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() 的 Authentication 对象,该对象抛出 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>

【问题讨论】:

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


    【解决方案1】:

    看看SecurityContextHolder.getContext() 的实现。在后台,它使用ThreadLocalSecurityContextHolderStrategy 策略来保存您当前的安全上下文。这意味着当前的安全上下文只能从请求线程获得。而你的 userActivityFeed() 实际上是在单独的线程中执行的,这就是为什么你在调用 SecurityContextHolder.getContext().getAuthentication() 时会得到 NPE。

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

    【讨论】:

    • 您的意思是SecurityContextHolder.getContext() 不会在会话中的请求之间共享吗?如果 Spring 安全相关对象(身份验证、主体等)不可用于后续对受限 url 的请求,我无法理解单个入口点身份验证(例如登录 url)的意义
    • 根据 spring 文档,docs.spring.io/spring-security/site/docs/3.0.x/reference/… 在单个会话中接收并发请求的应用程序中,线程之间将共享相同的 SecurityContext 实例。即使使用了 ThreadLocal,它也是从每个线程的 HttpSession 中检索到的同一个实例。如果您希望临时更改线程运行的上下文,这会产生影响。
    • 该文档谈到了实际处理请求的容器线程。但是您的 userActivityFeed() 方法正在由执行程序服务使用它自己的线程执行。这个线程对SecurityContext一无所知
    猜你喜欢
    • 2013-05-03
    • 2012-03-07
    • 2013-05-24
    • 2014-03-04
    • 2012-07-15
    • 2019-01-17
    • 2014-07-04
    • 2014-07-31
    • 2015-12-04
    相关资源
    最近更新 更多