0

I am using a scheduled service in spring boot app , i need to get the current connected user inside that service , my problem is that

SecurityContextHolder.getContext().getAuthentication()

returns the current connected user only once ( just after i am logged in ) , but in the next running tasks

SecurityContextHolder.getContext().getAuthentication()

returns NPE , i have searched and found that SecurityContextHolder is not shared outside the main thread.

My Service :

@Service
@EnableScheduling
public class SsePushNotificationService {

public void addEmitter(final SseEmitter emitter) {
    emitters.add(emitter);
}

public void removeEmitter(final SseEmitter emitter) {
    emitters.remove(emitter);
}

@Async("taskExecutor")
@Scheduled(fixedDelay = 5000)
public void doNotify() throws IOException {
    System.out.println("------@@@@@ inside doNotify");
    System.out.println("##### ---- curent thread /notification : " + Thread.currentThread().getName());


    if (SecurityContextHolder.getContext().getAuthentication() != null) {
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

        if (principal instanceof UserDetails) {
            String username = ((UserDetails) principal).getUsername();
            System.out.println("------@@@@@  principal instanceof UserDetails : " + username);
        } else {
            String username = principal.toString();
            System.out.println("------@@@@@  principal : " + username);
        }
    }

}
}

the controller :

@Controller
@CrossOrigin(origins = "*")
public class SsePushNotificationRestController {

@Autowired
SsePushNotificationService service;

@Autowired
UserDetailsServiceImpl userService;

@Autowired
UserNotificationService userNotifService;

final List<SseEmitter> emitters = new CopyOnWriteArrayList<>();

String username;
int nbrEvent;

@GetMapping(value = "/notification", produces = { MediaType.TEXT_EVENT_STREAM_VALUE })
public ResponseEntity<SseEmitter> doNotify() throws InterruptedException, IOException {
    

    System.out.println("##### ---- curent thread /notification : " + Thread.currentThread().getName());

    final SseEmitter emitter = new SseEmitter();
    service.addEmitter(emitter);
    service.doNotify();
    emitter.onCompletion(() -> service.removeEmitter(emitter));
    emitter.onTimeout(() -> service.removeEmitter(emitter));

    return new ResponseEntity<>(emitter, HttpStatus.OK);
}

}

Javascript :

 const eventSource = new EventSource('http://localhost:8080/notification');
        eventSource.onmessage = e => {
            const msg = e.data;
            $("#notifCounter").text(msg);
            $("#usrNotifCounter").text(msg);
        };
        eventSource.onopen = e => console.log('open');
        eventSource.onerror = e => {
            if (e.readyState == EventSource.CLOSED) {
                console.log('close');
            }
            else {
                console.log(e);
            }
        };
        eventSource.addEventListener('second', function(e) {
            console.log('second', e.data);
        }, false);

WebSecurityConfig :

@Configuration
@EnableWebSecurity
public class WebSecurityConfig<S extends Session> extends WebSecurityConfigurerAdapter {

@Autowired
private FindByIndexNameSessionRepository<S> sessionRepository;

@Autowired
private MySessionExpiredStrategy sessionExpiredStrategy;

@Bean
public UserDetailsService userDetailsService() {
    return new UserDetailsServiceImpl();
}

@Bean
public BCryptPasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

@Bean
public DaoAuthenticationProvider authenticationProvider() {
    DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
    authProvider.setUserDetailsService(userDetailsService());
    authProvider.setPasswordEncoder(passwordEncoder());

    return authProvider;
}

@Component
public class MySessionExpiredStrategy implements SessionInformationExpiredStrategy {

    @Override
    public void onExpiredSessionDetected(SessionInformationExpiredEvent event)
            throws IOException, ServletException {
        HttpServletResponse response = event.getResponse();
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().write(
                "Your account has been logged in elsewhere, and the current login has expired. If the password is leaked, please change it immediately!");
    }

}

@Bean
public SpringSessionBackedSessionRegistry<S> sessionRegistry() {
    return new SpringSessionBackedSessionRegistry<>(this.sessionRepository);
}

@Bean
public ConcurrentSessionControlAuthenticationStrategy sessionControlAuthenticationStrategy() {
    ConcurrentSessionControlAuthenticationStrategy csas = new ConcurrentSessionControlAuthenticationStrategy(
            sessionRegistry());
    csas.setExceptionIfMaximumExceeded(true);
    return csas;
}

@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring().antMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/img/**", "/error");
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(authenticationProvider());
}

@Override
protected void configure(HttpSecurity http) throws Exception {


            .anyRequest().access("@rbacService.hasPermission(request,authentication)")

            .and().formLogin().loginPage("/login").defaultSuccessUrl("/", true).permitAll().and().logout()
            .deleteCookies("JSESSIONID").invalidateHttpSession(true).clearAuthentication(true)
            .logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/login?logout")
            .permitAll().and().exceptionHandling().accessDeniedPage("/static/403")

            .and().sessionManagement().sessionFixation().migrateSession()
            .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
            .invalidSessionUrl("/static/invalidSession.html").maximumSessions(2).maxSessionsPreventsLogin(false)
            .expiredSessionStrategy(sessionExpiredStrategy).sessionRegistry(sessionRegistry())
            .expiredUrl("/login?invalid-session=true");

}
}

what is the best approch to share SecurityContextHolder between threads in that case.

Anass Boukalane
  • 539
  • 10
  • 26

0 Answers0