在本教程中,我们将说明Spring Security 如何允许我们控制 HTTP 会话。
此控制范围从会话超时到启用并发会话和其他高级安全配置。
我们可以精确控制会话的创建时间以及 Spring 安全性将如何与之交互:
...
下面是 Java 配置:
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http.sessionManagement()
- .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
- }
了解此配置仅控制 Spring 安全性的功能,而不是整个应用程序,这一点非常重要。如果我们指示 Spring Security 不要创建会话,它将不会创建会话,但我们的应用程序可能会创建会话!
默认情况下,Spring Security 将在需要时创建一个会话——这是“如果需要”。
对于更无状态的应用程序,“从不”选项将确保 Spring 安全性本身不会创建任何会话。但是,如果应用程序创建了一个,Spring Security将使用它。
最后,最严格的会话创建选项“无状态”保证应用程序根本不会创建任何会话。
这是在Spring 3.1中引入的,它将有效地跳过Spring Security过滤器链的部分 - 主要是与会话相关的部分,如HttpSessionSecurityContextRepository,SessionManagementFilter和RequestCacheFilter。
这些更严格的控制机制直接意味着不使用cookie,因此每个请求都需要重新进行身份验证。
这种无状态体系结构与 REST API 及其无状态约束配合得很好。它们还可以很好地与身份验证机制(如基本身份验证和摘要式身份验证)配合使用。
在运行身份验证过程之前,Spring 安全性将运行一个过滤器,负责在请求之间存储安全上下文。这是SecurityContextPersistenceFilter。
默认情况下,上下文将根据策略 HttpSessionSecurityContextRepository进行存储,该策略使用 HTTP 会话作为存储。
对于严格的create-session=“stateless”属性,此策略将被另一个策略(NullSecurityContextRepository)替换,并且不会创建或使用任何会话来保留上下文。
当已通过身份验证的用户尝试再次进行身份验证时,应用程序可以通过以下几种方式之一处理该事件。它可以使用户的活动会话无效,并使用新会话再次对用户进行身份验证,也可以允许两个会话同时存在。
启用并发会话控制支持的第一步是在 Web 中添加以下侦听器.xml:
-
- org.springframework.security.web.session.HttpSessionEventPublisher
-
或者我们可以将其定义为 Bean:
- @Bean
- public HttpSessionEventPublisher httpSessionEventPublisher() {
- return new HttpSessionEventPublisher();
- }
这对于确保在会话被销毁时通知 Spring 安全会话注册表至关重要。
为了允许同一用户有多个并发会话,应在 XML 配置中使用
-
-
-
或者我们可以通过 Java 配置来做到这一点:
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http.sessionManagement().maximumSessions(2)
- }
会话超时后,如果用户发送会话ID 过期的请求,他们将被重定向到可通过命名空间配置的 URL:
-
同样,如果用户发送的会话 ID 未过期但完全无效的请求,他们也会被重定向到可配置的 URL:
- ...
下面是相应的 Java 配置:
- http.sessionManagement()
- .expiredUrl("/sessionExpired.html")
- .invalidSessionUrl("/invalidSession.html");
我们可以使用属性轻松配置嵌入式服务器的会话超时值:
server.servlet.session.timeout=15m
如果我们不指定持续时间单位,Spring 将假定它是秒。
简而言之,使用此配置,会话将在 15 分钟不活动后过期。在此时间段后,会话将被视为无效。
如果我们将项目配置为使用 Tomcat,我们必须记住,它仅支持会话超时的分钟精度,至少为一分钟。这意味着,例如,如果我们指定超时值170s,则会导致两分钟的超时。
最后,值得一提的是,即使Spring Session为此目的支持类似的属性(spring.session.timeout),如果未指定,自动配置将回退到我们首先提到的属性的值。
在URL中暴露会话信息是一个不断增长的安全风险(从2007年的第七位到2013年的OWASP十大名单的第二位)。
从Spring 3.0开始,现在可以通过在
或者,从 Servlet 3.0 开始,也可以在 Web 中配置会话跟踪机制.xml:
-
COOKIE
并以编程方式:
servletContext.setSessionTrackingModes(EnumSet.of(SessionTrackingMode.COOKIE));
这选择在 cookie 或 URL 参数中存储JSESSIONID— 的位置。
该框架通过配置用户尝试再次进行身份验证时对现有会话发生的情况来提供针对典型会话固定攻击的保护:
...
下面是相应的 Java 配置:
- http.sessionManagement()
- .sessionFixation().migrateSession()
默认情况下,Spring 安全性启用了此保护(“迁移会话”)。在身份验证时,将创建新的 HTTP 会话,使旧会话失效,并复制旧会话中的属性。
如果这不是我们想要的,还有另外两个选项可用:
接下来,我们将讨论如何保护我们的会话 cookie。
我们可以使用httpOnly和安全标志来保护我们的会话 cookie:
我们可以在网络中为我们的会话 cookie 设置这些标志.xml:
-
1 -
-
true -
true -
此配置选项从 Java servlet 3 开始可用。默认情况下,http-only为真,安全为假。
我们再来看看对应的 Java 配置:
- public class MainWebAppInitializer implements WebApplicationInitializer {
- @Override
- public void onStartup(ServletContext sc) throws ServletException {
- // ...
- sc.getSessionCookieConfig().setHttpOnly(true);
- sc.getSessionCookieConfig().setSecure(true);
- }
- }
如果我们使用的是 Spring Boot,我们可以在application.properties 中设置这些标志:
- server.servlet.session.cookie.http-only=true
- server.servlet.session.cookie.secure=true
最后,我们还可以使用过滤器手动实现这一点:
- public class SessionFilter implements Filter {
- @Override
- public void doFilter(
- ServletRequest request, ServletResponse response, FilterChain chain)
- throws IOException, ServletException {
- HttpServletRequest req = (HttpServletRequest) request;
- HttpServletResponse res = (HttpServletResponse) response;
- Cookie[] allCookies = req.getCookies();
- if (allCookies != null) {
- Cookie session =
- Arrays.stream(allCookies).filter(x -> x.getName().equals("JSESSIONID"))
- .findFirst().orElse(null);
- if (session != null) {
- session.setHttpOnly(true);
- session.setSecure(true);
- res.addCookie(session);
- }
- }
- chain.doFilter(req, res);
- }
- }
只需在 Web 上下文中声明的 bean 上使用@Scope注释,即可使用会话作用域定义 Bean:
- @Component
- @Scope("session")
- public class Foo { .. }
或使用 XML:
然后可以将豆子注入到另一个豆子中:
- @Autowired
- private Foo theFoo;
Spring 会将新 bean 绑定到 HTTP 会话的生命周期。
原始 HTTP 会话也可以直接注入到控制器方法中:
- @RequestMapping(..)
- public void fooMethod(HttpSession session) {
- session.setAttribute(Constants.FOO, new Foo());
- //...
- Foo foo = (Foo) session.getAttribute(Constants.FOO);
- }
当前的 HTTP 会话也可以通过原始 Servlet API 以编程方式获取:
- ServletRequestAttributes attr = (ServletRequestAttributes)
- RequestContextHolder.currentRequestAttributes();
- HttpSession session= attr.getRequest().getSession(true); // true == allow create
在本文中,我们讨论了使用 Spring 安全性管理会话。
此外,Spring 参考包含一个非常好的会话管理常见问题解答。
与往常一样,本文中介绍的代码可在GitHub 上找到。这是一个基于 Maven 的项目,因此应该易于导入和按原样运行。