How to configure a session timeout for Grails application? - session

In one of controllers in my Grails application I'm preserving a parameter value in a session variable like this:
session.myVariable = params.myValue
After that, I can access the saved value from different controllers/GSP-pages as long as I actively use the app. However, if I don't use my app for a while, even though my browser window is still open, the session variable looses it's value.
Does this happens because the session expires? I was under impression that a session lives until the browser window is still open, but apparently I was wrong.
What should I do to ensure all session variables I define in my Grails app don't expire until the browser is closed? Is there any way to set session timeout manually?
Thank you in advance for your answers!

Another option would be modifying web.xml. Prior you must call
grails install-templates
Then edit src/templates/war/web.xml and add/modify after servlet-mapping:
<session-config>
<session-timeout>60</session-timeout>
</session-config>
The value of session-timeout uses minutes as unit.

Fast forward a few years... For Grails 3.0 set the session timeout with ServerProperties in the application configuration file.
grails-app/conf/application.yml
server:
session:
timeout: 3600 #seconds
Default value: 1800 seconds (30 minutes)
Verify the timeout for the
HttpSession
from a controller using getMaxInactiveInterval():
log.println "Timeout: ${session.getMaxInactiveInterval()} seconds"
Output --> Timeout: 3600 seconds
Update: Edited configuration for changes in Grails 3.1

The current grails (2.x) have a very odd design approach to setting the session timeout. None of the prevailing ideas are great:
comment out "//session Timeout" section the within the WebxmlGrails Plugin and add "sessionConfig.sessionTimeout=" to Config.groovy
grails install-templates, remove session-timeout from web.xml, add timeout in WebXmlConfig.groovy
wait for a fix. :/
A co-worker came up with the following code that works well for me and will do until a real solution is built into grails core.
Simply add the following to the bottom of your config.groovy file and then set the appropriate timeout.
grails.war.resources = { stagingDir, args ->
def webXML = new java.io.File("${stagingDir}/WEB-INF/web.xml")
webXML.text = webXML.text.replaceFirst("<session-timeout>30</session-timeout>", "<session-timeout>90</session-timeout>")
}
My I suggest that the correct solution is to allow a single line in the Config.groovy file:
session.timeout = 90;
Cheers.

With Grails 3.1.x session-timeout is deprecated. The correct property in application.yml is:
server:
session.timeout: 7200

I could be wrong, but I'm pretty sure Grails uses the sessions associated with your application container. If you're using Tomcat, for example, you can specify the length of a session.
Tutorial for changing Tomcat session length.

here is a better working solution. go you your grails home directory and find
Example: E:\grails-2.3.8\src\war\WEB-INF\web3.0.template.xml edit the session time out value to desired values:
Example:
enter code here
90

For Grails 3 application, modifying the Application.groovy worked for me:
package foo
import grails.boot.GrailsApp
import grails.boot.config.GrailsAutoConfiguration
import org.apache.catalina.Context
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory
import org.springframework.boot.context.embedded.tomcat.TomcatContextCustomizer
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory
import org.springframework.context.annotation.Bean
class Application extends GrailsAutoConfiguration {
static void main(String[] args) {
GrailsApp.run(Application, args)
}
#Bean
EmbeddedServletContainerFactory containerFactory() {
TomcatEmbeddedServletContainerFactory containerFactory = new TomcatEmbeddedServletContainerFactory()
containerFactory.addContextCustomizers(new TomcatContextCustomizer() {
#Override
void customize(Context context) {
int oneWeekInMinute = 7 * 24 * 60
context.setSessionTimeout(oneWeekInMinute)
}
});
return containerFactory
}
}

Related

Spring Boot 3 Redis Indexed Session - Failure to save login

I am upgrading my team's Spring Boot project from 2.7.5 to 3.0.0, which includes a migration of the now deprecated WebSecurityConfigurerAdapter. I am working in Kotlin, but I do not believe the language impacts the performance of the component.
In both the old and new versions of the security configuration class, I have declared this Autowired field and corresponding bean:
#Autowired
private lateinit var sessionRepository: FindByIndexNameSessionRepository<out Session>
#Bean
fun sessionRegistry(): SessionRegistry {
return SpringSessionBackedSessionRegistry(sessionRepository)
}
At first, my application would not start, because Spring could not wire the session repository into my configuration, but after some digging, it was because I had to change the #EnableRedisHttpSession annotation to
#EnableRedisIndexedHttpSession
class SecurityConfiguration(
Now, my current issue with the application is that I cannot log into my application, and I think it has to do with the Redis session management.
I am able to load the login page, and submit a login request. When I submit the request, I am able to see all of the normal debug messages my application prints associated with logging in, taking me through a successful authentication. When all of that finishes, my application redirects the user to the main page, which checks if a user's authentication != null, and redirects them to the main page if so. However, the authentication at this point is null, and there is not an entry in redis for this user's authentication.
In case it helps, this is what the security configuration had defined in the SecurityFilterChain bean for the session parts before the migration:
.sessionManagement().sessionFixation().migrateSession().and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED).maximumSessions(1).sessionRegistry(sessionRegistry()).expiredUrl("/api/v1/logout").and()
and after:
.sessionManagement { ssm ->
ssm.sessionFixation { it.migrateSession() }
ssm.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.maximumSessions(1).sessionRegistry(sessionRegistry()).expiredUrl("/api/v1/logout")
}
Is there something I am missing to allow the user authentication to persist? I have not touched any part of the code for this migration besides the pom.xml file and the rewriting of the security configuration.
UPDATE: I have altered the tags of the security configuration, in a desperate attempt at getting something to save.
#Configuration
#EnableWebSecurity
#EnableSpringHttpSession
#EnableRedisIndexedHttpSession(redisNamespace = "admindev:session", flushMode = FlushMode.IMMEDIATE, saveMode = SaveMode.ALWAYS)
class SecurityConfiguration(
I have increased the logging level for spring security, and everything looks fine, until the session token is not found.
Update 2:
I have tweaked my application as I have described in my answer to this post, and I am now able to access the frontend of my application. However, when I attempt to login through the backend, I get caught up in an infinite redirection loop. The app tries to send me to the error/4xx page, which is the correct behavior, since we don't have a "/" link on the backend, but then the app tries to redirect me according to the "your session has expired" page, and since I'm logged in, it tries to redirect me back to "/"...
Looking through the logs, it seems to be related to the exception handling I set up on my filter chain:
http.exceptionHandling { e -> e.authenticationEntryPoint(LoginRedirectHandler()) }
//...
class LoginRedirectHandler : LoginUrlAuthenticationEntryPoint("/login") {
override fun determineUrlToUseForThisRequest(
request: HttpServletRequest,
response: HttpServletResponse,
exception: AuthenticationException?
): String {
return "${this.loginFormUrl}?error=timeout&requestedUrl=${request.requestURL}"
}
}
Now, the question becomes: the backend webpages were not looping before, what could have changed about this behavior?

Spring security session timeout for spring reactive

I have a Reactive Application with Spring Security integrated, it was created by spring initilizer with mainly thre3 packages(spring boot, spring security and webflux).
I was trying to configure the session timeout by following configuration in application.properties:
spring.session.timeout=1m
after starting the application with mvn spring-boot:run, It can be accessed by http://localhost:8080 and it asked me to login(by default security setting). I can use the username user and the password generated on the console to login.
Per my configuration, I expected that after 1 minutes idle time, when I refresh the page http://localhost:8080 again, it can ask me to re-login. But in fact it didn't , until 30 minutes later
So I suspect the above configuration is not working
Did I used the wrong configuration?
the reproduce repo can be found here: https://github.com/ZhuBicen/ReactiveSpringSecurity.git
Spring should probably allow an auto-configuration for your case above for the reactive stack as it does for servlet.
However, "session" is state and that state won't scale unless there is some persistent storage backing it. You can use the Spring Session abstraction with an in-memory ReactiveSessionRepository even if you don't (yet) have a backing store like Redis or something. When you do get a proper supported backing store and add the corresponding dependencies, you can delete your in-memory ReactiveSessionRepository as spring boot will auto-configure your ReactiveSessionRepository for you.
First, add the spring session dependency
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-core</artifactId>
</dependency>
Second, manually create your ReactiveSessionRepository bean. (Note: this can be auto-configured for you if you're using Redis instead of in-memory, etc.)
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.session.SessionProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.ReactiveMapSessionRepository;
import org.springframework.session.ReactiveSessionRepository;
import org.springframework.session.config.annotation.web.server.EnableSpringWebSession;
import java.util.concurrent.ConcurrentHashMap;
/**
* This ReactiveSessionRepository isn't auto-configured so we need to create it and manually set the timeout on it.
* Later, ReactiveRedisSessionRepository will be auto-configured so we can delete this
*/
// https://www.baeldung.com/spring-session-reactive#in-memory-configuration
#Configuration
#EnableSpringWebSession
#RequiredArgsConstructor // if lombok
#Slf4j // if lombok
public class SessionConfig {
private final SessionProperties sessionProperties;
#Bean
public ReactiveSessionRepository reactiveSessionRepository() {
ReactiveMapSessionRepository sessionRepository = new ReactiveMapSessionRepository(new ConcurrentHashMap<>());
int defaultMaxInactiveInterval = (int) sessionProperties.getTimeout().toSeconds();
sessionRepository.setDefaultMaxInactiveInterval(defaultMaxInactiveInterval);
log.info("Set in-memory session defaultMaxInactiveInterval to {} seconds.", defaultMaxInactiveInterval);
return sessionRepository;
}
}
Third, set the property spring.session.timeout=3600.
I finally fixed it by implementing customized ServerAuthenticationSuccessHandler, eg:
class SuccessHandler extends RedirectServerAuthenticationSuccessHandler {
#Override
public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
// set to -1 means the session will never expired
// webFilterExchange.getExchange().getSession().subscribe(session->session.setMaxIdleTime(Duration.ofSeconds(-1)));
webFilterExchange.getExchange().getSession().subscribe(session->session.setMaxIdleTime(Duration.ofMinutes(60)));
return super.onAuthenticationSuccess(webFilterExchange, authentication);
}
}
The SuccessHandler can be setted by similar way:
#EnableWebFluxSecurity
public class SecurityConfig {
#Bean
SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) throws Exception {
http.formLogin().authenticationSuccessHandler(new SuccessHandler());
return http.build();
}
}
The parameter you have configured has nothing to do with the cookie session you have with the web-browser.
That parameter configures Spring Session which is a way to handle session between services, rest api's and such by providing a session through a header.
https://spring.io/projects/spring-session
Session cookies are historically bound to threads in threadlocal, which doesn't work in reactive applications. So in order to store sessions, you need be able to store the sessions somewhere else. Redis is one example ofwhere you can store websessions.
Here is a tutorial of using webflux, redis and spring session to manage websessions.
https://www.baeldung.com/spring-session-reactive
Reactive stack configuration is different from the servlet stack.
server.reactive.session.timeout=10m
Spring documentation reference link

Refresh springboot configuration dynamically

Is there any way to refresh springboot configuration as soon as we change .properties file?
I came across spring-cloud-config and many articles/blogs suggested to use this for a distributed environment. I have many deployments of my springboot application but they are not related or dependent on one another. I also looked at few solutions where they suggested providing rest endpoints to refresh configs manually without restarting application. But I want to refresh configuration dynamically whenever I change .properties file without manual intervention.
Any guide/suggestion is much appreciated.
Can you just use the Spring Cloud Config "Server" and have it signal to your Spring Cloud client that the properties file changed. See this example:
https://spring.io/guides/gs/centralized-configuration/
Under the covers, it is doing a poll of the underlying resource and then broadcasts it to your client:
#Scheduled(fixedRateString = "${spring.cloud.config.server.monitor.fixedDelay:5000}")
public void poll() {
for (File file : filesFromEvents()) {
this.endpoint.notifyByPath(new HttpHeaders(), Collections
.<String, Object>singletonMap("path", file.getAbsolutePath()));
}
}
If you don't want to use the config server, in your own code, you could use a similar scheduled annotation and monitor your properties file:
#Component
public class MyRefresher {
#Autowired
private ContextRefresher contextRefresher;
#Scheduled(fixedDelay=5000)
public void myRefresher() {
// Code here could potentially look at the properties file
// to see if it changed, and conditionally call the next line...
contextRefresher.refresh();
}
}

Grails 3 Controller Scope

Looking at Grails 3 documentation, the following is written about scopes:
prototype (default) - A new controller will be created for each request (recommended for actions as Closure properties)
The odd part here is that I get significantly different results in Grails 3.1.4 if I explicitly state:
static scope = "prototype"
Take for example the following UserController
class UserController {
def userService
List<String> users
def loadUsers() {
if (!users) {
println("########## have to load users");
try {
user = userService.getAllUsersInAd()
} catch (Exception e) {
// do something
}
} else {
println("########## dont have to do it " + users.size());
}
}
}
And the following UserService
class UserService {
def getAllUsersInAd() {
println("######### querying")
return new ArrayList<String>();
}
}
If static scope is omitted:
When I close a Firefox browser and re-open it, "dont have to do it is executed", regardless of how many times I close/reopen it. What is even more weird about this is that I can open a completely different browser (like chrome) when I close Firefox, and the same message executes. It is almost as if the scope of this controller is a similar one to #ApplicationScope of JSF.
After a good 5-10 mins of idle time, the query is executed again, but the scenario remains.
If static scope is stated:
After each browser closing, the "have to load users" is executed as expected.
My question is, is the documentation wrong for the default scope? If it is not, what is the difference between explicitly stating scope="prototype" and omitting it(aside from the obvious above) ?
In the default generated application.yml file for a Grails 3.1.4 app you should see a setting like this:
grails:
controllers:
defaultScope: singleton
That tells the framework to make controllers singleton. The default scope is prototype if that setting is not set.
Yes, I believe controllers were changed to singleton (application) scope by default a while ago (version 1.4.x as it happens). I'm trying to find documentation of that. It seems it was a JIRA issue fix, originally here, but Grails has moved away from JIRA, and didn't migrate all the bugs to GitHub.
You shouldn't have state in controllers anyway according to the Grails team:
Why does Grails recommend singleton scope for controllers with actions as methods?
See Burt's answer, and Jeff even speaks up in the comments.
EDIT: As Jeff says, the default is still prototype, but the default generated config changes them all to singleton, which is recommended.

Modifying Configuration for Embedded tomcat webapp

I've been trying to modify embedded tomcat configuration for my heroku app. I've installed heroku app using the wiki link below that configures a simple embedded tomcat.
http://devcenter.heroku.com/articles/create-a-java-web-application-using-embedded-tomcat
The source code is here:
public static void main(String[] args) throws Exception {
String webappDirLocation = "src/main/webapp/";
Tomcat tomcat = new Tomcat();
//The port that we should run on can be set into an environment variable
//Look for that variable and default to 8080 if it isn't there.
String webPort = System.getenv("PORT");
if(webPort == null || webPort.isEmpty()) {
webPort = "8080";
}
tomcat.setPort(Integer.valueOf(webPort));
tomcat.addWebapp("/", new File(webappDirLocation).getAbsolutePath());
System.out.println("configuring app with basedir: " + new File("./" + webappDirLocation).getAbsolutePath());
tomcat.start();
tomcat.getServer().await();
}
Questions:
Since I'm using embedded tomcat, how do I configure default session timeout for my web application? It seems to default to 30 min for some reason? I want to set to something like one week.
If I launch the application from within eclipse, how do I set autodeploy = true so that I don't have to compile and restart my application every time I modify java code?
is there way to set my web.xml and server.xml?
how do I run apache tomcat manager?
The documentation on the internet is not very clear. Can you please help?
Thanks in advance..
Kiran
Use Context.setSessionTimeout(int). Java docs here. Here's the same Main class with a session timeout set to 30 days:
package launch;
import java.io.File;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.Context;
public class Main {
public static void main(String[] args) throws Exception {
String webappDirLocation = "src/main/webapp/";
Tomcat tomcat = new Tomcat();
//The port that we should run on can be set into an environment variable
//Look for that variable and default to 8080 if it isn't there.
String webPort = System.getenv("PORT");
if(webPort == null || webPort.isEmpty()) {
webPort = "8080";
}
tomcat.setPort(Integer.valueOf(webPort));
Context ctx = tomcat.addWebapp("/", new File(webappDirLocation).getAbsolutePath());
ctx.setSessionTimeout(2592000);
System.out.println("configuring app with basedir: " + new File("./" + webappDirLocation).getAbsolutePath());
tomcat.start();
tomcat.getServer().await();
}
}
Notice the Context ctx = ... and ctx.setSessionTimeout(...).
As for Tomcat Manager, you can't use it when you embed Tomcat in your application this way. I am curious what you would like to use Tomcat Manager for?
Anything you would normally do from server.xml you can do via the embed API. The whole point of embedding is that you configure everything programmatically.
You can still set up your own web.xml as you normally would. Just add it in a WEB-INF directory under the directory you pass in as webappDirLocation. But again, I am curious what you would want to put in web.xml? Because you "own" the main application loop, you can set up any configuration you need from your main method. I highly recommend a practice of initializing everything you need in the main loop and reading OS environment variables for anything that is environment specific (e.g. a JDBC url).
Finally, as for Eclipse, you don't need hot deploy anymore because you are not using a container deployment model. You can simply run your application from inside Eclipse with "Debug as..." and Eclipse will auto compile and reload code as you change it. It's not exactly similar to hot deploy. For example, it won't hot reload classes with new method signatures. But it's much faster to cycle the whole app compared to when using a container, so overall, I find it much more productive.

Resources