Session Timeout is not working Spring Boot? - spring

I have set the following property
server.servlet.session.timeout=30s
in my application properties but the session time out is not triggerd.
but after setting
server.servlet.session.cookie.max-age=30s
the session time out got trigger but following code for updating logout time is not getting triggerd.
#Component
public class LogoutListener implements ApplicationListener<SessionDestroyedEvent> {
#Override
public void onApplicationEvent(SessionDestroyedEvent event)
{
List<SecurityContext> lstSecurityContext = event.getSecurityContexts();
UserDetails ud;
for (SecurityContext securityContext : lstSecurityContext)
{
ud = (UserDetails) securityContext.getAuthentication().getPrincipal();
us.findAllUsersByEmail(ud.getUsername()).get(0).setLastLogout(LocalDateTime.now());
System.out.println("lastloginspec : " + ud.getUsername() + " : 00 : " + LocalDateTime.now());
}
}
}
#Bean
public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
return new ServletListenerRegistrationBean<HttpSessionEventPublisher>(new HttpSessionEventPublisher());
}
Could any one Help me out ?

I have implemented the session listener by following way.
Create a custom http session listener.
#Component
public class CustomHttpSessionListener implements HttpSessionListener{
private static final Logger LOG= LoggerFactory.getLogger(Test.class);
#Override
public void sessionCreated(HttpSessionEvent se) {
LOG.info("New session is created.");
UserPrincipal principal = (UserPrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
#Override
public void sessionDestroyed(HttpSessionEvent se) {
LOG.info("Session destroyed.");
UserPrincipal principal = (UserPrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}}
Invoke new ServletListenerRegistrationBean and add CustomHttpListener to it and annotate it as #Bean.
#Autowired private CustomHttpSessionListener customHttpSessionListener;
#Bean
public ServletListenerRegistrationBean<CustomSessionListner>sessionListenerWithMetrics() { ServletListenerRegistrationBean<CustomSessionListner>
listenerRegBean = new ServletListenerRegistrationBean<>();
listenerRegBean.setListener(customHttpSessionListener);
return listenerRegBean;
}
Adding a property to application.properties
server.servlet.session.timeout = 15m

This is not a full answer, but a step to isolate and troubleshoot. Replace your LogoutListener with and see when you start the application if it is printing any events. If it is not printing your issue is not specific SessionDestroyedEvent instead generic to your listener.
#Component
public class LogoutListener
implements ApplicationListener<ApplicationEvent> {
#Override
public void onApplicationEvent(ApplicationEvent event)
{
System.out.println("event caught at LogoutListener: " + event);
}
}
And also add this to application.properties to see if event is fired as it should log Publishing event:
logging.level.org.springframework.security.web.session.HttpSessionEventPublisher=DEBUG

Related

EventListener don't catch events

I have problem with listening to AuthenticationSuccessEvent and AuthenticationFailureBadCredentialsEvent, none of this event is triggered. Here's my code:
#Component
#RequiredArgsConstructor
public class AuthenticationFailureListener {
private final LoginAttemptService loginAttemptService;
#EventListener
public void onAuthenticationFailure(AuthenticationFailureBadCredentialsEvent event) {
Object principal = event.getAuthentication().getPrincipal();
if (principal instanceof String) {
String username = (String) principal;
loginAttemptService.addUserLoginAttemptCache(username);
}
System.out.println("onAuthenticationFailure");
}
}
Second class
#Component
#RequiredArgsConstructor
public class AuthenticationSuccessListener {
private final LoginAttemptService loginAttemptService;
#EventListener
public void onAuthenticationSuccess(AuthenticationSuccessEvent event) {
Object principal = event.getAuthentication().getPrincipal();
if (principal instanceof UserPrincipal) {
UserPrincipal user = (UserPrincipal) principal;
loginAttemptService.evictUserFromLoginAttemptCache(user.getUsername());
}
System.out.println("onAuthenticationSuccess");
}
}
and I added this to Security Configuration
#Bean
public AuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
return new DefaultAuthenticationEventPublisher(applicationEventPublisher);
}
But my fail and successful login don't trigger any listener :disappointed: I'm using spring security 5.7.6. I've checked and all these beans are created. What's maybe a reason for that behavior ?

Get session events from Hazelcast session repository?

I'm not getting session closed or expired events when using embedded Hazelcast session repository in Spring boot application. I do get session creation events. I have a very short timeout for the sessions (30s). I have verified that the session gets expired after 30s by getting "unauthorized" reply from the server. How do you receive session expiration/destruction events?
This is my session configuration:
#Configuration
#EnableHazelcastHttpSession(maxInactiveIntervalInSeconds = 30)
public class SessionConfiguration{
#Bean
#SpringSessionHazelcastInstance
public HazelcastInstance hazelcastInstance() {
Config config = new Config();
config.setClusterName("spring-session-cluster");
// Add this attribute to be able to query sessions by their PRINCIPAL_NAME_ATTRIBUTE's
AttributeConfig attributeConfig = new AttributeConfig()
.setName(Hazelcast4IndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
.setExtractorClassName(Hazelcast4PrincipalNameExtractor.class.getName());
// Configure the sessions map
config.getMapConfig(Hazelcast4IndexedSessionRepository.DEFAULT_SESSION_MAP_NAME)
.addAttributeConfig(attributeConfig).addIndexConfig(
new IndexConfig(IndexType.HASH, Hazelcast4IndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE));
// Use use custom serializer to de/serialize sessions faster. This is optional.
SerializerConfig serializerConfig = new SerializerConfig();
serializerConfig.setImplementation(new HazelcastSessionSerializer()).setTypeClass(MapSession.class);
config.getSerializationConfig().addSerializerConfig(serializerConfig);
return Hazelcast.newHazelcastInstance(config);
}
#Bean
public SessionRepositoryCustomizer<Hazelcast4IndexedSessionRepository> customize() {
return (sessionRepository) -> {
sessionRepository.setFlushMode(FlushMode.IMMEDIATE);
sessionRepository.setSaveMode(SaveMode.ALWAYS);
sessionRepository.setSessionMapName(Hazelcast4IndexedSessionRepository.DEFAULT_SESSION_MAP_NAME);
sessionRepository.setDefaultMaxInactiveInterval(30); //this is extra; tried with and without
};
}
}
And this is my listener:
#Component
public class SessionListener {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SessionListener.class);
#EventListener
public void sessionCreated(SessionCreatedEvent event) {
log.info("SESSION:CREATE:ID="+event.getSessionId()); //only this gets called but none of the others
}
#EventListener
public void sessionDeleted(SessionDeletedEvent event) {
log.info("SESSION:DELETE:ID="+event.getSessionId());
}
#EventListener
public void sessionDestroyed(SessionDestroyedEvent event) {
log.info("SESSION:DESTROY:ID="+event.getId());
}
#EventListener
public void sessionExpired(SessionExpiredEvent event) {
log.info("SESSION:EXPIRE:ID="+event.getSessionId());
}
}
Partial answer (I don't know exactly why it works):
If you add a session map listener to HazelcastInstance (in the creating bean) you suddenly start receiving SessionExpiredEvents.
So replace lines:
config.getMapConfig(Hazelcast4IndexedSessionRepository.DEFAULT_SESSION_MAP_NAME)
.addAttributeConfig(attributeConfig).addIndexConfig(
new IndexConfig(IndexType.HASH, Hazelcast4IndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE));
with (set maximum idle seconds to the session map configuration):
config.getMapConfig(Hazelcast4IndexedSessionRepository.DEFAULT_SESSION_MAP_NAME)
.addAttributeConfig(attributeConfig).addIndexConfig(
new IndexConfig(IndexType.HASH, Hazelcast4IndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE))
.setMaxIdleSeconds(tout);
and
return Hazelcast.newHazelcastInstance(config);
with (add session entry listener to the session map)
HazelcastInstance instance = Hazelcast.newHazelcastInstance(config);
IMap<Object, Object> map = instance.getMap( Hazelcast4IndexedSessionRepository.DEFAULT_SESSION_MAP_NAME );
map.addEntryListener( new HazelcastSessionEntryListener(), true );
return instance;
where HazelcastSessionEntryListener can be defined like this:
#Component
public class HazelcastSessionEntryListener implements EntryListener<Object, Object>
{
public HazelcastSessionEntryListener(){}
#Override
public void entryAdded(EntryEvent<Object, Object> event){}
#Override
public void entryUpdated(EntryEvent<Object, Object> event){}
#Override
public void entryRemoved(EntryEvent<Object, Object> event){}
#Override
public void entryEvicted(EntryEvent<Object, Object> event){}
#Override
public void entryExpired(EntryEvent<Object, Object> event){}
#Override
public void mapCleared(MapEvent event){}
#Override
public void mapEvicted(MapEvent event){}
}
Funny thing is that HazelcastSessionEntryListener is just an empty implementation in my case (it doesn't do anything). Seems like buggy behaviour (but I'm not a Spring expert).

How to use Spring boot AutoWired and ScheduledExecutorService?

I need to use autowired in more than one class with ScheduledExecutorService, what I have tried is shown in this code. logging size of User list in below example always shows 0, even after user added to arraylist. How to properly use Autowired and ScheduledExecutorService in spring boot?
#Component
public class AnotherClass {
List<User> users = new ArrayList();
public void addUser(User user){
users.add(user);
}
public void logUsers(){
logger.info("User size " + users.size()); <================= Always logs 0, when called from executor
}
}
#RestController
public class SecondClass {
#Autowired
private AnotherClass anotherClass;
#GetMapping(value="/user/test")
public void logUsers(){
anotherClass.addUser(new User());
}
}
Application Class
#Component
#SpringBootApplication
public class SpringBootDemoApplication {
private ScheduledExecutorService exec = Executors.newScheduledThreadPool(1);
#Autowired
private AnotherClass anotherClass;
#PostConstruct
public void init() {
logger();
}
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
public void logger(){
exec.scheduleAtFixedRate(new Runnable(){
#Override
public void run(){
try {
anotherClass.logUsers();
}catch (Exception e){
}
}
}, 2000, 1000, TimeUnit.MILLISECONDS);
}
}
The code works if you use the Spring #Autowired and not the #AutoWired Annotation.

Spring `#Autowire` field is `null` eventhough it works fine in other classes

Spring #Autowire field is null even though it works fine in other classes successfully.
public class SendRunner implements Runnable {
private String senderAddress;
#Autowired
private SubscriberService subscriberService;
public SendRunner(String senderAddress) {
this.senderAddress = senderAddress;
}
#Override
public void run() {
sendRequest();
}
private void sendRequest() {
try {
HashMap<String, String> dataMap = new HashMap<>();
dataMap.put("subscriberId", senderAddress);
HttpEntity<?> entity = new HttpEntity<Object>(dataMap, httpHeaders);
Subscriber subscriber = subscriberService.getSubscriberByMsisdn(senderAddress);
} catch (Exception e) {
logger.error("Error occurred while trying to send api request", e);
}
}
Also this class is managed as a bean in the dispatcher servlet :
<bean id="SendRunner" class="sms.dating.messenger.connector.SendRunner">
</bean>
In here i'm getting a null pointer exception for subscriberService. What would be the possible reason for this? Thanks in advance.
Can you please try with below code snippet
#Configuration
public class Someclass{
#Autowired
private SubscriberService subscriberService;
Thread subscriberThread = new Thread() {
#Override
public void run() {
try {
HashMap<String, String> dataMap = new HashMap<>();
dataMap.put("subscriberId", senderAddress);
HttpEntity<?> entity = new HttpEntity<Object>(dataMap, httpHeaders);
Subscriber subscriber = subscriberService.getSubscriberByMsisdn(senderAddress);
} catch (Exception e) {
logger.error("Error occurred while trying to send api request", e);
}
}
};
}
Can you please annotate your SendRunner class with #Component or #Service and include the SendRunner package in componentscanpackage
Your bean not in Spring Managed context, below can be the reasons.
Package sms.dating.messenger.connector not in Component scan.
You are moving out of the Spring context by creating an object with new (see below),
this way you will not get the autowired fields.
SendRunner sendRunner = new SendRunner () ,
sendRunner.sendRequest();
Just check how I implement. Hope this will help.
#RestController
public class RestRequest {
#Autowired
SendRunner sendRunner;
#RequestMapping("/api")
public void Uri() {
sendRunner.start();
}
}
SendRunner class
#Service
public class SendRunner extends Thread{
#Autowired
private SubscriberService subscriberService;
#Override
public void run() {
SendRequest();
}
private void SendRequest() {
System.out.println("Object is " + subscriberService);
String senderAddress = "address";
subscriberService.getSubscriberByMsisdn(senderAddress);
}
}
Below are the logs printed when I hit the REST api.
Object is com.example.demo.SubscriberService#40f33492

Change Multitenancy sessions manually

I need to create a multitenanacy application with ability to switch between schemas inside my java-code (not based on a user request).
I've read articles:
https://fizzylogic.nl/2016/01/24/make-your-spring-boot-application-multi-tenant-aware-in-2-steps/
http://www.greggbolinger.com/tenant-per-schema-with-spring-boot/
Solution works fine, when the schema is passed in Rest-request.
However I need to implement the following logic:
public void compare(String originalSchema, String secondSchema){
TenantContext.setCurrentTenant(originalSchema);
List<MyObject> originalData = myRepository.findData();
TenantContext.setCurrentTenant(secondSchema);
List<MyObject> migratedData = myRepository.findData();
}
The point is, that connection is not switched, when I manually set up TenenantContext. MultiTenantConnectionProviderImpl.getConnection is invoked only on the first call to my repository.
#Component
public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider {
#Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
final Connection connection = getAnyConnection();
try {
connection.createStatement().execute( "ALTER SESSION SET CURRENT_SCHEMA = " + tenantIdentifier );
}
catch ( SQLException e ) {
throw new HibernateException(
"Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]",e);
}
return connection;
}
}
Is it possible to force switching sessions?
Found a hard-coded solution.
#Service
public class DatabaseSessionManager {
#PersistenceUnit
private EntityManagerFactory entityManagerFactory;
public void bindSession() {
if (!TransactionSynchronizationManager.hasResource(entityManagerFactory)) {
EntityManager entityManager = entityManagerFactory.createEntityManager();
TransactionSynchronizationManager.bindResource(entityManagerFactory, new EntityManagerHolder(entityManager));
}
}
public void unbindSession() {
EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager
.unbindResource(entityManagerFactory);
EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
}
}
Each block, loading data in a new tenantContext should execute the following:
databaseSessionManager.unbindSession();
TenantContext.setCurrentTenant(schema);
databaseSessionManager.bindSession();
//execute selects
Well, you need it
public interface Service {
List<MyObject> myObjects();
}
#Service
#Transactional(propagation = Propagation.REQUIRES_NEW)
public class ServiceImpl implements Service {
#Autowired
private MyRepository myRepository;
#Override
public List<MyObject> myObjects() {
return myRepository.findData();
}
}
#Service
public class AnotherService() {
#Autowired
private Service service;
public void compare(String originalSchema, String secondSchema){
TenantContext.setCurrentTenant(originalSchema);
List<MyObject> originalData = service.myObjects();
TenantContext.setCurrentTenant(secondSchema);
List<MyObject> migratedData = service.myObjects();
}
}
Try using
spring.jpa.open-in-view=false
in your application.properties file.
More info on this
What is this spring.jpa.open-in-view=true property in Spring Boot?
Hope this helps..

Resources