我有一个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>
您的意思是'SecurityContextHolder.getContext()'不会在会话中的请求之间共享吗?如果春季安全相关对象(身份验证,主要等)不适用于受限制的URL的后续请求,则我无法理解单一入口点身份验证(例如登录URL)的问题 – wildthing81
按照Spring文档https:/ /docs.spring.io/spring-security/site/docs/3.0.x/reference/technical-overview.html在一个接收单个会话中的并发请求的应用程序中,同一个SecurityContext实例将在线程之间共享。即使正在使用ThreadLocal,它也是从每个线程的HttpSession中检索的实例。如果您想临时更改线程正在运行的上下文,这会产生影响。 – wildthing81
该文档说明了实际处理请求的容器线程。但是你的'userActivityFeed()'方法由执行器服务使用它自己的线程执行。这个线程对SecurityContext一无所知 – Leffchik