Users Online Using Spring Security

I am using Spring Security and I would like to know which users are online now. At first I tried the approach using SessionRegistryImpl and <session-management session-authentication-strategy-ref="..." ... /> , but I think this list is stored in memory, and I would like to avoid it (it will be a huge website, and many users will be simultaneously online, the List can become huge). Please correct me if I am wrong.

The second approach I tried is to use the listener and the HttpSessionListener interface and the user AuthenticationManager and store the "online flag" in the database. Basically, the flag is set to true in the authenticate(...) method of my authentication manager and set to false in the sessionDestroyed(...) method of my session listener.

web.xml:

 <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <display-name>Test</display-name> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/security.xml</param-value> </context-param> <!-- Security --> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <listener> <listener-class>my.package.SessionListener</listener-class> </listener> <servlet> <servlet-name>test</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>test</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <session-config> <session-timeout>1</session-timeout> </session-config> </web-app> 

Spring Security Configuration:

 <beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd"> <beans:bean id="authenticationManager" class="my.package.security.AuthenticationManager" /> <http disable-url-rewriting="true" authentication-manager-ref="authenticationManager"> <!--<intercept-url pattern="/" access="ROLE_ANONYMOUS" />--> <intercept-url pattern="/login*" access="ROLE_ANONYMOUS" /> <intercept-url pattern="/favicon.ico" access="ROLE_ANONYMOUS" /> <intercept-url pattern="/*" access="ROLE_USER" /> <form-login login-processing-url="/authorize" login-page="/login" authentication-failure-url="/login-failed" /> <logout logout-url="/logout" logout-success-url="/login" /> <remember-me data-source-ref="dataSource" /> </http> </beans:beans> 

my.package.SessionListener:

 public class SessionListener implements HttpSessionListener { public void sessionCreated(HttpSessionEvent httpSessionEvent) { } public void sessionDestroyed(HttpSessionEvent httpSessionEvent) { UserJpaDao userDao = WebApplicationContextUtils.getWebApplicationContext(httpSessionEvent.getSession().getServletContext()).getBean(UserJpaDao.class); Authentication a = SecurityContextHolder.getContext().getAuthentication(); if(a != null) { User loggedInUser = userDao.findByAlias(a.getName()); if(loggedInUser != null) { loggedInUser.setOnline(false); userDao.save(loggedInUser); } } } } 

my.package.security.AuthenticationManager:

 public class AuthenticationManager implements org.springframework.security.authentication.AuthenticationManager { @Autowired UserJpaDao userDao; public Authentication authenticate(Authentication authentication) throws AuthenticationException { User loggedInUser = null; Collection<? extends GrantedAuthority> grantedAuthorities = null; ... loggedInUser = userDao.findByAlias(authentication.getName()); if(loggedInUser != null) { // Verify password etc. loggedInUser.setOnline(true); userDao.save(loggedInUser); } else { loggedInUser = null; throw new BadCredentialsException("Unknown username"); } return new UsernamePasswordAuthenticationToken(loggedInUser, authentication.getCredentials(), grantedAuthorities); } } 

sessionCreated and sessionDestroyed start correctly, but SecurityContextHolder.getContext().getAuthentication(); always null.

Update: almost everything works fine. The only problem is that when the session expires due to a timeout, SecurityContextHolder.getContext().getAuthentication() returns null in the sessionDestroyed(...) method. It works great when logging out manually.

Can someone help me? Any hints are welcome.

thanks

+3
spring spring-security session
source share
2 answers

I decided to apply the speech session method (only because I could not get another method to work). Here is my code (important parts).

web.xml:

 <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <display-name>Test</display-name> <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/applicationContext.xml /WEB-INF/security.xml </param-value> </context-param> ... <!-- Security --> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <listener> <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class> </listener> <servlet> <servlet-name>test</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>test</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <session-config> <session-timeout>1</session-timeout> </session-config> </web-app> 

security.xml:

 <beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd"> <beans:bean id="authenticationManager" class="my.package.security.AuthenticationManager" /> <beans:bean id="userDetailsDao" class="my.package.dao.UserDetailsDao" /> <http disable-url-rewriting="true" authentication-manager-ref="authenticationManager"> <intercept-url pattern="/login*" access="ROLE_ANONYMOUS" /> <intercept-url pattern="/favicon.ico" access="ROLE_ANONYMOUS" /> <intercept-url pattern="/*" access="ROLE_USER" /> <form-login login-processing-url="/authorize" login-page="/login" authentication-failure-url="/login-failed" /> <logout logout-url="/logout" logout-success-url="/login" /> <remember-me data-source-ref="dataSource" user-service-ref="userDetailsDao" /> <session-management session-authentication-strategy-ref="sas" invalid-session-url="/invalid-session" /> </http> <beans:bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl"/> <beans:bean id="sas" class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy"> <beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" /> <beans:property name="maximumSessions" value="1" /> </beans:bean> </beans:beans> 

my.package.security.AuthenticationManager:

 public class AuthenticationManager implements org.springframework.security.authentication.AuthenticationManager { @Autowired UserJpaDao userDao; public Authentication authenticate(Authentication authentication) throws AuthenticationException { UserDetails userDetails = null; if(authentication.getPrincipal() == null || authentication.getCredentials() == null) { throw new BadCredentialsException("Invalid username/password"); } User loggedInUser = userDao.findByAlias(authentication.getName()); if(loggedInUser != null) { // TODO: check credentials userDetails = new UserDetails(loggedInUser); } else { loggedInUser = null; throw new BadCredentialsException("Unknown username"); } return new UsernamePasswordAuthenticationToken(userDetails, authentication.getCredentials(), userDetails.getAuthorities()); } } 

my.package.dao.UserDetailsDao (this is only necessary for the mem-me function):

 public class UserDetailsDao implements UserDetailsService { @Autowired UserJpaDao userDao; public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userDao.findByAlias(username); if(user != null) { return new UserDetails(user); } throw new UsernameNotFoundException("The specified user cannot be found"); } } 

my.package.UserDetails:

 public class UserDetails implements org.springframework.security.core.userdetails.UserDetails { private String alias; private String encryptedPassword; public UserDetails(User user) { this.alias = user.getAlias(); this.encryptedPassword = user.getEncryptedPassword(); } @Override public Collection<? extends GrantedAuthority> getAuthorities() { ArrayList<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>(); authorities.add(new SimpleGrantedAuthority("ROLE_USER")); return authorities; } @Override public String getPassword() { return this.encryptedPassword; } @Override public String getUsername() { return this.alias; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } } 

sessionRegistry.getAllPrincipals() will return List<Object> "castable" in List<UserDetails> .

My code is to get a list of online users where class User objects are stored in the database via JPA:

 List<User> onlineUsers = userDao.findByListOfUserDetails((List<UserDetails>)(List<?>)sessionRegistry.getAllPrincipals()); 

Note. sessionRegistry is an implementation using the Autowired class of SessionRegistryImpl .

Note: for the mem-me function, I use a persistent approach to tokens. A persistent_logins needed in the database (see 10.3 Persistent approach to tokens ).

Hope this can be helpful to someone else.

+1
source share

You cannot use SecurityContextHolder to get the Principal in your SessionListener , since it is only valid in the context of the request.

All the necessary information is in the session itself.

Example:

 @Override public void sessionDestroyed(HttpSessionEvent se) { HttpSession session = se.getSession(); SecurityContext context = (SecurityContext)session.getAttribute("SPRING_SECURITY_CONTEXT"); Authentication authentication = context.getAuthentication(); Object principal = authentication.getPrincipal(); // Your code goes here } 
+5
source share

All Articles