1、概览 {#1概览}
本文将带你了解如何在 Spring Security 中配置 Session 超时、Session 并发以及其他高级的 Session 安全设置。
2、何时创建 Session? {#2何时创建-session}
可以精确控制 Session 的创建时间,以及 Spring Security 与 Session 的交互方式:
always
:如果 Session 不存在,则会创建一个 Session。ifRequired
:仅在需要时才创建 Session(默认值)。never
:框架不会自己创建 Session,但如果 Session 已经存在,则会使用该 Session。stateless
:Spring Security 不会创建或使用 Session。
<http create-session="ifRequired">...</http>
Java 配置如下:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
return http.build();
}
注意,该配置只控制 Spring Security 的行为,而不是整个应用。如果指定 Spring Security 不创建 Session,它就不会创建 Session,但是应用可能会创建 Session!
默认情况下,Spring Security 会在需要时创建一个 Session,即 ifRequired
。
对于无状态应用,never
选项将确保 Spring Security 本身不会创建任何 Session。但如果应用创建了 Session,Spring Security 就会使用它。
最后,最严格的 Session 创建选项 stateless
(无状态)保证应用根本不会创建任何 Session。
这是在 Spring 3.1 中 引入 的,这会跳过 Spring Security Filter Chain 的部分内容,主要是 Session 相关部分,如 HttpSessionSecurityContextRepository
、SessionManagementFilter
和 RequestCacheFilter
。
这些更严格的控制机制直接意味着不使用 Cookie,因此每个请求都需要重新进行身份认证。
这种无状态架构与 REST API 及其无状态约束配合得很好。它们还能很好地与 Basic 和 Digest Authentication 等身份认证机制配合使用。
3、实现细节 {#3实现细节}
在运行身份认证流程之前,Spring Security 会运行一个 Filter,负责在请求之间存储 Security Context。这就是 SecurityContextPersistenceFilter
。
默认情况下,Context 将根据 HttpSessionSecurityContextRepository
策略进行存储,该策略使用 HTTP Session 作为存储介质。
对于严格的 create-session="stateless"
属性,将使用另一种策略 - NullSecurityContextRepository
,不会创建或使用 Session 来保存 Context。
4、并发 Session 控制 {#4并发-session-控制}
当一个已通过身份认证的用户 再次尝试进行身份认证 时,应用可以通过几种方式之一来处理该事件。它可以使用户的活动的 Session 失效,然后用新的 Session 再次对用户进行身份认证,或者允许两个 Session 同时存在。
启用并发 Session 控制支持的第一步是在 web.xml
中添加以下 Listener(监听器):
<listener>
<listener-class>
org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>
或者,可以把它定义为一个 Bean:
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
这是确保在 Session 销毁时通知 Spring Security Session 注册表的关键。
要允许同一用户同时有多个 Session,可以在 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、Session 超时 {#5session-超时}
5.1、处理 Session 超时 {#51处理-session-超时}
Session 超时后,如果用户使用过期的 Session ID 发送请求,会被重定向到一个可通过命名空间配置的 URL:
<session-management>
<concurrency-control expired-url="/sessionExpired.html" ... />
</session-management>
同样,如果用户使用未过期但完全无效的 Session ID 发送请求,也会被重定向到一个可配置的 URL:
<session-management invalid-session-url="/invalidSession.html">
...
</session-management>
下面是相应的 Java 配置:
http.sessionManagement()
.expiredUrl("/sessionExpired.html")
.invalidSessionUrl("/invalidSession.html");
5.2、在 Spring Boot 中配置 Session 超时 {#52在-spring-boot-中配置-session-超时}
在 Spring Boot 中可以通过 application.properties
配置嵌入式服务器的 Session 超时时间:
server.servlet.session.timeout=15m
如果不指定时间单位,默认为秒。
简而言之,使用此配置后,Session 将在 15 分钟未活动后过期。过了这段时间,Session 将被视为无效。
注意,如果使用的是 Tomcat 服务器,它只支持 分钟粒度 的会话超时配置,最小为一分钟。这意味着,如果指定 170 秒 的超时值,实际上的超时时间为 2 分钟。
最后,需要注意的是,Spring Session 也支持类似的属性(spring.session.timeout
)来实现相同的目的,如果未指定该属性,自动配置将回退到先前提到的属性的值。
6、防止使用 URL 参数进行会话跟踪 {#6防止使用-url-参数进行会话跟踪}
在 URL 中暴露 Session 信息是一个日益严重的 安全风险。
从 Spring 3.0 开始,可以通过在 <http>
命名空间中设置 disable-url-rewriting="true"
来禁用将 jsessionid
附加到 URL 的 URL 重写逻辑。
另外,从 Servlet 3.0 开始,也可以在 web.xml
中配置 Session 跟踪机制:
<session-config>
<tracking-mode>COOKIE</tracking-mode>
</session-config>
编程式设置如下:
servletContext.setSessionTrackingModes(EnumSet.of(SessionTrackingMode.COOKIE));
这选择的是 JSESSIONID 的存储位置 - Cookie 或 URL 参数。
7、使用 Spring Security 进行 Session Fixation 保护 {#7使用-spring-security-进行-session-fixation-保护}
框架通过配置用户尝试重新认证时对现有 Session 的处理方式,提供了对典型 Session Fixation 攻击的保护。
<session-management session-fixation-protection="migrateSession"> ...
对应的 Java 配置如下:
http.sessionManagement()
.sessionFixation().migrateSession()
默认情况下,Spring Security 启用了这种保护(migrateSession
)。身份认证时,会创建一个新的 HTTP Session,旧的 Session 会失效,旧 Session 中的属性(attribute)会被复制过来。
如果这不是想要的,还有另外两种选择:
- 设置为
none
时,原始 Session 不会失效。 - 设置
newSession
时,会创建一个干净的 Session,不会复制旧 Session 的任何属性。
8、Session Cookie 的安全 {#8session-cookie-的安全}
可以使用 httpOnly
和 secure
标志来确保 Session Cookie 的安全:
httpOnly
:如果为true
,则浏览器脚本(Javascript)将无法访问 Cookiesecure
:如果为true
,则 Cookie 只能通过 HTTPS 连接发送
可以在 web.xml
中为 Session 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使用-session}
9.1、Session Scope 的 Bean {#91session-scope-的-bean}
只需在 Web Context 中声明的 Bean 上使用 @Scope
注解,就可以用 session 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、将原始 Session 注入 Controller {#92将原始-session-注入-controller}
原始 HTTP Session 可以直接注入 Controller 方法:
@RequestMapping(..)
public void fooMethod(HttpSession session) {
session.setAttribute(Constants.FOO, new Foo());
//...
Foo foo = (Foo) session.getAttribute(Constants.FOO);
}
9.3、获取原始 Session {#93获取原始-session}
也可通过原始 Servlet API 以编程式获取当前 HTTP Session:
ServletRequestAttributes attr = (ServletRequestAttributes)
RequestContextHolder.currentRequestAttributes();
HttpSession session= attr.getRequest().getSession(true); // true,如果 Session 不存在则自动创建
10、总结 {#10总结}
本文介绍了如何使用 Spring Security 来管理 Http Session,包括并发设置、超时设置以及其他的高级安全设置等。
Ref:https://www.baeldung.com/spring-security-session