1. 概述
在本文中, 我们将说明 Spring Security 如何允许我们控制 HTTP 会话. 此控件的范围从会话超时到启用并发会话和其他高级安全配置.
2. 会话何时创建?
我们可以准确控制会话何时创建以及 Spring Security 如何与之交互:
always - 如果一个会话尚不存在, 将始终创建一个会话
ifRequired - 仅在需要时创建会话 (默认)
never - 框架永远不会创建会话本身, 但如果它已经存在, 它将使用一个
stateless - Spring Security 不会创建或使用任何会话
<http create-session="ifRequired">...</http>
Java 配置:
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http.sessionManagement()
- .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
- }
了解此配置仅控制 Spring Security 的功能非常重要 - 而不是整个应用程序. 如果我们不指示 Spring Security, 可能无法创建会话, 但我们的应用程序可能会!
默认情况下, Spring Security 会在需要时创建会话 - 这是 "ifRequired".
对于更无状态的应用程序,"never" 选项将确保 Spring Security 本身不会创建任何会话; 但是, 如果应用程序创建了一个, 那么 Spring Security 将使用它.
最后, 最严格的会话创建选项 - "stateless" - 保证应用程序根本不会创建任何会话.
这是在 Spring 3.1 中引入的, 它将有效地跳过部分 Spring Security 过滤器链. 主要是会话相关的部分, 如 HttpSessionSecurityContextRepository,SessionManagementFilter,RequestCacheFilter.
这些更严格的控制机制直接暗示不使用 cookie, 所以每个请求都需要重新进行身份验证. 这种无状态架构适用于 REST API 及其无状态约束. 它们也适用于基本和摘要式身份验证等身份验证机制.
3. Under The Hood
在执行身份验证过程之前, Spring Security 将运行一个负责在请求之间存储安全上下文的过滤器 - SecurityContextPersistenceFilter. 上下文将根据策略存储 - 默认情况下为 HttpSessionSecurityContextRepository - 它使用 HTTP 会话作为存储. 对于 strict 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 Security 会话注册表是至关重要.
要为同一用户启用允许多个并发会话的方案, 应在 xml 配置中使用元素:
- <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. 会话超时
会话超时后, 如果用户发送的会话 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");
6. 防止使用 URL 参数进行会话跟踪
在 URL 中公开会话信息的安全风险越来越大 (从 2007 年的第 7 位到 2013 年在 OWASP 排行榜前 10 位的第 2 位).
从 Spring 3.0 开始, 现在可以通过在命名空间中设置 disable-url-rewriting ="true" 来禁用将 jsessionid 附加到 URL 的 URL 重写逻辑.
或者, 从 Servlet 3.0 开始, 也可以在 Web.xml 中配置会话跟踪机制:
- <session-config>
- <tracking-mode>COOKIE</tracking-mode>
- </session-config>
编程方式
servletContext.setSessionTrackingModes(EnumSet.of(SessionTrackingMode.COOKIE));
这将选择存储 JSESSIONID 的位置 - 在 cookie 或 URL 参数中.
7. Spring Security 的会话固定保护
该框架通过配置在用户已有会话的情况但尝试再次进行身份验证时, 提供了针对典型会话固定攻击的保护:
<session-management session-fixation-protection="migrateSession"> ...
相应的 Java 配置:
- http.sessionManagement()
- .sessionFixation().migrateSession()
默认情况下, Spring Security 启用了此保护 ("migrateSession") - 在身份验证时, 会创建一个新的 HTTP 会话, 旧的会话将失效, 旧会话的属性将被复制.
如果这不是所需的行为, 则可以使用其他两个选项:
设置 "none" 时, 原始会话不会失效
设置 "newSession" 时, 将创建一个干净的会话, 而不会复制旧会话中的任何属性
8. 安全会话 Cookie
接下来, 我们将讨论如何保护会话 cookie.
我们可以使用 httpOnly 和 secure 标签来保护我们的会话 cookie:
httpOnly: 如果为 true, 那么浏览器脚本将无法访问 cookie
secure: 如果为 true, 则 cookie 将仅通过 HTTPS 连接发送
我们可以在 Web.xml 中为会话 cookie 设置这些标志:
- <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 为 true 且 secure 为 false.
我们来看看相应的 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
最后, 我们还可以使用 Filter 手动实现此目的:
- 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.Session 使用
9.1. Session Scoped Beans
只需在 Web-Context 中, 使用 @Scope 注释声明的 bean:
- @Component
- @Scope("session")
- public class Foo {
- ..
- }
或者使用 xml:
<bean id="foo" scope="session"/>
然后, bean 可以简单地注入另一个 bean:
- @Autowired
- private Foo theFoo;
Spring 会将新 bean 绑定到 HTTP Session 的生命周期.
9.2. 将会话注入控制器
原始 HTTP 会话也可以直接注入 Controller 方法:
- @RequestMapping(..)
- public void fooMethod(HttpSession session) {
- session.addAttribute(Constants.FOO, new Foo();
- //...
- Foo foo = (Foo) session.getAttribute(Constants.Foo);
- }
9.3. 获取会话
当前的 HTTP Session 也可以通过原始 Servlet API 以编程方式获得:
- ServletRequestAttributes attr = (ServletRequestAttributes)
- ????RequestContextHolder.currentRequestAttributes();
- HttpSession session= attr.getRequest().getSession(true); // true == allow create
10. 总结
在本文中, 我们讨论了使用 Spring Security 管理 Sessions. 此外, Spring Reference 包含一个非常好的会话管理常见问题解答.
与往常一样, 本文中提供的代码可以在 GitHub 上获得. 这是一个基于 Maven 的项目, 因此它应该很容易导入和运行.
来源: http://www.bubuko.com/infodetail-3066541.html