1. 概述
在本教程中,我们将说明Spring Security 如何允许我们控制 HTTP 会话。
此控制范围从会话超时到启用并发会话和其他高级安全配置。
2. 会话何时创建?
我们可以精确控制会话的创建时间以及 Spring 安全性将如何与之交互:
- always – 如果会话尚不存在,将始终创建一个会话。
- ifRequired – 仅在需要时才会创建会话(默认)。
- never – 框架永远不会创建会话本身,但如果它已经存在,它将使用一个会话。
- stateless – Spring 安全性不会创建或使用任何会话。
<http create-session="ifRequired">...</http>
下面是 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 及其无状态约束配合得很好。它们还可以很好地与身份验证机制(如基本身份验证和摘要式身份验证)配合使用。
3. 引擎盖下
在运行身份验证过程之前,Spring 安全性将运行一个过滤器,负责在请求之间存储安全上下文。这是SecurityContextPersistenceFilter。
默认情况下,上下文将根据策略 HttpSessionSecurityContextRepository进行存储,该策略使用 HTTP 会话作为存储。
对于严格的create-session=“stateless”属性,此策略将被另一个策略(NullSecurityContextRepository)替换,并且不会创建或使用任何会话来保留上下文。
4. 并发会话控制
当已通过身份验证的用户尝试再次进行身份验证时,应用程序可以通过以下几种方式之一处理该事件。它可以使用户的活动会话无效,并使用新会话再次对用户进行身份验证,也可以允许两个会话同时存在。
启用并发会话控制支持的第一步是在 Web 中添加以下侦听器.xml:
<listener>
<listener-class>
org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>
或者我们可以将其定义为 Bean:
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
这对于确保在会话被销毁时通知 Spring 安全会话注册表至关重要。
为了允许同一用户有多个并发会话,应在 XML 配置中使用 <session-management> 元素:
<http ...>
<session-management>
<concurrency-control max-sessions="2" />
</session-management>
</http>
或者我们可以通过 Java 配置来做到这一点:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().maximumSessions(2)
}
5. 会话超时
5.1. 处理会话超时
会话超时后,如果用户发送会话ID 过期的请求,他们将被重定向到可通过命名空间配置的 URL:
<session-management>
<concurrency-control expired-url="/sessionExpired.html" ... />
</session-management>
同样,如果用户发送的会话 ID 未过期但完全无效的请求,他们也会被重定向到可配置的 URL:
<session-management invalid-session-url="/invalidSession.html">
...
</session-management>
下面是相应的 Java 配置:
http.sessionManagement()
.expiredUrl("/sessionExpired.html")
.invalidSessionUrl("/invalidSession.html");
5.2. 使用 Spring 引导配置会话超时
我们可以使用属性轻松配置嵌入式服务器的会话超时值:
server.servlet.session.timeout=15m
如果我们不指定持续时间单位,Spring 将假定它是秒。
简而言之,使用此配置,会话将在 15 分钟不活动后过期。在此时间段后,会话将被视为无效。
如果我们将项目配置为使用 Tomcat,我们必须记住,它仅支持会话超时的分钟精度,至少为一分钟。这意味着,例如,如果我们指定超时值170s,则会导致两分钟的超时。
最后,值得一提的是,即使Spring Session为此目的支持类似的属性(spring.session.timeout),如果未指定,自动配置将回退到我们首先提到的属性的值。
6. 防止使用 URL 参数进行会话跟踪
在URL中暴露会话信息是一个不断增长的安全风险(从2007年的第七位到2013年的OWASP十大名单的第二位)。
从Spring 3.0开始,现在可以通过在<http>namespace中设置disable-url-rewrite=“true”来禁用将jsessionid附加到URL的URL重写逻辑。
或者,从 Servlet 3.0 开始,也可以在 Web 中配置会话跟踪机制.xml:
<session-config>
<tracking-mode>COOKIE</tracking-mode>
</session-config>
并以编程方式:
servletContext.setSessionTrackingModes(EnumSet.of(SessionTrackingMode.COOKIE));
这选择在 cookie 或 URL 参数中存储JSESSIONID— 的位置。
7. 带弹簧安全性的会话固定保护
该框架通过配置用户尝试再次进行身份验证时对现有会话发生的情况来提供针对典型会话固定攻击的保护:
<session-management session-fixation-protection="migrateSession"> ...
下面是相应的 Java 配置:
http.sessionManagement()
.sessionFixation().migrateSession()
默认情况下,Spring 安全性启用了此保护(“迁移会话”)。在身份验证时,将创建新的 HTTP 会话,使旧会话失效,并复制旧会话中的属性。
如果这不是我们想要的,还有另外两个选项可用:
- 设置“无”后,原始会话不会失效。
- 设置“newSession”后,将创建一个干净的会话,而不会复制旧会话中的任何属性。
8.安全会话饼干
接下来,我们将讨论如何保护我们的会话 cookie。
我们可以使用httpOnly和安全标志来保护我们的会话 cookie:
- httpOnly:如果为 true,则浏览器脚本将无法访问 cookie
- 安全:如果为 true,则 cookie 将仅通过 HTTPS 连接发送
我们可以在网络中为我们的会话 cookie 设置这些标志.xml:
<session-config>
<session-timeout>1</session-timeout>
<cookie-config>
<http-only>true</http-only>
<secure>true</secure>
</cookie-config>
</session-config>
此配置选项从 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);
}
}
9. 使用会话
9.1. 会话范围的 bean
只需在 Web 上下文中声明的 bean 上使用@Scope注释,即可使用会话作用域定义 Bean:
@Component
@Scope("session")
public class Foo { .. }
或使用 XML:
<bean id="foo" scope="session"/>
然后可以将豆子注入到另一个豆子中:
@Autowired
private Foo theFoo;
Spring 会将新 bean 绑定到 HTTP 会话的生命周期。
9.2. 将原始会话注入控制器
原始 HTTP 会话也可以直接注入到控制器方法中:
@RequestMapping(..)
public void fooMethod(HttpSession session) {
session.setAttribute(Constants.FOO, new Foo());
//...
Foo foo = (Foo) session.getAttribute(Constants.FOO);
}
9.3. 获取原始会话
当前的 HTTP 会话也可以通过原始 Servlet API 以编程方式获取:
ServletRequestAttributes attr = (ServletRequestAttributes)
RequestContextHolder.currentRequestAttributes();
HttpSession session= attr.getRequest().getSession(true); // true == allow create
10. 结论
在本文中,我们讨论了使用 Spring 安全性管理会话。
此外,Spring 参考包含一个非常好的会话管理常见问题解答。
与往常一样,本文中介绍的代码可在GitHub 上找到。这是一个基于 Maven 的项目,因此应该易于导入和按原样运行。
本文详细介绍了Spring Security中会话管理的各项功能,包括会话创建、并发会话控制、会话超时处理、会话固定保护及会话安全性配置等。通过XML和Java配置示例,读者可以了解到如何有效控制和配置HTTP会话。

2919

被折叠的 条评论
为什么被折叠?



