将 Spring 会话 JDBC 添加到我的 Spring 引导项目会破坏 iframe 中的身份验证
Adding Spring Session JDBC to my Spring Boot project breaks authentication in an iframe
我目前正在构建一个 Spring 启动 Web 应用程序(2.3.1,但在版本 2.1.7 和 2.1.5 中也观察到了以下问题),该应用程序受到 Spring 安全性的保护.我主要使用默认设置(例如嵌入式 Tomcat、嵌入式 H2 数据库、Spring Web-MVC)。我使用以下代码对许可 POST
映射进行了一些自定义身份验证:
UsernamePasswordAuthenticationToken authentication = ...;
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(authentication);
这很好用。因为我想用它来进行学生考试,所以我想通过身份验证来保持会话,所以万一服务器软件由于某种原因崩溃或宕机,当服务器再次出现时,没有人需要重新进行身份验证。
为此我使用了 Spring 会话 JDBC。我将以下依赖项添加到我的 pom.xml
:
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-jdbc</artifactId>
</dependency>
以及我的 application.properties
的以下行
spring.session.store-type=jdbc
我在本地测试时一切正常。我看到会话出现在我的数据库中,一切正常。然而,在实践中,应用程序是从 iframe
开始的:学习管理系统执行 LTI 启动,这是一个 POST 请求,其响应在 iframe
中结束。当我尝试进行测试部署时,出现 403
错误,表明我的请求未通过身份验证。经过一天令人沮丧的调试后,我最终能够将此问题确定为将 spring-session-jdbc
添加到我的项目会导致 Web 应用程序在 iframe
的每个请求上发送新的会话 ID cookie。 .如果我在没有 iframe
的情况下重复相同的请求,则相同的应用程序可以正常工作。如果我删除 spring-session-jdbc
依赖项,应用程序在 iframe
中也能正常工作。
如果我在 iframe 中执行身份验证,我会在 Spring 安全调试日志中看到以下内容(exec-1 是身份验证成功的地方,exec-2 是 iframe 在被执行后执行的请求由 exec-1 请求重定向):
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.
当我在常规浏览器 运行 中使用相同的 运行 应用程序执行相同的请求时 window,Spring 安全调试日志显示如下:
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'
对我来说这似乎很奇怪:如果这纯粹是浏览器问题,那么我是否使用 spring-session-jdbc
应该无关紧要。如果 spring-session-jdbc
如何存储我的身份验证有问题,那么它是否发生在 iframe
中应该无关紧要。我在这里缺少什么吗?我是不是遇到了错误?
我可以尝试一种变通方法,在 iframe 中使用一些 javascript 让 POST 出现在空白选项卡中,但这感觉很丑陋。
一般来说,我建议您不要在 iframe 中使用您的应用程序。
这会带来安全风险,您可以在 this answer.
中阅读更多相关信息
现在解释您所看到的行为。
Spring 安全使用 Session
cookie 来存储用户的会话。
Cookie 与域关联,因此,例如,如果存在与域 whosebug.com
关联的 cookie,则该 cookie 将包含在对 stackoverlow.com
.
的任何请求中
为了控制这种行为,cookies 还有一个名为 SameSite
的属性。
SameSite
attribute 可以有 3 个值,None
、Lax
或 Strict
。
当值为 None
时,它的行为如上所述(包含在所有请求中)。
当值为 Lax
时,cookie 将仅包含在顶级导航 GET
请求中。
当包含 Spring 会话依赖项时,Session
cookie SameSite
属性默认设置为 Lax
。
由于在 iframe 中呈现应用程序不是顶级导航,因此 Session
cookie 不包含在对 iframe 的请求中,应用程序无法知道用户已登录.
您可以使用 Spring 会话将 SameSite
属性显式设置为 None
。
同样,我会警告不要这样做,因为它会使您的应用程序容易受到 CSRF 和点击劫持攻击。
如果在考虑了安全隐患之后,您认为有必要将 SameSite
属性设置为 None
,您可以通过在依赖项中包含 Spring Session 并创建一个 custom CookieSerializer
.
我目前正在构建一个 Spring 启动 Web 应用程序(2.3.1,但在版本 2.1.7 和 2.1.5 中也观察到了以下问题),该应用程序受到 Spring 安全性的保护.我主要使用默认设置(例如嵌入式 Tomcat、嵌入式 H2 数据库、Spring Web-MVC)。我使用以下代码对许可 POST
映射进行了一些自定义身份验证:
UsernamePasswordAuthenticationToken authentication = ...;
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(authentication);
这很好用。因为我想用它来进行学生考试,所以我想通过身份验证来保持会话,所以万一服务器软件由于某种原因崩溃或宕机,当服务器再次出现时,没有人需要重新进行身份验证。
为此我使用了 Spring 会话 JDBC。我将以下依赖项添加到我的 pom.xml
:
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-jdbc</artifactId>
</dependency>
以及我的 application.properties
spring.session.store-type=jdbc
我在本地测试时一切正常。我看到会话出现在我的数据库中,一切正常。然而,在实践中,应用程序是从 iframe
开始的:学习管理系统执行 LTI 启动,这是一个 POST 请求,其响应在 iframe
中结束。当我尝试进行测试部署时,出现 403
错误,表明我的请求未通过身份验证。经过一天令人沮丧的调试后,我最终能够将此问题确定为将 spring-session-jdbc
添加到我的项目会导致 Web 应用程序在 iframe
的每个请求上发送新的会话 ID cookie。 .如果我在没有 iframe
的情况下重复相同的请求,则相同的应用程序可以正常工作。如果我删除 spring-session-jdbc
依赖项,应用程序在 iframe
中也能正常工作。
如果我在 iframe 中执行身份验证,我会在 Spring 安全调试日志中看到以下内容(exec-1 是身份验证成功的地方,exec-2 是 iframe 在被执行后执行的请求由 exec-1 请求重定向):
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.
当我在常规浏览器 运行 中使用相同的 运行 应用程序执行相同的请求时 window,Spring 安全调试日志显示如下:
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'
对我来说这似乎很奇怪:如果这纯粹是浏览器问题,那么我是否使用 spring-session-jdbc
应该无关紧要。如果 spring-session-jdbc
如何存储我的身份验证有问题,那么它是否发生在 iframe
中应该无关紧要。我在这里缺少什么吗?我是不是遇到了错误?
我可以尝试一种变通方法,在 iframe 中使用一些 javascript 让 POST 出现在空白选项卡中,但这感觉很丑陋。
一般来说,我建议您不要在 iframe 中使用您的应用程序。
这会带来安全风险,您可以在 this answer.
现在解释您所看到的行为。
Spring 安全使用 Session
cookie 来存储用户的会话。
Cookie 与域关联,因此,例如,如果存在与域 whosebug.com
关联的 cookie,则该 cookie 将包含在对 stackoverlow.com
.
为了控制这种行为,cookies 还有一个名为 SameSite
的属性。
SameSite
attribute 可以有 3 个值,None
、Lax
或 Strict
。
当值为 None
时,它的行为如上所述(包含在所有请求中)。
当值为 Lax
时,cookie 将仅包含在顶级导航 GET
请求中。
当包含 Spring 会话依赖项时,Session
cookie SameSite
属性默认设置为 Lax
。
由于在 iframe 中呈现应用程序不是顶级导航,因此 Session
cookie 不包含在对 iframe 的请求中,应用程序无法知道用户已登录.
您可以使用 Spring 会话将 SameSite
属性显式设置为 None
。
同样,我会警告不要这样做,因为它会使您的应用程序容易受到 CSRF 和点击劫持攻击。
如果在考虑了安全隐患之后,您认为有必要将 SameSite
属性设置为 None
,您可以通过在依赖项中包含 Spring Session 并创建一个 custom CookieSerializer
.