I get all active users, but when the user logs out, it is still listed as the active user. How do I prevent a user from being listed as an active user after logging out?
I have not been able to find solutions in the documentation.
https://github.com/romanych2021/TestSession
Thanks.
ActiveUserServiceImpl.java
#Service
public class ActiveUserServiceImpl implements ActiveUserService{
#Autowired
SessionRegistry sessionRegistry;
public List<String > getAllActiveUser(){
List<Object> principals = sessionRegistry.getAllPrincipals();
User[] users = (User[]) principals.toArray(new User[0]);
return Arrays.stream(users)
.filter(user -> !sessionRegistry.getAllSessions(user, false)
.isEmpty()).map(User::getUsername).collect(Collectors.toList());
}
}
SecurityConfig.java
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.mvcMatchers("/").permitAll()
.mvcMatchers("/login").anonymous()
.mvcMatchers("/user", "/allUser").hasAnyRole("ADMIN", "USER")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/")
.and().csrf().disable()
.logout()
.permitAll()
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.and().sessionManagement()
.maximumSessions(1)
.expiredUrl("/login")
.sessionRegistry(sessionRegistry);
}
}
HTML
<form method="post" action="/logout">
<button type="submit">Exit</button>
</form>
keep your active users map in a hashMap and remove the loooged out users from that map when day log out.In the value unbound event you can exclude inactive users.
#Getter
#Setter
public class ActiveUserStore {
public HashMap<String, ActorUser> userDetails;
public ActiveUserStore() {
userDetails = new HashMap<>();
}
}
#Getter
#Setter
public class LoggedUser implements HttpSessionBindingListener {
private ActorUser sessionUser;
private ActiveUserStore activeUserStore;
public LoggedUser(ActorUser sessionUser, ActiveUserStore activeUserStore) {
this.activeUserStore = activeUserStore;
this.sessionUser = sessionUser;
}
#Override
public void valueBound(HttpSessionBindingEvent event) {
HashMap<String, ActorUser> userDetails = activeUserStore.getUserDetails();
LoggedUser loggedUser = (LoggedUser) event.getValue();
if (isNotNull(userDetails) && !userDetails.containsKey(loggedUser.getSessionUser().getUsername())) {
userDetails.put(loggedUser.getSessionUser().getUsername(), loggedUser.getSessionUser());
}
}
#Override
public void valueUnbound(HttpSessionBindingEvent event) {
LoggedUser loggedUser = (LoggedUser) event.getValue();
HashMap<String, ActorUser> userDetails = activeUserStore.getUserDetails();
if (isNotNull(userDetails) && isNotNull(loggedUser.getSessionUser()) &&
userDetails.containsKey(loggedUser.getSessionUser().getUsername())) {
userDetails.remove(loggedUser.getSessionUser().getUsername());
}
}
In order to solve this problem, I needed to create only one class.
package com.testsession.service;
import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.web.WebApplicationInitializer;
import javax.servlet.ServletContext;
public class MyWebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext container) {
container.addListener(new HttpSessionEventPublisher());
}
}
Related
I am working on an administration system, I have HomeController.java:
#Controller
public class HomeController {
#Autowired
private UserRepository userRepository;
#Autowired
private UserService userService;
#GetMapping("/")
public String root() {
return "/home";
}
#GetMapping("/home")
public String home() {
return "/home";
}
#RequestMapping("/login")
public String login() {
return "/login";
}
#RequestMapping("/user")
public String userIndex() {
return "/index";
}
#GetMapping("/profile")
public String currentUser(#ModelAttribute("user") #Valid UserDto userDto, BindingResult result, Model model) {
Authentication loggedInUser = SecurityContextHolder.getContext().getAuthentication();
String email = loggedInUser.getName();
User user = userRepository.findByEmail(email);
String firstName = user.getFirstName();
model.addAttribute("firstName", firstName);
model.addAttribute("email", email);
return "profile";
}
when i try to log in with bad credentials everything works fine and it gives invalid pass or username.
The problem is when i enter the correct Credentials, i get this page:
Here is SecurityConfig.java:
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userService;
#Autowired
private UserRepository userRepository;
#Override
protected void configure(final HttpSecurity http) throws Exception {
// #formatter:off
http
.authorizeRequests()
.antMatchers(
"/"
, "/home"
, "/registration"
, "/forgot-password/"
, "/reset-password/"
, "/welcome"
, "/js/**"
, "/css/**"
, "/img/**"
, "/webjars/**"
)
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/super/**").hasRole("SUPER")
.antMatchers("/partner/**").hasRole("PARTNER")
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/index" , true)
.permitAll()
.and()
.logout()
.invalidateHttpSession(true)
.clearAuthentication(true)
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout")
.permitAll()
.and()
.rememberMe()
.and()
.rememberMe()
.key("uniqueAndSecret")
.userDetailsService(userService);
}
//Beans
#Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder(11);
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider auth = new DaoAuthenticationProvider();
auth.setUserDetailsService(userService);
auth.setPasswordEncoder(encoder());
return auth;
}
#Bean
public AuthenticationManager customAuthenticationManager() throws Exception {
return authenticationManager();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
}
Here:
#RequestMapping("/user")
public String userIndex() {
return "/index";
}
you do redirect to index (without '.html' extension), but there is no mapping for index url.
So you can change it to:
return "/index.html";
if you have index.html file in static directory.
I am attempting to make a web page that is only accessible by the 'Admin' role, however all users are able to access it. I have User and Role entities with a functioning ManyToMany relationship set up.
Here is my SecurityConfig.java:
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(
"/registration",
"/js/**",
"/css/**",
"/img/**",
"/webjars/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/competition/**").hasRole("Admin")
.and()
.logout()
.invalidateHttpSession(true)
.clearAuthentication(true)
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout")
.permitAll();
}
#Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authenticationProvider(){
DaoAuthenticationProvider auth = new DaoAuthenticationProvider();
auth.setUserDetailsService(userService);
auth.setPasswordEncoder(passwordEncoder());
return auth;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
as you can see with the line
.antMatchers("/competition/**").hasRole("Admin")
I'm trying to make link /competition/** admin-only.
Here is the controller:
#Controller
public class CompetitionController {
#Autowired
CompetitionRepository competitionRepository;
#GetMapping("/competition/{competitors}")
public String match(ModelMap map, #PathVariable(value = "competitors") String competitors, Principal principal) {
String[] parts = competitors.split("-");
String part1 = parts[0];
String part2 = parts[1];
map.addAttribute("part1", part1);
map.addAttribute("part2", part2);
return "match";
}
}
Finally here is my UserService:
#Service
public class UserServiceImpl implements UserService {
#Autowired
private UserRepository userRepository;
#Autowired
private BCryptPasswordEncoder passwordEncoder;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = userRepository.findByEmail(email);
if (user == null){
throw new UsernameNotFoundException("Invalid username or password.");
}
return new org.springframework.security.core.userdetails.User(user.getEmail(),
user.getPassword(),
mapRolesToAuthorities(user.getRoles()));
}
public User findByEmail(String email){
return userRepository.findByEmail(email);
}
public User save(UserRegistrationDto registration){
User user = new User();
user.setFirstName(registration.getFirstName());
user.setLastName(registration.getLastName());
user.setEmail(registration.getEmail());
user.setPassword(passwordEncoder.encode(registration.getPassword()));
user.setRoles(Arrays.asList(new Role("ROLE_USER")));
User userAdmin = userRepository.findByEmail("admin#email.com");
if (userAdmin == null){
userAdmin = new User();
userAdmin.setFirstName("Admin");
userAdmin.setLastName("");
userAdmin.setEmail("admin#email.com");
userAdmin.setPassword(passwordEncoder.encode("admin"));
userAdmin.setRoles(Arrays.asList(new Role("ROLE_Admin")));
userRepository.save(userAdmin);
}
return userRepository.save(user);
}
private Collection<? extends GrantedAuthority> mapRolesToAuthorities(Collection<Role> roles){
return roles.stream()
.map(role -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList());
}
}
I attempted to change .hasRole to .hasAuthority as seen in this answer (to no avail): Spring Security Java configuration for authenticated users with a role.
I'm currently trying to build a database UI implementing spring security, but i'm stuck on how to change the intercept url access from access=hasRole('ROLE_ADMIN') to access=denyAll and deny any user from accessing that particular page without having me to logout.
this is my WebSecurityConfig class:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Value("${users-by-username-query}")
private String usersQuery;
#Value("${authorities-by-username-query}")
private String authoritiesQuery;
#Autowired
private MyAuthenticationHandler myAuthenticationHandler;
private CustomAccessDecisionManager customAccessDecisionManager;
#Autowired
private Service service;
#Override
protected void configure(HttpSecurity http) throws Exception {
List<UrlRole> viewPermissions = service.findAllUrlRole();
System.out.println("Return from service class with size "+viewPermissions.size());
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry interceptUrlRegistry = http
// .authorizeRequests().antMatchers("/","/hello.html","/footer.jsp","/header.jsp","/sidebar.jsp","/reg_issuer.jsp","/reg_user.jsp","/rest/**","/IssuerList.jsp","/loginSecurity","/index.jsp","/verify_otp.jsp")
.authorizeRequests().antMatchers("/","/rest/**")
.permitAll();
for (int i = 0;i<viewPermissions.size();i++) {
String url = viewPermissions.get(i).getUrl();
String string = "";
if(viewPermissions.get(i).getRole().equalsIgnoreCase("denyAll")){
string = viewPermissions.get(i).getRole();
}else{
string = "hasRole('"+viewPermissions.get(i).getRole()+"')";
for (int j = 0;j<viewPermissions.size();j++) {
if(j!=i && viewPermissions.get(j).getUrl().equalsIgnoreCase(url) ){
string+=" or hasRole('"+viewPermissions.get(j).getRole()+"')";
}
}
}
interceptUrlRegistry.antMatchers(viewPermissions.get(i).getUrl()).access(string);
}
interceptUrlRegistry.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").successHandler(myAuthenticationHandler)
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and()
.logout()
.permitAll()
.and()
.exceptionHandling()
.accessDeniedPage("/403");
}
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery(usersQuery)
.authoritiesByUsernameQuery(authoritiesQuery);
}
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**", "/assets/**");
}
}
Currently i am looping the "antMatchers(viewPermissions.get(i).getUrl()).access(string)" to get the url and roles from database but it only been done when the first time i deploy it in wildfly. That is why the new access for url will not be implemented unless i restart the wildfly server.
Is there anyway to implement it without having to restart the server?
THE ANSWERED I GOT AND WORKS FOR ME IS AS BELOW.
this is my new WebSecurityConfig class:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Value("${users-by-username-query}")
private String usersQuery;
#Value("${authorities-by-username-query}")
private String authoritiesQuery;
#Autowired
private MyAuthenticationHandler myAuthenticationHandler;
#Autowired
private Service service;
#Override
protected void configure(HttpSecurity http) throws Exception {
List<UrlRole> viewPermissions = service.findAllUrlRole();
System.out.println("Return from service class with size "+viewPermissions.size());
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry interceptUrlRegistry = http
.authorizeRequests().antMatchers("/rest/**")
.permitAll();
interceptUrlRegistry.antMatchers("/login").access("hasRole('ROLE_ANONYMOUS')");
interceptUrlRegistry.anyRequest().authenticated().accessDecisionManager(accessDecisionManager())
.and()
.formLogin()
.loginPage("/login").successHandler(myAuthenticationHandler)
.usernameParameter("username")
.passwordParameter("password")
.and()
.logout()
.permitAll()
.and()
.exceptionHandling()
.accessDeniedPage("/403");
}
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery(usersQuery)
.authoritiesByUsernameQuery(authoritiesQuery);
}
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**", "/assets/**","/rest/findAllUrlRole","/error","/403","/404","/500");
}
#SuppressWarnings("unchecked")
#Bean
public AccessDecisionManager accessDecisionManager() {
System.out.println("Arrive AccessDecisionManager");
List<AccessDecisionVoter<? extends Object>> decisionVoters
= Arrays.asList(
new WebExpressionVoter(),
new RoleVoter(),
new AuthenticatedVoter(),
new MinuteBasedVoter());
System.out.println("End of AccessDecisionManager: "+ decisionVoters);
return new UnanimousBased(decisionVoters);
}
}
this is my MinuteBasedVoter class:
#SuppressWarnings("rawtypes")
public class MinuteBasedVoter implements AccessDecisionVoter {
#Override
public int vote(
Authentication authentication, Object object, Collection collection) {
WebServiceTester a = new WebServiceTester();
String username = authentication.getName(); //to get current user role
String url = ((FilterInvocation) object).getRequestUrl(); // to get current url
boolean NONanonymous = true;
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority grantedAuthority : authorities) {
if(grantedAuthority.getAuthority().equalsIgnoreCase("ROLE_ANONYMOUS")){
NONanonymous = false;
}
}
int vote = ACCESS_ABSTAIN;
boolean NONexist = true;
if(NONanonymous){
List<Role> roles = new ArrayList<Role>();
Role role = new Role();
vote = ACCESS_DENIED;
try{
List<UrlRole> urlroles = a.findAllUrlRole(); // to get all url and its respective role
// below is how i match the role of current user and the role that can access the current url
for(int i = 0; i<urlroles.size();i++){
if(url.startsWith(urlroles.get(i).getUrl())){
NONexist = false;
System.out.println("URL: "+url+" , Role: "+urlroles.get(i).getRole());
role.setRole(urlroles.get(i).getRole());
roles.add(role);
for (GrantedAuthority grantedAuthority : authorities) {
if(grantedAuthority.getAuthority().equalsIgnoreCase(urlroles.get(i).getRole())){
vote = ACCESS_GRANTED;
}
}
}
}
}catch(Exception e){
System.out.println("Error at MinuteBasedVoter: "+e);
}
if(NONexist){
vote = ACCESS_GRANTED;
}
}
return vote;
}
#Override
public boolean supports(ConfigAttribute attribute) {
// TODO Auto-generated method stub
return true;
}
#Override
public boolean supports(Class clazz) {
// TODO Auto-generated method stub
return true;
}
}
i got this solution from http://www.baeldung.com/spring-security-custom-voter but with a twist of my own.
I have an event listener, which when it receive the event that the user has changed their password invalidates the session. This is the code:
#Component
public class UserListener {
private static Logger logger = LoggerFactory.getLogger(UserListener.class);
#Autowired
private SessionRegistry sessionRegistry;
#EventListener
public void handleChangePasswordEvent(ChangePasswordEvent event) {
logger.info("handleChangePasswordEvent for : " + event.getUsername());
List<Object> loggedUsers = sessionRegistry.getAllPrincipals();
logger.info("loggedUsers : " + loggedUsers.size());
for (Object principal : loggedUsers) {
if (principal instanceof User) {
final User loggedUser = (User) principal;
logger.info("loggedUser : " + loggedUser.getUsername());
if (event.getUsername().equals(loggedUser.getUsername())) {
List<SessionInformation> sessionsInfo = sessionRegistry.getAllSessions(principal, false);
if (null != sessionsInfo && sessionsInfo.size() > 0) {
for (SessionInformation sessionInformation : sessionsInfo) {
logger.info("Exprire now :" + sessionInformation.getSessionId());
sessionInformation.expireNow();
sessionRegistry.removeSessionInformation(sessionInformation.getSessionId());
// User is not forced to re-logging
}
}
}
}
}
}
}
The listener works fine, the problem is that the list of authenticated users that returns me sessionRegistry is empty.
I've tried all the solutions I've seen for the same problem and they have not worked for me.
Here I put all the configuration of Spring Security.
#Configuration
#EnableGlobalMethodSecurity(securedEnabled = true)
#EnableWebSecurity
#ComponentScan(value = { "security" })
public class SecurityConfig extends GlobalAuthenticationConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private CustomLogoutHandler logoutHandler;
#Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
#Autowired
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationEventPublisher(authenticationEventPublisher())
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
public DefaultAuthenticationEventPublisher authenticationEventPublisher() {
return new DefaultAuthenticationEventPublisher();
}
/**
* Security Configuration for Admin zone
*/
#Configuration
#Order(1)
public class AdminConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationSuccessHandler successHandler;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**")
.hasAuthority(AuthorityEnum.ROLE_ADMIN.name())
.and()
.formLogin()
.loginPage("/admin/login")
.usernameParameter("username")
.passwordParameter("password")
.successHandler(successHandler)
.permitAll()
.and()
.logout()
.addLogoutHandler(logoutHandler)
.logoutRequestMatcher(new AntPathRequestMatcher("/admin/logout"))
.logoutSuccessUrl("/admin/login?logout")
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
.and()
.exceptionHandling().accessDeniedPage("/403")
.and()
.csrf()
.disable();
}
}
/**
* Security Configuration for Frontend zone
*/
#Configuration
#Order(2)
public class FrontendConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/*.*")
.permitAll()
.and()
.formLogin()
.loginPage("/login")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and()
.logout()
.addLogoutHandler(logoutHandler)
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout")
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
.and()
.exceptionHandling().accessDeniedPage("/403")
.and()
.csrf();
}
}
#Configuration
#Order(3)
public class GlobalWebConfiguration extends WebSecurityConfigurerAdapter {
private SessionRegistry sessionRegistry;
#Autowired
private MessageSource messageSource;
#Override
public void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionAuthenticationStrategy(compositeSessionAuthenticationStrategy())
.sessionFixation()
.changeSessionId()
.maximumSessions(1)
.maxSessionsPreventsLogin(true)
.expiredUrl("/login?expired")
.sessionRegistry(sessionRegistry())
.and()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.invalidSessionUrl("/");
// Here we protect site from:
// 1. X-Content-Type-Options
http.headers().contentTypeOptions();
// 2. Web Browser XSS Protection
http.headers().xssProtection();
http.headers().cacheControl();
http.headers().httpStrictTransportSecurity();
// 3. X-Frame-Options
http.headers().frameOptions();
}
#Bean
public SessionRegistry sessionRegistry() {
if (sessionRegistry == null) {
sessionRegistry = new SessionRegistryImpl();
}
return sessionRegistry;
}
#Bean
#Order(1)
public ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlAuthenticationStrategy() {
ConcurrentSessionControlAuthenticationStrategy strategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry());
strategy.setExceptionIfMaximumExceeded(true);
strategy.setMessageSource(messageSource);
return strategy;
}
#Bean
#Order(2)
public SessionFixationProtectionStrategy sessionFixationProtectionStrategy(){
return new SessionFixationProtectionStrategy();
}
#Bean
#Order(3)
public RegisterSessionAuthenticationStrategy registerSessionAuthenticationStrategy(){
RegisterSessionAuthenticationStrategy registerSessionAuthenticationStrategy = new RegisterSessionAuthenticationStrategy(sessionRegistry());
return registerSessionAuthenticationStrategy;
}
#Bean
public CompositeSessionAuthenticationStrategy compositeSessionAuthenticationStrategy(){
List<SessionAuthenticationStrategy> sessionAuthenticationStrategies = new ArrayList<>();
sessionAuthenticationStrategies.add(concurrentSessionControlAuthenticationStrategy());
sessionAuthenticationStrategies.add(sessionFixationProtectionStrategy());
sessionAuthenticationStrategies.add(registerSessionAuthenticationStrategy());
CompositeSessionAuthenticationStrategy compositeSessionAuthenticationStrategy = new CompositeSessionAuthenticationStrategy(sessionAuthenticationStrategies);
return compositeSessionAuthenticationStrategy;
}
}
}
I have a WebSecurityConfigurerAdapter for the admin zone with its own login page and another for normal users.
At the end is GlobalWebConfiguration to configure the session manager for both zones (admin and users).
Hope someone can help me
I'm using spring security in my web application, and now I want to have a list of all users who are logged in my program.
How can I have access to that list? Aren't they already kept somewhere within spring framework? Like SecurityContextHolder or SecurityContextRepository?
For accessing the list of all logged in users you need to inject SessionRegistry instance to your bean.
#Autowired
#Qualifier("sessionRegistry")
private SessionRegistry sessionRegistry;
And then using injcted SessionRegistry you can access the list of all principals:
List<Object> principals = sessionRegistry.getAllPrincipals();
List<String> usersNamesList = new ArrayList<String>();
for (Object principal: principals) {
if (principal instanceof User) {
usersNamesList.add(((User) principal).getUsername());
}
}
But before injecting session registry you need to define session management part in your spring-security.xml (look at Session Management section in Spring Security reference documentation) and in concurrency-control section you should set alias for session registry object (session-registry-alias) by which you will inject it.
<security:http access-denied-page="/error403.jsp" use-expressions="true" auto-config="false">
<security:session-management session-fixation-protection="migrateSession" session-authentication-error-url="/login.jsp?authFailed=true">
<security:concurrency-control max-sessions="1" error-if-maximum-exceeded="true" expired-url="/login.html" session-registry-alias="sessionRegistry"/>
</security:session-management>
...
</security:http>
In JavaConfig, it would look like this:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
// ...
http.sessionManagement().maximumSessions(1).sessionRegistry(sessionRegistry());
}
#Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
#Bean
public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
return new ServletListenerRegistrationBean<HttpSessionEventPublisher>(new HttpSessionEventPublisher());
}
}
With the calling code looking like this:
public class UserController {
#Autowired
private SessionRegistry sessionRegistry;
public void listLoggedInUsers() {
final List<Object> allPrincipals = sessionRegistry.getAllPrincipals();
for(final Object principal : allPrincipals) {
if(principal instanceof SecurityUser) {
final SecurityUser user = (SecurityUser) principal;
// Do something with user
System.out.println(user);
}
}
}
}
Note that SecurityUser is my own class which implements UserDetails.
Please correct me if I'm wrong.
I think #Adam's answer is incomplete. I noticed that sessions already expired in the list were appearing again.
public class UserController {
#Autowired
private SessionRegistry sessionRegistry;
public void listLoggedInUsers() {
final List<Object> allPrincipals = sessionRegistry.getAllPrincipals();
for (final Object principal : allPrincipals) {
if (principal instanceof SecurityUser) {
final SecurityUser user = (SecurityUser) principal;
List<SessionInformation> activeUserSessions =
sessionRegistry.getAllSessions(principal,
/* includeExpiredSessions */ false); // Should not return null;
if (!activeUserSessions.isEmpty()) {
// Do something with user
System.out.println(user);
}
}
}
}
}
Hope it helps.
Please correct me if I'm wrong too.
I think #Adam's and #elysch`s answer is incomplete. I noticed that there are needed to add listener:
servletContext.addListener(HttpSessionEventPublisher.class);
to
public class AppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) {
...
servletContext.addListener(HttpSessionEventPublisher.class);
}
with security conf:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
// ...
http.sessionManagement().maximumSessions(1).sessionRegistry(sessionRegistry());
}
#Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
#Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
}
And then you will get current online users!
You need to inject SessionRegistry (as mentioned eariler) and then you can do it in one pipeline like this:
public List<UserDetails> findAllLoggedInUsers() {
return sessionRegistry.getAllPrincipals()
.stream()
.filter(principal -> principal instanceof UserDetails)
.map(UserDetails.class::cast)
.collect(Collectors.toList());
}
Found this note to be quite important and relevant:
"[21] Authentication by mechanisms which perform a redirect after
authenticating (such as form-login) will not be detected by
SessionManagementFilter, as the filter will not be invoked during the
authenticating request. Session-management functionality has to be
handled separately in these cases."
https://docs.spring.io/spring-security/site/docs/3.1.x/reference/session-mgmt.html#d0e4399
Also, apparently a lot of people have troubles getting sessionRegistry.getAllPrincipals() returning something different from an empty array. In my case, I fixed it by adding the sessionAuthenticationStrategy to my custom authenticationFilter:
#Bean
public CustomUsernamePasswordAuthenticationFilter authenticationFilter() throws Exception {
...
authenticationFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy());
}
#Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
//cf. https://stackoverflow.com/questions/32463022/sessionregistry-is-empty-when-i-use-concurrentsessioncontrolauthenticationstrate
public SessionAuthenticationStrategy sessionAuthenticationStrategy() {
List<SessionAuthenticationStrategy> stratList = new ArrayList<>();
SessionFixationProtectionStrategy concStrat = new SessionFixationProtectionStrategy();
stratList.add(concStrat);
RegisterSessionAuthenticationStrategy regStrat = new RegisterSessionAuthenticationStrategy(sessionRegistry());
stratList.add(regStrat);
CompositeSessionAuthenticationStrategy compStrat = new CompositeSessionAuthenticationStrategy(stratList);
return compStrat;
}
Similar to #rolyanos solution, mine for me always works:
- for the controller
#RequestMapping(value = "/admin")
public String admin(Map<String, Object> model) {
if(sessionRegistry.getAllPrincipals().size() != 0) {
logger.info("ACTIVE USER: " + sessionRegistry.getAllPrincipals().size());
model.put("activeuser", sessionRegistry.getAllPrincipals().size());
}
else
logger.warn("EMPTY" );
logger.debug(log_msg_a + " access ADMIN page. Access granted." + ANSI_RESET);
return "admin";
}
- for the front end
<tr th:each="activeuser, iterStat: ${activeuser}">
<th><b>Active users: </b></th> <td align="center" th:text="${activeuser}"></td>
</tr>
- for spring confing
#Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
#Bean
public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
return new ServletListenerRegistrationBean<HttpSessionEventPublisher>(new HttpSessionEventPublisher());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.logout()
.logoutSuccessUrl("/home")
.logoutUrl("/logout")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID");
http.authorizeRequests()
.antMatchers("/", "/home")
.permitAll()
.antMatchers("/admin")
.hasRole("ADMIN")
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/home")
.defaultSuccessUrl("/main")
.permitAll()
.and()
.logout()
.permitAll();
http.sessionManagement().maximumSessions(1).sessionRegistry(sessionRegistry());
http.authorizeRequests().antMatchers("/webjars/**").permitAll();
http.exceptionHandling().accessDeniedPage("/403");
}