0

I am currently building a Spring Boot web application (2.3.1, but the following issue also was observed with versions 2.1.7 and 2.1.5) that is secured with Spring Security. I mostly use default settings (e.g. embedded Tomcat, embedded H2 database, Spring Web-MVC). I do some custom authentication on a permissive POST mapping with the following code:

UsernamePasswordAuthenticationToken authentication = ...;
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(authentication);

This works fine. Since I want to use it for student examinations, I want to persist the session with the authentication, so in case the server software would crash or go down for some reason, no one needs to re-authenticate when the server comes up again.

I used Spring Session JDBC for this. I added the following dependency to my pom.xml:

<dependency>
   <groupId>org.springframework.session</groupId>
   <artifactId>spring-session-jdbc</artifactId>
</dependency>

and the following line to my application.properties

spring.session.store-type=jdbc

This all works fine when I test locally. I see the sessions appearing in my database and everything works great. However, in practice the application is started from an iframe: a learning management system does an LTI-launch, which is a POST request of which the response ends up in the iframe. When I attempted to do a test deployment, I got a 403 error that my request was not Authenticated. After a frustrating day of debugging, I ultimately was able to pin down this issue to the fact that adding spring-session-jdbc to my project causes the web application to send a new session ID cookie on each request of the iframe. If I repeat the same requests without the iframe, the same application works fine. If I remove the spring-session-jdbc dependency, the application works fine within the iframe as well.

If I perform the authentication within the iframe, I see the following in the Spring Security debug logs (exec-1 is where a succesful authentication happens, exec-2 is the request the iframe performs after being redirected by the exec-1 request):

2020-06-24 22:14:02.015 DEBUG 7474 --- [nio-8031-exec-1] o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@671fa7ec
2020-06-24 22:14:02.015 DEBUG 7474 --- [nio-8031-exec-1] w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
2020-06-24 22:14:02.063 DEBUG 7474 --- [nio-8031-exec-1] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
2020-06-24 22:14:02.113 DEBUG 7474 --- [nio-8031-exec-2] o.s.security.web.FilterChainProxy        : /launch/development at position 1 of 11 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2020-06-24 22:14:02.113 DEBUG 7474 --- [nio-8031-exec-2] o.s.security.web.FilterChainProxy        : /launch/development at position 2 of 11 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2020-06-24 22:14:02.114 DEBUG 7474 --- [nio-8031-exec-2] w.c.HttpSessionSecurityContextRepository : No HttpSession currently exists
2020-06-24 22:14:02.114 DEBUG 7474 --- [nio-8031-exec-2] w.c.HttpSessionSecurityContextRepository : No SecurityContext was available from the HttpSession: null. A new one will be created.

When I perform the same requests with the same running application in a regular browser window, the Spring Security debug logs show the following:

2020-06-24 22:18:10.518 DEBUG 7474 --- [nio-8031-exec-5] o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@671fa7ec
2020-06-24 22:18:10.518 DEBUG 7474 --- [nio-8031-exec-5] w.c.HttpSessionSecurityContextRepository : SecurityContext 'org.springframework.security.core.context.SecurityContextImpl@e1239cdc: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@e1239cdc: Principal: testuserid; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_INSTRUCTOR' stored to HttpSession: 'org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper$HttpSessionWrapper@66670854
2020-06-24 22:18:10.536 DEBUG 7474 --- [nio-8031-exec-5] o.s.s.w.a.ExceptionTranslationFilter     : Chain processed normally
2020-06-24 22:18:10.536 DEBUG 7474 --- [nio-8031-exec-5] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
2020-06-24 22:18:10.590 DEBUG 7474 --- [nio-8031-exec-6] o.s.security.web.FilterChainProxy        : /home at position 1 of 11 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2020-06-24 22:18:10.591 DEBUG 7474 --- [nio-8031-exec-6] o.s.security.web.FilterChainProxy        : /home at position 2 of 11 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2020-06-24 22:18:10.592 DEBUG 7474 --- [nio-8031-exec-6] w.c.HttpSessionSecurityContextRepository : Obtained a valid SecurityContext from SPRING_SECURITY_CONTEXT: 'org.springframework.security.core.context.SecurityContextImpl@82a4fa0f: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@82a4fa0f: Principal: testuserid; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_INSTRUCTOR'

To me this seems rather odd: if this is purely a browser problem, it should not matter whether I use spring-session-jdbc. If it is a problem with how spring-session-jdbc stores my authentication, it should not matter if it happens in an iframe or not. Is there something I am missing here? Did I stumble on a bug?

I could try a workaround where I use some javascript inside the iframe to let the POST happen in a blank tab, but that feels rather ugly.

Paul Bouman
  • 103
  • 1

1 Answers1

1

In general, I would advise you against using your appication in an iframe.
This poses a security risk, which you can read more about in this answer.

Now to explain the behaviour you are seeing.
Spring Security uses a Session cookie to store the user's session.
Cookies are associated with domains, so if, for example, there is a cookie associated with the domain stackoverflow.com then that cookie will be included in any request to stackoverlow.com.

In order to control that behaviour, cookies also have an attribute called SameSite.
The SameSite attribute can have 3 values, None, Lax or Strict.
When the value is None, it behaves as described above (included in all requests).
When the value is Lax, then the cookie will only be included in top level navigation GET requests.

When including the Spring Session dependency, the Session cookie SameSite attribute is set to Lax by default.

Since rendering an application in an iframe is not a top level navigation, the Session cookie is not included in the request to the iframe, and the application has no way of knowing that a user is signed in.

You can explicitly set the SameSite attribute to None by using Spring Session.
Again, I would caution against this, since it can make your application vulnerable to CSRF and clickjacking attacks.
If, after consider the security implications, you deem it necessary to set the SameSite attribute to None, you can do so by including Spring Session in your dependencies and creating a custom CookieSerializer.

  • Thank you so much for this clear answer, it is now clear to me what was going on! I noticed the SameSite attribute in the cookies while debugging, but did not pay enough attention to it. The option to use a custom CookieSerializer indeed provides a trade-off I can work with. Unfortunately, the iframe is not something that I can get rid of in the short term, but it is worthwhile to investigate the alternatives for the future. – Paul Bouman Jun 26 '20 at 11:43