How to set time to live for session atribute in spring - spring

I add my object to the session like this:
session.setAttribute("bucket", new ArrayList<ProductDto>());
I would like to know if it is possible to set the time for the life of this object. Ie, for example, after an hour it will be automatically destroyed

If you mean "http servlet session" for "session", then there is no way to configure timeout for individual attribute, no matter is it spring or not. You can only timeout the whole session.
If you use pure spring and servlet web application - configure session timeout in web.xml.
If you mean "spring-boot" for "spring", configure session timeout as written here:

you can set session time out in your application.properties file
server.session.timeout=1000 # session timeout in second
server.session.cookie.max-age=1000 # Maximum age of the session cookie in seconds. also add if server.session.timeout not working
refer:https://javadeveloperzone.com/spring-boot/spring-boot-tomcat-session-timeout/

You can use cache manager to acheive that:
#EnableCaching
#Configuration
public class CacheConfiguration implements CachingConfigurer {
#Override
public CacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager() {
#Override
protected Cache createConcurrentMapCache(final String name) {
return new ConcurrentMapCache(name,
CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.MINUTES).maximumSize(100).build().asMap(), false);
}
};
return cacheManager;
}
#Override
public KeyGenerator keyGenerator() {
return new DefaultKeyGenerator();
}
}

Related

Spring session jdbc - How to add multiple HttpSessionIdResolver for a single application

I have a problem in injecting multiple HttpSessionIdResolver for a single spring application.
For normal web application I would like to use CookieHttpSessionIdResolver
For Rest API I would go for HeaderHttpSessionIdResolver and Rest API url will be like "/api/**"
Internally spring sets a bean and uses that bean for all request(In this case HeaderHttpSessionIdResolver
and my web stopped working because i dont set X-Auth-Token header for every request) but i would like to override it.
Could any one please help me.
Thank you.
#EnableJdbcHttpSession(maxInactiveIntervalInSeconds = 3600)
public class SessionConfig extends AbstractHttpSessionApplicationInitializer{
#Autowired
#Qualifier("userDatabase")
private DataSource dataSource;
#Bean
public DataSource dataSource() {
return dataSource;
}
#Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
#Bean(value = "httpSessionIdResolver")
public HttpSessionIdResolver httpSessionIdResolver() {
return HeaderHttpSessionIdResolver.xAuthToken();
}
#Bean(value = "cookieHttpSessionIdResolver")
public HttpSessionIdResolver cookieHttpSessionIdResolver() {
return new CookieHttpSessionIdResolver();
}
}
I overridden spring session to enable both cookie and header based session.
Now it's working fine.
Currently I'm checking for URL that contains /api/* and if it contains i'm using header based other wise cookie based session.

JdbcOperationsSessionRepository and SessionDestroyed not compatible

I am using spring boot and I am using spring session where spring creates a session in APP_SESSION table and adds rows into it. It has max_inactive_interval column which has some seconds value in it after which the session gets timed out. Now I want to do something before session times out. I wan to perform some database operation on different table and set a flag.
I have used JdbcOperationsSessionRepository for this
#Bean
#Order(1)
public JdbcOperationsSessionRepository sessionRepository(
#Qualifier("springSessionJdbcOperations") JdbcOperations jdbcOperations,
PlatformTransactionManager transactionManager) {
JdbcOperationsSessionRepository repository = super.sessionRepository(jdbcOperations, transactionManager);
repository.setDefaultMaxInactiveInterval(maxInactiveIntervalInSeconds);
repository.setTableName(TABLE_NAME);
return repository;
}
I have tried this
#Bean // bean for http session listener
public HttpSessionListener aohttpSessionListener() {
return new HttpSessionListener() {
#Override
public void sessionCreated(HttpSessionEvent session) { // This method will be called when session created
System.out.println("Session Created with session id+" + session.getSession().getId());
}
#Override
public void sessionDestroyed(HttpSessionEvent session) { // This method will be automatically called when session destroyed
System.out.println("Session Destroyed, Session id:" + session.getSession().getId());
// database operations
}
}
};
But it does not work I guess according to this https://github.com/spring-projects/spring-session/issues/1082
Any help is appreciated. Thanks in advance.
Spring Session JDBC does not support publishing of session events. Only Spring Session Redis supports that.
Source code comment on line 68

Clear Redis Cache on Spring Boot Application Startup

On application (spring boot service) startup, need to clear the Redis cache.
Redis is running in a different docker container with own volume mapping. Since it retains the old cache, the application picks the data from Redis cache instead of Database even after the application restarts
Tried #EventListener for ContextRefreshedEvent and it is never getting called.
Tried with #PostConstruct in ApplicationMain class, but it doesn't clear the cache.
Tried using #CacheEvict(allEntries = true), but still no luck
#Component
public class ApplicationStartUp {
#Autowired
private CacheManager cacheManager;
#EventListener()
public void onApplicationEvent(ContextStartedEvent event) {
cacheManager.getCacheNames()
.parallelStream()
.forEach(n -> cacheManager.getCache(n).clear());
}
}
I was successfully able to clear the cache with ApplicationReadyEvent. As the CacheManager bean is available by the time, the cache is getting cleared properly on startup
#Autowired
private CacheManager cacheManager;
#EventListener
public void onApplicationEvent(ApplicationReadyEvent event) {
cacheManager.getCacheNames()
.parallelStream()
.forEach(n -> cacheManager.getCache(n).clear());
}
For the Redis cache manager, if you want to clear the cache on boot, I think you will need to initialize the cache manager with a set of names. See the RedisCacheManagerBuilder docs
For example:
RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory)
.initialCacheNames(Set.of("cacheOne", "cacheTwo"))
.build();
Then you should be able to use #PostConstruct in you cache config class, for example.
#PostConstruct
public void clearCache() {
cacheManager.getCacheNames()
.parallelStream()
.forEach(n -> cacheManager.getCache(n).clear());
}
A simple practice to clear Redis DB at earlier life cycle stage. To config CacheManager #bean with argument RedisConnectionFactory passed in, calling flushDb() to delete all keys of the currently selected database before build up.
#Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
redisConnectionFactory.getConnection().flushDb(); //Delete all keys of the currently selected database
return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory)).cacheDefaults(RedisCacheConfiguration.defaultCacheConfig()).build();
}

How to simulate session closing / expiring in Spring Boot tests?

I would like to add a couple of tests to the example shown here:
https://spring.io/guides/gs/securing-web/
to be able to verify that a user can no longer access resources requiring authentication when session closes or expires. I would like to simulate both the following conditions in my tests:
a) the user voluntarily ends their session (e.g. close their browser);
b) the session times out;
I don't know how to reproduce those conditions using MockMvc.
I managed to do the following:
#Test
public void sessionIsInvalid() throws Exception {
FormLoginRequestBuilder login = formLogin()
.user("user")
.password("password");
mockMvc.perform(login)
.andExpect(authenticated())
.andDo(mvcResult -> {
MockHttpSession session = (MockHttpSession)mvcResult.getRequest().getSession();
session.invalidate();
mockMvc.perform(get("/hello")
.session(session))
.andExpect(status().isFound());
});
}
...which seems to work but I am not totally sure what invalidate does in this context and whether it matches condition a) above.
To emulate the session timeout, I've done instead:
#Test
public void sessionExpires() throws Exception {
FormLoginRequestBuilder login = formLogin()
.user("user")
.password("password");
mockMvc.perform(login)
.andExpect(authenticated())
.andDo(mvcResult -> {
MockHttpSession session = (MockHttpSession)mvcResult.getRequest().getSession();
session.setMaxInactiveInterval(1);
Thread.sleep(3);
mockMvc.perform(get("/hello")
.session(session))
.andExpect(status().isFound());
});
}
...but this doesn't work. Can someone help me understand what I am doing wrong?
When using Spring Boot with Spring Security (which is all about in you link), my approach is this:
create a custom spring security filter that is able to "convince" spring security that the session is expired (whatever it believes a session is)
add the custom filter just before ConcurrentSessionFilter
create an inner static #TestConfiguration class which could, in theory, just configure the HttpSecurity to add the custom filter (that's all we want). In practice I found that usually I have to have the class annotated with #TestConfiguration to extend my project's security configuration class (or at least the main one, if having many, e.g. SecurityConfiguration for my project); because in SecurityConfiguration I usually declare other #Bean too (e.g. CorsConfigurationSource) I usually have to also use #WebMvcTest(properties = "spring.main.allow-bean-definition-overriding=true", ...) to avoid the bean overriding error; have the class annotated with #TestConfiguration to be annotated with #Order(HIGHEST_PRECEDENCE) too.
create a simple web mvc test trying to GET some project-existing endpoint, e.g.:
#Test
#SneakyThrows
#WithMockUser
void sessionExpired() {
this.mvc.perform(get("/some-endpoint-here")).andExpect(...);
}
run the test and expect for your configured session expiration strategy to kick in; see HttpSecurity.sessionManagement(session -> session...expiredUrl(...)) or HttpSecurity.sessionManagement(session -> session...expiredSessionStrategy(...))
The below spring security configuration provided as a #TestConfiguration works with Spring Boot 2.3.12.RELEASE (and probably many more).
#TestConfiguration
#Order(HIGHEST_PRECEDENCE)
static class Config extends SecurityConfiguration {
public Config(SessionInformationExpiredStrategy expiredSessionStrategy, InvalidSessionStrategy invalidSessionStrategy) {
super(expiredSessionStrategy, invalidSessionStrategy);
}
#SneakyThrows
#Override
protected void configure(HttpSecurity http) {
super.configure(http);
// the custom filter as a lambda expression
http.addFilterBefore((request, response, chain) -> {
// preparing some objects we gonna need
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpSession session = httpRequest.getSession(false);
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// getting our hands on the object that Spring Security believes
// is the "session" and which is in ConcurrentSessionFilter
List<Filter> filters = (List) ReflectionTestUtils.getField(chain, "additionalFilters");
int currentPosition = (int) ReflectionTestUtils.getField(chain, "currentPosition");
ConcurrentSessionFilter concurrentSessionFilter = (ConcurrentSessionFilter) filters.get(currentPosition);
SessionRegistry sessionRegistry = (SessionRegistry) ReflectionTestUtils.getField(concurrentSessionFilter, "sessionRegistry");
// the "session" does not exist (from Spring Security's PoV),
// we actually have to create (aka "register") it
sessionRegistry.registerNewSession(session.getId(), authentication.getPrincipal());
// the actual session expiration (from Spring Security's PoV)
sessionRegistry.getSessionInformation(session.getId()).expireNow();
// let the filters continue their job; ConcurrentSessionFilter
// follows and it'll determine that the "session" is expired
chain.doFilter(request, response);
}, ConcurrentSessionFilter.class);
log.debug("begin");
}
}
session.setMaxInactiveInterval(1); // in seconds
Thread.sleep(3); // in milliseconds

#EnableRedisHttpSession + Spring Boot ignoring server.session.timeout on application.yml

I have a project with Spring Boot 1.3.3 [another stuff] and Redis configurated to manage sessions, i.e., #EnableRedisHttpSession. The application works well and stores the information on Redis regularly.
The problem that I'm facing is that, different from what documentation says, whether I define or not a server.session.timeout, the Redis always is using the default value for its annotation attribute (maxInactiveIntervalInSeconds) that is: 1800
Here, the documentation that I followed: http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-session.html
I've also tried the approach defined by #rwinch here https://github.com/spring-projects/spring-session/issues/110 but also without success.
Updating ......
My configuration file as requested:
#First attempt (server.session.timeout) following the Spring documentation mentioned
server:
session:
timeout: 10
spring:
#session timeout under spring (as mentioned by M Deinum in comment - unfortunately doesnt work)
session:
timeout: 10
redis:
host: 192.168.99.101
port: 6379
Beside that, I've also tried to implement a SessionListener that was in charge of setting the timeout (something like this):
public class SessionListener implements HttpSessionListener {
#Value(value = "${server.session.timeout}")
private int timeout;
#Override
public void sessionCreated(HttpSessionEvent event) {
if(event!=null && event.getSession()!=null){
event.getSession().setMaxInactiveInterval(timeout);
}
}
...
It still didn't result in a correct scenario. I'm really racking my brain :|
Please guys, am I missing some point? Does anyone else have faced it?
Thanks in advance.
Another solution:
#EnableRedisHttpSession
public class HttpSessionConfig {
#Value("${server.session.timeout}")
private Integer maxInactiveIntervalInMinutes;
#Inject
private RedisOperationsSessionRepository sessionRepository;
#PostConstruct
private void afterPropertiesSet() {
sessionRepository.setDefaultMaxInactiveInterval(maxInactiveIntervalInMinutes * 60);
}
In this way you use the default configuration, and just add your timeout. So you maintain the default HttpSessionListener, and you don't need to use an ApplicationListener to set the time out, just one time, in the application lifecycle.
Well, just in case someone is facing the same situation, we have 2 ways to workaround:
I. Implement the following:
#EnableRedisHttpSession
public class Application {
//some other codes here
#Value("${spring.session.timeout}")
private Integer maxInactiveIntervalInSeconds;
#Bean
public RedisOperationsSessionRepository sessionRepository( RedisConnectionFactory factory) {
RedisOperationsSessionRepository sessionRepository = new RedisOperationsSessionRepository(factory);
sessionRepository.setDefaultMaxInactiveInterval(maxInactiveIntervalInSeconds);
return sessionRepository;
}
Unfortunately, I had to implement a listener in order to perform additional actions when a session expires. And, when you define a RedisOperationsSessionRepository, you don't have a HttpSessionListener anymore (instead of it, you have a SessionMessageListener, as described here: http://docs.spring.io/spring-session/docs/current/reference/html5/#api-redisoperationssessionrepository). Because of this question, the 2nd approach was required.
II. To overcome the problem:
#EnableRedisHttpSession
public class Application implements ApplicationListener{
#Value("${spring.session.timeout}")
private Integer maxInactiveIntervalInSeconds;
#Autowired
private RedisOperationsSessionRepository redisOperation;
#Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextRefreshedEvent) {
redisOperation.setDefaultMaxInactiveInterval(maxInactiveIntervalInSeconds);
}
}
...
Assuming that none of them are the desirable out-of-box setup, at least they allow me to continue in my PoC.
#EnableRedisHttpSession(maxInactiveIntervalInSeconds = 60)
You can remove EnableRedisHttpSession annotation, instead, set the property:
spring.session.store-type=redis
Both spring.session.timeout and server.servlet.session.timeout will work. Please note spring.session.timeout will override server.servlet.session.timeout per my test.
Extend RedisHttpSessionConfiguration and do init in #PostConstruct method.
#Configuration
public class HttpSessionConfig extends RedisHttpSessionConfiguration {
#Value("${spring.session.timeout}")
private Integer sessionTimeoutInSec;
#Value("${spring.session.redis.namespace}")
private String sessionRedisNamespace;
#Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory();
}
#PostConstruct
public void initConfig() throws Exception {
this.setMaxInactiveIntervalInSeconds(sessionTimeoutInSec);
this.setRedisNamespace(sessionRedisNamespace);
}
}

Resources