我有一個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