Storing Form Login Sessions in a Database - spring

A security context on my Spring server uses Spring Security's built-in form login implementation. Currently, login sessions are being stored locally, in memory, by the servlet container. I'd like to replace the way HttpSessions are stored and retrieved with one of my Spring Data Mongo repositories. I looked for one of those "slots" in the Java configuration for session management, but didn't find anything. To be clear, I'm looking for the equivalent of a UserDetailsService, but for sessions.
Here's the relevant snippet of Java configuration in my security configuration class:
...
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.formlogin()
.loginProcessingUrl("/authentication/login")
.successHandler(successHandler)
.usernameParameter("username")
.passwordParameter("password")
.failureUrl("/login?error")
.loginPage("/login")
.and()
.logout()
.logoutUrl("/authentication/logout")
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
.logoutSuccessUrl("/login")
.and()
...
As far as I can tell, I'm not doing anything particularly strange. It may not be relevant, but I wanted to show precisely what I'm looking at when I say I couldn't find the correct configuration slot.
I looked closely at the source code for the SecurityContextPersistenceFilter, which seems to be the filter in Spring Security's filter chain responsible for retrieving and storing HttpSessions. It delegates session retrieval to a call to getSession() in HttpServletRequest, and session storage to saveContext() in an injected SecurityContextRepository.
In order to correctly replace the default, server-local session storage mechanism, I see three approaches.
Plug in a Spring Data-backed security context repository into the persistence filter. Then, wrap incoming requests to implement custom getSession() behavior that queries the custom security context repository rather than local storage. The built-in persistence filter will then "do the right thing".
Set up an ObjectPostProcessor to replace the default filter with a custom SecurityContextPersistenceFilter that uses my Spring Data repository directly rather than calling getSession() or a using security context repository. I've actually never used an object post processor before, so if that's not what they're meant for, please tell me.
The last option is not one I'm considering, but it's worth mentioning. I think that underneath all the magic, Spring Security really delegates to the servlet container's implementation of session storage. So one way to change the backing store to Mongo would be to use something like Tomcat's Manager interface to customize the session persistence behavior. This is not something I want to do because it becomes quite separate from Spring, I lose the ability to use my services via dependency injection, and it depends completely on the container, making it difficult to change at whim.
I'm sure that gutting out the session storage and replacing it with a database is a fairly common requirement for Spring servers. How is it usually done? If I'm just missing a configuration option, I'd love to see where it's located. Otherwise, suggestions about which route to take (and why) are what I'm looking for.

In Spring 3 the place is in SessionManagement.
Basically you define the session filter and specialise either the session strategy or the session registry.
The session registry is in charge of dealing with session invalidation and creation. In that point you could persist whatever is that you need to persist.
The downside of this approach is that it requires either that you declare the session event publisher in web.xml file or that you handle everything.
An example would be to implement SessionRegistry and SessionAuthenticationStrategy. From there when a user authenticates or a getSession(true) (or invalidate it) is executed it will reach the code and there you can act upon it. Your strategy would have your session registry injected. If a user authenticates through the authentication chain it would reach your strategy, which would pass the session to your registry.
An alternative approach is to add a custom filter of your own. A class extending GenericFilterBean. And then register it:
<security:custom-filter ref="customSessionFilter" after="LAST" />
In this example it would be executed last. This is useful since you could check for an active session or a successfully authenticated user.

An approach similar to your option #3 without relying on container-specific interfaces would be to use Spring Session with a MongoDB-backed SessionRepository implementation.
This would handle persisting all HTTP Session data rather than only the bits specific to Spring-security.

Related

Persist session after browser is closed

I have a NextJS application that uses a Backend for Frontend Architecture with Spring Security OAuth2 Client and Spring Cloud Gateway, which communicates to my Spring Authorization Server, very similar to this sample.
My webapp is working real nice, I'm getting the SESSION and X-CSRF token from my BFF and are being set in the browser on my NextJS app as cookies, so everything is cool to that point. But my doubt is that I closed the browser window and my session goes away, obviously it happens since both the cookies have MAX-AGE as "Session".
I know that the best practice is to let is as is, let the session either expire by the session timeout or when the browser session ends, but I'm curious to know how to persist the SESSION and X-CSRF cookies after the browser closes, so I have these questions:
Is it just enough to set the MAX-AGE to something in both the BFF and Spring Authorization Server?
Is Spring Security Remember Me needed? Though my BFF uses WebFlux Security so that functionality isn't available.
Should the X-CSRF Cookie also be persisted after the browser is closed, just as the session?
Should the session timeout equal the max age that I would set for both the cookies?
Should the X-CSRF token be persisted in a database if I spun up multiple instances of the BFF?
Also I'm confused on how to setup this because of the fact that I do the login on the Spring Authorization Server but I'm also logged in in the BFF since I have the SESSION and X-CSRF token to communicate with my BFF, so I guess that both session configuration should be the same on these two apps since they both create a session cookie even though the browser only gets the BFF one.
Also worth noting that both my BFF and my Spring Authorization Server, use Spring Session with Redis using different namespaces.
Relevant Spring Security configuration in my BFF:
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(authorizeExchange ->
authorizeExchange.anyExchange().authenticated()
)
.exceptionHandling(exceptionHandling ->
exceptionHandling.authenticationEntryPoint(authenticationEntryPoint())
)
.csrf(csrf ->
csrf.csrfTokenRepository(csrfTokenRepository())
)
.cors(Customizer.withDefaults())
.oauth2Login(oauth2 ->
oauth2.authenticationSuccessHandler(authenticationSuccessHandler())
)
.logout(logout ->
logout
.logoutHandler(logoutHandler())
.logoutSuccessHandler(logoutSuccessHandler())
)
.oauth2Client(Customizer.withDefaults());
return http.build();
}
Security Configuration on my Spring Authorization Server:
// AuthorizationServerConfig class
#Bean
#Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
return http.cors(Customizer.withDefaults())
.formLogin(Customizer.withDefaults())
.build();
}
// WebSecurityConfig class
#Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests ->
authorizeRequests.anyRequest().authenticated()
)
.cors(withDefaults())
.formLogin(withDefaults());
return http.build();
}
I've done some research and for folks looking for something similar this may shed a little bit of light.
To answer my own questions:
Is it just enough to set the MAX-AGE to something in both the BFF and Spring Authorization Server?
I think that setting the max-age for my BFF for my session and x-csrf token is enough for enabling my users to be able to open my SPA again and be able to keep using the application freely without login in again.
You can find more information about how to set the max age when using Spring Security for servlet applications here or here for Reactive applications
I don't think that it makes sense to set the max age for the Spring Authorization Server microservice since the browser only gets and needs the one from the BFF Gateway microservice.
Also it is worth noting that I would guess that the session timeout should be equal or superior for what you set the max age, since when your users are away from the app the inactivity on the BFF would invalidate the session if it timeouts, more on that here
Is Spring Security Remember Me needed? Though my BFF uses WebFlux Security so that functionality isn't available.
If you have a servlet application, Remember me does allow to auto login the user when they close the window back, in my case I use a Reactive application therefore this feature is not yet built in. But if yours is a servlet one, you can try the feature here
Should the X-CSRF Cookie also be persisted after the browser is closed, just as the session?
I would think so, because it won't be generated back since you aren't automatically login on the app when you reopen the browser window, you are just still using the same session. This sounds like a bad practice but I haven't found what to do in this case.
You can set the max age for the X-CSRF token on both servlet and reactive applications by using CookieCsrfTokenRepository or CookieServerCsrfTokenRepository.
Should the session timeout equal the max age that I would set for both the cookies?
Again I would think that the timeout should be equal or superior or depending of the time you would like to give the users to make time it out, since it refreshes every time someone does something on the server. Look more on that here
Should the X-CSRF token be persisted in a database if I spun up multiple instances of the BFF?
I don't think so, since it appears that it's tied to the HTTPSession and you are using something like Spring Session and already storing that on the database, then I don't think that you should try to store it in a different way. More on that here
If anyone wants to add something more, or I said something wrong please correct it.

Configure communication between multiple OAuth2 authorization servers and a single resource server

I'm currently setting up a single resource server that will be validating access tokens from various authorization servers.
Spring security (using the Okta security starter with this as well) seems to only allow me to set a single issuer URI.
I managed to find a solution that works but I'm unsure if this is the best practice/standard way of doing it. In the code snippet below I've explicitly setup the resources with Spring's Java Config for simplicity.
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.pathMatchers("/api/protected/by/authserver1")
.and()
.oauth2ResourceServer()
.jwt()
.jwtDecoder(ReactiveJwtDecoders.fromOidcIssuerLocation("https://authserver1")
.and()
.and()
.authorizeExchange()
.pathMatchers("/api/protected/by/authserver2")
.and()
.oauth2ResourceServer()
.jwt()
.jwtDecoder(ReactiveJwtDecoders.fromOidcIssuerLocation("https://authserver2");
return http.build()
}
This seems to work exactly as intended, tokens minted from one auth server and used on the endpoint validating the other receive 401. When the minted tokens are used on their respective endpoint, they are successfully validated.
It looks a little funny having .and() calls back to back, I'm under the impression that these chained calls are just creating multiple web filters under the hood? Either way, is this the standard way of enabling this functionality in a Spring application with Spring Security and WebFlux?
Additionally, I came across this SO question but I don't know that I'll be able to setup a 'federation provider' within the context of this project. However, If that approach is the best practice I'd like to know. However, I think that's happening to some extent at the Okta level with the federation broker mode on the auth server access policies...?
Either way, is this the standard way of enabling this functionality in a Spring application with Spring Security and WebFlux?
No. What's more the example you've provided won't work. You can investigate the ServerHttpSecurity implementation and see why. Actually when you call oauth2ResourceServer() it sets new OAuth2ResourceServerSpec or returns the old one which can be modified. So in your case only the second JwtDecoder will be applied, because it overrides the first one. If you want to configure oauth2ResourceServer per path you'll have to define multiple SecurityWebFilterChain as posted here https://stackoverflow.com/a/54792674/1646298 .

Configure Spring HTTP Security at Runtime

All http security is applied at startup:
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")
}
During runtime, I am trying to add more to it ...
applicationContext.getBean(WebSecurityConfigurerAdapter).http.authorizeRequests().antMatchers("bla").hasRole("admin")
When that line is executed, it adds it to http.authorizeRequests()'s but /bla is still accessible by "non admins"
When server is restarted, this change takes effect because it is loading bla from the database.
How do I make the security take effect instantly without restarting the server?
You are trying to dynamicaly change a spring bean at runtime which is very hard to do unless you use tools like spring-loaded or JRebel.
There is a lot of SO about it :
Update spring beans dynamically. Is it possible?
dynamically change spring beans
Can I replace a Spring bean definition at runtime?
The best approach (in my opinion) for your use case is to use spring profiles.
Define a bean with authorisations for /bla and another bean without. Then use them in different profiles.
see dynamically declare beans at runtime in Spring
My solution to these case scenarios is to make a dynamic custom spring security rule to match with all the path.
http
.authorizeRequests()
.antMatchers("/**").access("#customSecurityRule.check(authentication)");
This way new endpoint will automatically be configured with our custom security rule, and in our custom security rule we can preety much do anything we want, checking their roles, validate it againts our database and etc.

How to override InvalidSessionStrategy in Spring Security

The default implementation of InvalidSessionStrategy i.e. SimpleRedirectInvalidSessionStrategy in Spring security redirects the request to a default url. This default url comes from a final variable.
I would like to override the behavior by not redirecting all the time to the same url. How could I achieve that ? If I create a custom implementation of InvalidSessionStrategy, how can I use that ?
This is my configuration right now
and()
.addFilterAfter(mySpringSecurityConfig.forceLogoutFilter(), PreAuthenticationFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.invalidSessionUrl("http://xxx/login.jsp")
.sessionAuthenticationErrorUrl(PropertyMgr.getCarnivalURL("http://xxx/login.jsp"))
But I don't want to use "http://xxx/login.jsp" for all invalid sessions. Thanks for the help.
Please post your entire configuration and the Spring Security version, so that we can have the whole picture.
However, to override InvalidSessionStrategy, simply define your class implementing this interface.
Then find a way to put your implementation into SessionManagementFilter.
One way can be via SessionManagementConfigurer.
In the worst case you can always extend SessionManagementFilter, put your InvalidSessionStrategy there, and substitute the default sesssion filter.

JSF2 BackingBean method not being called after Spring Security configuration done

I integrated an webapp that uses JSF 2 with Spring Security 3.2 and Spring 4.0 (compatible, see documentation, and this thread), using annotations, and I have this configuation:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/my-account", "**/myAccount.**").authenticated()
.and()
.formLogin().loginPage("/login").permitAll();
The login page is being showed correctly, but when I submit the username and password, JSF BakcingBean method is never called. I want to process some validations (required fileds, etc) on this method and throw exceptions (required field messages).
If I comment the line that setup my custom login page, the desired method is called.
This article, and this other, are examples of what I'm trying to do. Notice that the methods declared on the managed beans, apparently, are being called.
The question are: am I forgetting some configuration? How to do to Spring let JSF perform my validations, display required fields messages, etc?
People, after searching all this day on the internet I haven't found any example of this being doing using annotations.
I just migrated to XML files and now everything works.
I'm not an expert of Spring, but based in just what I tested, I think that using annotations Spring created that automatic filters that, for some reason, intercepted all the requests coming from the configured custom login form, blocking the JSF from handling the requests. This can be happening because some undocumented incompatibility between "Spring 4" and Spring "Security 3.2". This incompatibility doesn't occur when using XMLs.
If you create the security filters on the classic manual way on the web.xml, and configure your custom login form on Spring XML files, you can use the JSF features on the login form again.
Same security configuration of annotation migrated to XML and it worked.
PS: Sorry, I can't share the detailed files because this time it's not open source.
I answered this in the Spring Security JIRA at SEC-2761, but I'm posting here to help anyone else that stumbles across this issue.
The problem is that Java Configuration defaults the login processing URL to be a POST to the value of the login form. This means since the login page is configured to be loginPage("/login") a POST to /login will be intercepted by Spring Security.
To avoid this problem, you can either
perform a POST to a different URL and have the LoginController process that URL.
configure Spring Security to intercept a different URL using .loginProcessingUrl("/j_spring_security_check")
An example configuration for option 2 can be seen below:
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/j_spring_security_check")
...
}
I attached a working sample application to the previously mentioned JIRA. You can download it here.

Resources