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

相關問題